lutong 3 месяцев назад
Родитель
Сommit
a4718023bf

+ 148 - 0
alien-entity/src/main/java/shop/alien/entity/store/StoreRenovationRequirement.java

@@ -0,0 +1,148 @@
+package shop.alien.entity.store;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 装修需求动态表
+ * <p>
+ * 注意:装修需求的图片和视频附件直接存储在本表的 attachment_urls 字段中
+ * 存储格式:多个URL用逗号拼接,例如:"url1,url2,url3"
+ * 最多支持9个附件(图片或视频)
+ * </p>
+ *
+ * @author auto-generated
+ * @since 2025-01-15
+ */
+@Data
+@JsonInclude
+@TableName("store_renovation_requirement")
+@ApiModel(value = "StoreRenovationRequirement对象", description = "装修需求动态表")
+public class StoreRenovationRequirement {
+
+    @ApiModelProperty(value = "主键")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "门店ID(发布装修需求的普通公司门店)")
+    @TableField("store_id")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "需求标题")
+    @TableField("requirement_title")
+    private String requirementTitle;
+
+    @ApiModelProperty(value = "装修类型(1:新房装修, 2:旧房改造, 3:局部装修)")
+    @TableField("renovation_type")
+    private Integer renovationType;
+
+    @ApiModelProperty(value = "房屋面积(平方米)")
+    @TableField("house_area")
+    private BigDecimal houseArea;
+
+    @ApiModelProperty(value = "装修预算(万元)")
+    @TableField("renovation_budget")
+    private BigDecimal renovationBudget;
+
+    @ApiModelProperty(value = "详细需求描述")
+    @TableField("detailed_requirement")
+    private String detailedRequirement;
+
+    @ApiModelProperty(value = "房屋图纸附件(图片和视频URL,多个URL用逗号拼接,最多9个)")
+    @TableField("attachment_urls")
+    private String attachmentUrls;
+
+    @ApiModelProperty(value = "期望装修时间")
+    @TableField("expected_renovation_time")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    private Date expectedRenovationTime;
+
+    @ApiModelProperty(value = "联系人姓名")
+    @TableField("contact_name")
+    private String contactName;
+
+    @ApiModelProperty(value = "联系电话")
+    @TableField("contact_phone")
+    private String contactPhone;
+
+    @ApiModelProperty(value = "所在城市")
+    @TableField("city")
+    private String city;
+
+    @ApiModelProperty(value = "城市编码(adcode)")
+    @TableField("city_adcode")
+    private String cityAdcode;
+
+    @ApiModelProperty(value = "详细地址")
+    @TableField("detailed_address")
+    private String detailedAddress;
+
+    @ApiModelProperty(value = "服务协议确认(0:未确认, 1:已确认)")
+    @TableField("agreement_confirmed")
+    private Integer agreementConfirmed;
+
+    @ApiModelProperty(value = "动态状态(0:草稿, 1:已发布, 2:已下架)")
+    @TableField("status")
+    private Integer status;
+
+    @ApiModelProperty(value = "浏览数")
+    @TableField("view_count")
+    private Integer viewCount;
+
+    @ApiModelProperty(value = "咨询数(装修商家查看并联系的数量)")
+    @TableField("inquiry_count")
+    private Integer inquiryCount;
+
+    @ApiModelProperty(value = "审核状态(0:待审核, 1:审核通过, 2:审核失败)")
+    @TableField("audit_status")
+    private Integer auditStatus;
+
+    @ApiModelProperty(value = "审核失败原因")
+    @TableField("audit_reason")
+    private String auditReason;
+
+    @ApiModelProperty(value = "审核时间")
+    @TableField("audit_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date auditTime;
+
+    @ApiModelProperty(value = "审核人ID")
+    @TableField("audit_user_id")
+    private Integer auditUserId;
+
+    @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
+    @TableField("delete_flag")
+    @TableLogic
+    private Integer deleteFlag;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "创建人ID")
+    @TableField("created_user_id")
+    private Integer createdUserId;
+
+    @ApiModelProperty(value = "修改时间")
+    @TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updatedTime;
+
+    @ApiModelProperty(value = "修改人ID")
+    @TableField("updated_user_id")
+    private Integer updatedUserId;
+}
+

+ 94 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/StoreRenovationRequirementDto.java

@@ -0,0 +1,94 @@
+package shop.alien.entity.store.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+import shop.alien.entity.store.dto.deserializer.StringToListDeserializer;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 装修需求动态DTO
+ *
+ * @author auto-generated
+ * @since 2025-01-15
+ */
+@Data
+@JsonInclude
+@ApiModel(value = "StoreRenovationRequirementDto对象", description = "装修需求动态DTO")
+public class StoreRenovationRequirementDto {
+
+    @ApiModelProperty(value = "主键")
+    private Integer id;
+
+    @ApiModelProperty(value = "门店ID")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "需求标题")
+    private String requirementTitle;
+
+    @ApiModelProperty(value = "装修类型(1:新房装修, 2:旧房改造, 3:局部装修)")
+    private Integer renovationType;
+
+    @ApiModelProperty(value = "房屋面积(平方米)")
+    private BigDecimal houseArea;
+
+    @ApiModelProperty(value = "装修预算(万元)")
+    private BigDecimal renovationBudget;
+
+    @ApiModelProperty(value = "详细需求描述")
+    private String detailedRequirement;
+
+    @ApiModelProperty(value = "期望装修时间")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    private Date expectedRenovationTime;
+
+    @ApiModelProperty(value = "联系人姓名")
+    private String contactName;
+
+    @ApiModelProperty(value = "联系电话")
+    private String contactPhone;
+
+    @ApiModelProperty(value = "所在城市")
+    private String city;
+
+    @ApiModelProperty(value = "城市编码(adcode)")
+    private String cityAdcode;
+
+    @ApiModelProperty(value = "详细地址")
+    private String detailedAddress;
+
+    @ApiModelProperty(value = "服务协议确认(0:未确认, 1:已确认)")
+    private Integer agreementConfirmed;
+
+    @ApiModelProperty(value = "动态状态(0:草稿, 1:已发布, 2:已下架)")
+    private Integer status;
+
+    @ApiModelProperty(value = "浏览数")
+    private Integer viewCount;
+
+    @ApiModelProperty(value = "咨询数")
+    private Integer inquiryCount;
+
+    @ApiModelProperty(value = "附件列表(图片/视频URL列表)")
+    @JsonDeserialize(using = StringToListDeserializer.class)
+    private List<String> attachmentUrls;
+
+    @ApiModelProperty(value = "创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "修改时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updatedTime;
+}
+

+ 17 - 0
alien-entity/src/main/java/shop/alien/mapper/StoreRenovationRequirementMapper.java

@@ -0,0 +1,17 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import shop.alien.entity.store.StoreRenovationRequirement;
+
+/**
+ * 装修需求动态表 Mapper 接口
+ *
+ * @author auto-generated
+ * @since 2025-01-15
+ */
+@Mapper
+public interface StoreRenovationRequirementMapper extends BaseMapper<StoreRenovationRequirement> {
+
+}
+

+ 171 - 0
alien-entity/src/main/resources/db/migration/renovation_chat_analysis.md

@@ -0,0 +1,171 @@
+# 装修需求聊天功能分析报告
+
+## 一、现有系统支持情况
+
+### ✅ 已支持的功能
+
+1. **聊天表结构(LifeMessage)**
+   - 支持 `user_` + 手机号 格式(用户端)
+   - 支持 `store_` + 手机号 格式(商户端)
+   - 有 `businessId` 字段可关联装修需求ID
+   - 支持多种消息类型(文本、图片、链接等)
+   - 支持已读/未读状态管理
+
+2. **WebSocket实时通信**
+   - 已实现 WebSocket 实时消息推送
+   - 消息保存时可通过 `businessId` 关联装修需求
+
+3. **现有消息接口**
+   - `/message/getMessageList` - 获取消息列表
+   - `/message/getMessageListByReceiverId` - 获取聊天详情
+   - `/message/getAllNoReadCount` - 获取未读消息数
+
+## 二、需要补充的功能
+
+### 1. 从装修需求发起聊天接口
+
+**需求场景:**
+- 装修商铺查看装修需求后,点击"联系商家"按钮发起聊天
+- 商户查看装修需求后,点击"联系装修商"按钮发起聊天
+
+**需要实现的接口:**
+```
+POST /renovation/startChat
+参数:
+  - requirementId: 装修需求ID
+  - currentUserId: 当前登录用户ID(商户或装修商铺)
+  
+返回:
+  - senderId: 发送方ID(格式:store_手机号 或 user_手机号)
+  - receiverId: 接收方ID(格式:store_手机号 或 user_手机号)
+  - chatInitiated: 是否首次发起聊天(用于统计咨询数)
+```
+
+**实现逻辑:**
+1. 根据 requirementId 查询装修需求信息
+2. 获取装修需求的联系信息(contactPhone)
+3. 根据 store_id 查询装修商铺的 StoreUser,获取手机号
+4. 根据 contactPhone 查询对应的用户(LifeUser 或 StoreUser)
+5. 构造发送方和接收方的ID(user_ 或 store_ + 手机号)
+6. 如果是首次发起聊天,更新 inquiry_count + 1
+7. 返回对方ID,前端可直接跳转到聊天页面
+
+### 2. ID格式转换逻辑
+
+**问题:**
+- `StoreRenovationRequirement.contactPhone` 存储的是普通手机号
+- `StoreRenovationRequirement.store_id` 关联装修商铺
+- 需要转换为聊天系统需要的格式(`user_`/`store_` + 手机号)
+
+**解决方案:**
+```java
+// 1. 获取装修商铺的ID
+StoreInfo storeInfo = storeInfoMapper.selectById(storeId);
+StoreUser storeUser = storeUserMapper.selectOne(
+    new QueryWrapper<StoreUser>().eq("store_id", storeId)
+);
+String renovationStoreId = "store_" + storeUser.getPhone();
+
+// 2. 获取发布需求的商户ID
+// 方式1:通过 contactPhone 查找
+LifeUser lifeUser = lifeUserMapper.selectOne(
+    new QueryWrapper<LifeUser>().eq("user_phone", contactPhone)
+);
+String merchantId = "user_" + contactPhone;
+
+// 方式2:如果有 created_user_id,可通过 StoreUser 查找
+StoreUser merchantStoreUser = storeUserMapper.selectById(createdUserId);
+String merchantId = "store_" + merchantStoreUser.getPhone();
+```
+
+### 3. 消息类型扩展
+
+**当前消息类型:**
+- 1-文本
+- 2-图片
+- 3-链接
+- 4-12: 二手交易相关
+- 8: 律师消息
+
+**建议新增:**
+- 13: 装修需求咨询消息(可在消息列表显示特殊标识)
+- 或直接使用 type=1(文本),通过 businessId 区分业务场景
+
+### 4. 咨询数统计
+
+**实现位置:**
+在发起聊天时(首次聊天),更新 `inquiry_count` 字段:
+```java
+// 检查是否已有聊天记录
+LambdaQueryWrapper<LifeMessage> wrapper = new LambdaQueryWrapper<>();
+wrapper.eq(LifeMessage::getBusinessId, requirementId);
+wrapper.eq(LifeMessage::getSenderId, senderId);
+wrapper.eq(LifeMessage::getReceiverId, receiverId);
+long chatCount = lifeMessageMapper.selectCount(wrapper);
+
+if (chatCount == 0) {
+    // 首次聊天,增加咨询数
+    StoreRenovationRequirement requirement = requirementMapper.selectById(requirementId);
+    requirement.setInquiryCount(requirement.getInquiryCount() + 1);
+    requirementMapper.updateById(requirement);
+}
+```
+
+### 5. 消息列表展示装修需求信息
+
+**需求:**
+在消息列表中,如果是装修需求相关的聊天,显示装修需求标题等信息
+
+**实现方式:**
+- 查询消息列表时,通过 `businessId` 关联查询装修需求信息
+- 在 LifeMessageVo 中添加装修需求相关字段(可选)
+
+## 三、建议实现的接口
+
+### 1. 发起装修需求聊天接口
+```
+POST /renovation/startChat
+```
+
+### 2. 获取装修需求聊天信息接口
+```
+GET /renovation/getChatInfo/{requirementId}
+返回:发送方ID、接收方ID、是否已发起过聊天等
+```
+
+### 3. WebSocket消息扩展
+在发送消息时,确保 `businessId` 字段设置为装修需求ID:
+```java
+lifeMessage.setBusinessId(requirementId);
+```
+
+## 四、数据库字段说明
+
+### StoreRenovationRequirement 表
+- `id`: 装修需求ID(用作 businessId)
+- `store_id`: 装修商铺ID(可通过此ID查询装修商铺的StoreUser获取手机号)
+- `contact_phone`: 发布需求的商户手机号
+- `created_user_id`: 创建用户ID(可辅助查找商户)
+
+### LifeMessage 表
+- `sender_id`: 发送方ID(user_手机号 或 store_手机号)
+- `receiver_id`: 接收方ID(user_手机号 或 store_手机号)
+- `business_id`: 业务ID(设置为装修需求ID)
+- `type`: 消息类型(建议使用 1-文本,或新增 13-装修需求咨询)
+
+## 五、总结
+
+**现状:** 基础的聊天功能已完整支持,可直接使用 LifeMessage 表进行聊天。
+
+**待补充:** 
+1. 从装修需求页面发起聊天的便捷接口
+2. ID格式转换逻辑(手机号 -> user_/store_ + 手机号)
+3. 咨询数统计功能
+4. 可选:装修需求消息类型标识
+
+**优先级:**
+- 🔴 高:发起聊天接口、ID格式转换
+- 🟡 中:咨询数统计
+- 🟢 低:消息类型扩展、消息列表展示装修需求信息
+
+

+ 38 - 0
alien-entity/src/main/resources/db/migration/store_renovation_requirement.sql

@@ -0,0 +1,38 @@
+-- 装修需求动态表
+CREATE TABLE `store_renovation_requirement` (
+    `id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
+    `store_id` INT NOT NULL COMMENT '门店ID(发布装修需求的普通公司门店)',
+    `requirement_title` VARCHAR(200) NOT NULL COMMENT '需求标题',
+    `renovation_type` TINYINT NOT NULL COMMENT '装修类型(1:新房装修, 2:旧房改造, 3:局部装修)',
+    `house_area` DECIMAL(10, 2) DEFAULT NULL COMMENT '房屋面积(平方米)',
+    `renovation_budget` DECIMAL(10, 2) DEFAULT NULL COMMENT '装修预算(万元)',
+    `detailed_requirement` TEXT COMMENT '详细需求描述',
+    `attachment_urls` VARCHAR(2000) DEFAULT NULL COMMENT '房屋图纸附件(图片和视频URL,多个URL用逗号拼接,最多9个)',
+    `expected_renovation_time` DATE DEFAULT NULL COMMENT '期望装修时间',
+    `contact_name` VARCHAR(50) NOT NULL COMMENT '联系人姓名',
+    `contact_phone` VARCHAR(20) NOT NULL COMMENT '联系电话',
+    `city` VARCHAR(50) NOT NULL COMMENT '所在城市',
+    `city_adcode` VARCHAR(20) DEFAULT NULL COMMENT '城市编码(adcode)',
+    `detailed_address` VARCHAR(200) NOT NULL COMMENT '详细地址',
+    `agreement_confirmed` TINYINT(1) DEFAULT '0' COMMENT '服务协议确认(0:未确认, 1:已确认)',
+    `status` TINYINT DEFAULT '0' COMMENT '动态状态(0:草稿, 1:已发布, 2:已下架)',
+    `view_count` INT DEFAULT '0' COMMENT '浏览数',
+    `inquiry_count` INT DEFAULT '0' COMMENT '咨询数(装修商家查看并联系的数量)',
+    `audit_status` TINYINT DEFAULT '0' COMMENT '审核状态(0:待审核, 1:审核通过, 2:审核失败)',
+    `audit_reason` VARCHAR(500) DEFAULT NULL COMMENT '审核失败原因',
+    `audit_time` DATETIME DEFAULT NULL COMMENT '审核时间',
+    `audit_user_id` INT DEFAULT NULL COMMENT '审核人ID',
+    `delete_flag` TINYINT(1) DEFAULT '0' COMMENT '删除标记, 0:未删除, 1:已删除',
+    `created_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    `created_user_id` INT DEFAULT NULL COMMENT '创建人ID',
+    `updated_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
+    `updated_user_id` INT DEFAULT NULL COMMENT '修改人ID',
+    PRIMARY KEY (`id`),
+    KEY `idx_store_id` (`store_id`),
+    KEY `idx_status` (`status`),
+    KEY `idx_audit_status` (`audit_status`),
+    KEY `idx_city` (`city`),
+    KEY `idx_renovation_type` (`renovation_type`),
+    KEY `idx_updated_time` (`updated_time`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='装修需求动态表';
+

+ 209 - 0
alien-store/src/main/java/shop/alien/store/controller/StoreRenovationRequirementController.java

@@ -0,0 +1,209 @@
+package shop.alien.store.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import io.swagger.annotations.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.dto.StoreRenovationRequirementDto;
+import shop.alien.store.service.StoreRenovationRequirementService;
+
+import java.util.List;
+
+/**
+ * 装修需求动态表 Controller
+ *
+ * @author auto-generated
+ * @since 2025-01-15
+ */
+@Slf4j
+@Api(tags = {"装修需求动态"})
+@ApiSort(1)
+@CrossOrigin
+@RestController
+@RequestMapping("/renovation/requirement")
+@RequiredArgsConstructor
+public class StoreRenovationRequirementController {
+
+    private final StoreRenovationRequirementService requirementService;
+
+    @ApiOperation("发布或更新装修需求")
+    @ApiOperationSupport(order = 1)
+    @PostMapping("/saveOrUpdate")
+    public R<Boolean> saveOrUpdate(@RequestBody StoreRenovationRequirementDto dto) {
+        log.info("StoreRenovationRequirementController.saveOrUpdate?dto={}", dto);
+        try {
+            boolean result = requirementService.saveOrUpdateRequirement(dto);
+            if (result) {
+                return R.success("操作成功");
+            }
+            return R.fail("操作失败");
+        } catch (Exception e) {
+            log.error("保存装修需求失败: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("获取装修需求详情")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "需求ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/getDetail")
+    public R<StoreRenovationRequirementDto> getDetail(@RequestParam Integer id) {
+        log.info("StoreRenovationRequirementController.getDetail?id={}", id);
+        try {
+            // 增加浏览数
+            requirementService.incrementViewCount(id);
+            
+            StoreRenovationRequirementDto dto = requirementService.getRequirementDetail(id);
+            if (dto == null) {
+                return R.fail("装修需求不存在");
+            }
+            return R.data(dto);
+        } catch (Exception e) {
+            log.error("获取装修需求详情失败: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("分页查询装修需求列表")
+    @ApiOperationSupport(order = 3)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "page", value = "页码", dataType = "Integer", paramType = "query", defaultValue = "1"),
+            @ApiImplicitParam(name = "size", value = "每页数量", dataType = "Integer", paramType = "query", defaultValue = "10"),
+            @ApiImplicitParam(name = "storeId", value = "门店ID", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "city", value = "城市", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "renovationType", value = "装修类型(1:新房装修, 2:旧房改造, 3:局部装修)", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "status", value = "状态(0:草稿, 1:已发布, 2:已下架)", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "auditStatus", value = "审核状态(0:待审核, 1:审核通过, 2:审核失败)", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "sortType", value = "排序类型(1:最新发布, 2:价格最高, 3:面积最大)", dataType = "Integer", paramType = "query")
+    })
+    @GetMapping("/getPage")
+    public R<IPage<StoreRenovationRequirementDto>> getPage(
+            @RequestParam(defaultValue = "1") Integer page,
+            @RequestParam(defaultValue = "10") Integer size,
+            @RequestParam(required = false) Integer storeId,
+            @RequestParam(required = false) String city,
+            @RequestParam(required = false) Integer renovationType,
+            @RequestParam(required = false) Integer status,
+            @RequestParam(required = false) Integer auditStatus,
+            @RequestParam(required = false, defaultValue = "1") Integer sortType) {
+        log.info("StoreRenovationRequirementController.getPage?page={}, size={}, storeId={}, city={}, renovationType={}, status={}, auditStatus={}, sortType={}",
+                page, size, storeId, city, renovationType, status, auditStatus, sortType);
+        try {
+            Page<StoreRenovationRequirementDto> pageParam = new Page<>(page, size);
+            IPage<StoreRenovationRequirementDto> result = requirementService.getRequirementPage(
+                    pageParam, storeId, city, renovationType, status, auditStatus, sortType);
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("分页查询装修需求失败: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("更新状态(草稿/发布/下架)")
+    @ApiOperationSupport(order = 4)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "需求ID", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "status", value = "状态(0:草稿, 1:已发布, 2:已下架)", dataType = "Integer", paramType = "query", required = true)
+    })
+    @PostMapping("/updateStatus")
+    public R<Boolean> updateStatus(@RequestParam Integer id, @RequestParam Integer status) {
+        log.info("StoreRenovationRequirementController.updateStatus?id={}, status={}", id, status);
+        try {
+            boolean result = requirementService.updateStatus(id, status);
+            if (result) {
+                return R.success("更新成功");
+            }
+            return R.fail("更新失败");
+        } catch (Exception e) {
+            log.error("更新状态失败: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("审核装修需求")
+    @ApiOperationSupport(order = 5)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "需求ID", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "auditStatus", value = "审核状态(1:审核通过, 2:审核失败)", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "auditReason", value = "审核原因(审核失败时必填)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "auditUserId", value = "审核人ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @PostMapping("/audit")
+    public R<Boolean> audit(@RequestParam Integer id,
+                            @RequestParam Integer auditStatus,
+                            @RequestParam(required = false) String auditReason,
+                            @RequestParam Integer auditUserId) {
+        log.info("StoreRenovationRequirementController.audit?id={}, auditStatus={}, auditReason={}, auditUserId={}",
+                id, auditStatus, auditReason, auditUserId);
+        try {
+            boolean result = requirementService.auditRequirement(id, auditStatus, auditReason, auditUserId);
+            if (result) {
+                return R.success("审核成功");
+            }
+            return R.fail("审核失败");
+        } catch (Exception e) {
+            log.error("审核装修需求失败: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("删除装修需求")
+    @ApiOperationSupport(order = 6)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "需求ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @PostMapping("/delete")
+    public R<Boolean> delete(@RequestParam Integer id) {
+        log.info("StoreRenovationRequirementController.delete?id={}", id);
+        try {
+            boolean result = requirementService.deleteRequirement(id);
+            if (result) {
+                return R.success("删除成功");
+            }
+            return R.fail("删除失败");
+        } catch (Exception e) {
+            log.error("删除装修需求失败: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("获取门店发布的装修需求列表")
+    @ApiOperationSupport(order = 7)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "门店ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/getListByStoreId")
+    public R<List<StoreRenovationRequirementDto>> getListByStoreId(@RequestParam Integer storeId) {
+        log.info("StoreRenovationRequirementController.getListByStoreId?storeId={}", storeId);
+        try {
+            List<StoreRenovationRequirementDto> list = requirementService.getRequirementListByStoreId(storeId);
+            return R.data(list);
+        } catch (Exception e) {
+            log.error("获取门店装修需求列表失败: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("增加咨询数(装修商家查看需求时调用)")
+    @ApiOperationSupport(order = 8)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "需求ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @PostMapping("/incrementInquiryCount")
+    public R<Boolean> incrementInquiryCount(@RequestParam Integer id) {
+        log.info("StoreRenovationRequirementController.incrementInquiryCount?id={}", id);
+        try {
+            requirementService.incrementInquiryCount(id);
+            return R.success("操作成功");
+        } catch (Exception e) {
+            log.error("增加咨询数失败: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+}
+

+ 105 - 0
alien-store/src/main/java/shop/alien/store/service/StoreRenovationRequirementService.java

@@ -0,0 +1,105 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.store.StoreRenovationRequirement;
+import shop.alien.entity.store.dto.StoreRenovationRequirementDto;
+
+import java.util.List;
+
+/**
+ * 装修需求动态表 服务类
+ *
+ * @author auto-generated
+ * @since 2025-01-15
+ */
+public interface StoreRenovationRequirementService extends IService<StoreRenovationRequirement> {
+
+    /**
+     * 发布装修需求(保存或更新)
+     *
+     * @param dto 装修需求DTO
+     * @return 是否成功
+     */
+    boolean saveOrUpdateRequirement(StoreRenovationRequirementDto dto);
+
+    /**
+     * 根据ID获取装修需求详情
+     *
+     * @param id 需求ID
+     * @return 装修需求DTO
+     */
+    StoreRenovationRequirementDto getRequirementDetail(Integer id);
+
+    /**
+     * 分页查询装修需求列表
+     *
+     * @param page 分页对象
+     * @param storeId 门店ID(可选)
+     * @param city 城市(可选)
+     * @param renovationType 装修类型(可选,1:新房装修, 2:旧房改造, 3:局部装修)
+     * @param status 状态(可选,0:草稿, 1:已发布, 2:已下架)
+     * @param auditStatus 审核状态(可选,0:待审核, 1:审核通过, 2:审核失败)
+     * @param sortType 排序类型(1:最新发布, 2:价格最高, 3:面积最大),默认为1
+     * @return 分页结果
+     */
+    IPage<StoreRenovationRequirementDto> getRequirementPage(Page<StoreRenovationRequirementDto> page,
+                                                             Integer storeId,
+                                                             String city,
+                                                             Integer renovationType,
+                                                             Integer status,
+                                                             Integer auditStatus,
+                                                             Integer sortType);
+
+    /**
+     * 更新浏览数
+     *
+     * @param id 需求ID
+     */
+    void incrementViewCount(Integer id);
+
+    /**
+     * 更新咨询数
+     *
+     * @param id 需求ID
+     */
+    void incrementInquiryCount(Integer id);
+
+    /**
+     * 更新状态
+     *
+     * @param id 需求ID
+     * @param status 状态(0:草稿, 1:已发布, 2:已下架)
+     * @return 是否成功
+     */
+    boolean updateStatus(Integer id, Integer status);
+
+    /**
+     * 审核装修需求
+     *
+     * @param id 需求ID
+     * @param auditStatus 审核状态(1:审核通过, 2:审核失败)
+     * @param auditReason 审核原因(审核失败时必填)
+     * @param auditUserId 审核人ID
+     * @return 是否成功
+     */
+    boolean auditRequirement(Integer id, Integer auditStatus, String auditReason, Integer auditUserId);
+
+    /**
+     * 删除装修需求(逻辑删除)
+     *
+     * @param id 需求ID
+     * @return 是否成功
+     */
+    boolean deleteRequirement(Integer id);
+
+    /**
+     * 获取门店发布的装修需求列表
+     *
+     * @param storeId 门店ID
+     * @return 装修需求列表
+     */
+    List<StoreRenovationRequirementDto> getRequirementListByStoreId(Integer storeId);
+}
+

+ 276 - 0
alien-store/src/main/java/shop/alien/store/service/impl/StoreRenovationRequirementServiceImpl.java

@@ -0,0 +1,276 @@
+package shop.alien.store.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.StringUtils;
+import shop.alien.entity.store.StoreRenovationRequirement;
+import shop.alien.entity.store.dto.StoreRenovationRequirementDto;
+import shop.alien.mapper.StoreRenovationRequirementMapper;
+import shop.alien.store.service.StoreRenovationRequirementService;
+import shop.alien.util.common.JwtUtil;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 装修需求动态表 服务实现类
+ *
+ * @author auto-generated
+ * @since 2025-01-15
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+@Transactional(rollbackFor = Exception.class)
+public class StoreRenovationRequirementServiceImpl extends ServiceImpl<StoreRenovationRequirementMapper, StoreRenovationRequirement> implements StoreRenovationRequirementService {
+
+    @Override
+    public boolean saveOrUpdateRequirement(StoreRenovationRequirementDto dto) {
+        log.info("StoreRenovationRequirementServiceImpl.saveOrUpdateRequirement?dto={}", dto);
+        try {
+            StoreRenovationRequirement requirement = new StoreRenovationRequirement();
+            BeanUtils.copyProperties(dto, requirement);
+
+            // 处理附件URL列表:List转逗号拼接字符串
+            if (dto.getAttachmentUrls() != null && !dto.getAttachmentUrls().isEmpty()) {
+                String attachmentUrlsStr = String.join(",", dto.getAttachmentUrls());
+                requirement.setAttachmentUrls(attachmentUrlsStr);
+            }
+
+            // 获取当前登录用户ID
+            Integer currentUserId = null;
+            try {
+                com.alibaba.fastjson.JSONObject userInfo = JwtUtil.getCurrentUserInfo();
+                if (userInfo != null) {
+                    currentUserId = userInfo.getInteger("userId");
+                }
+            } catch (Exception e) {
+                log.warn("获取当前用户ID失败: {}", e.getMessage());
+            }
+
+            if (requirement.getId() == null) {
+                // 新增
+                requirement.setCreatedUserId(currentUserId);
+                requirement.setCreatedTime(new Date());
+                if (requirement.getStatus() == null) {
+                    requirement.setStatus(0); // 默认草稿
+                }
+                if (requirement.getViewCount() == null) {
+                    requirement.setViewCount(0);
+                }
+                if (requirement.getInquiryCount() == null) {
+                    requirement.setInquiryCount(0);
+                }
+                if (requirement.getAuditStatus() == null) {
+                    requirement.setAuditStatus(0); // 默认待审核
+                }
+            } else {
+                // 更新
+                requirement.setUpdatedUserId(currentUserId);
+                requirement.setUpdatedTime(new Date());
+            }
+
+            return this.saveOrUpdate(requirement);
+        } catch (Exception e) {
+            log.error("保存装修需求失败: {}", e.getMessage(), e);
+            throw new RuntimeException("保存装修需求失败: " + e.getMessage());
+        }
+    }
+
+    @Override
+    public StoreRenovationRequirementDto getRequirementDetail(Integer id) {
+        log.info("StoreRenovationRequirementServiceImpl.getRequirementDetail?id={}", id);
+        StoreRenovationRequirement requirement = this.getById(id);
+        if (requirement == null) {
+            return null;
+        }
+
+        StoreRenovationRequirementDto dto = new StoreRenovationRequirementDto();
+        BeanUtils.copyProperties(requirement, dto);
+
+        // 处理附件URL字符串:逗号拼接转List
+        if (StringUtils.hasText(requirement.getAttachmentUrls())) {
+            List<String> attachmentUrls = Arrays.asList(requirement.getAttachmentUrls().split(","));
+            dto.setAttachmentUrls(attachmentUrls);
+        } else {
+            dto.setAttachmentUrls(new ArrayList<>());
+        }
+
+        return dto;
+    }
+
+    @Override
+    public IPage<StoreRenovationRequirementDto> getRequirementPage(Page<StoreRenovationRequirementDto> page,
+                                                                     Integer storeId,
+                                                                     String city,
+                                                                     Integer renovationType,
+                                                                     Integer status,
+                                                                     Integer auditStatus,
+                                                                     Integer sortType) {
+        log.info("StoreRenovationRequirementServiceImpl.getRequirementPage?storeId={}, city={}, renovationType={}, status={}, auditStatus={}, sortType={}",
+                storeId, city, renovationType, status, auditStatus, sortType);
+
+        LambdaQueryWrapper<StoreRenovationRequirement> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(StoreRenovationRequirement::getDeleteFlag, 0);
+
+        if (storeId != null) {
+            wrapper.eq(StoreRenovationRequirement::getStoreId, storeId);
+        }
+        if (StringUtils.hasText(city)) {
+            wrapper.eq(StoreRenovationRequirement::getCity, city);
+        }
+        if (renovationType != null) {
+            wrapper.eq(StoreRenovationRequirement::getRenovationType, renovationType);
+        }
+        if (status != null) {
+            wrapper.eq(StoreRenovationRequirement::getStatus, status);
+        }
+        if (auditStatus != null) {
+            wrapper.eq(StoreRenovationRequirement::getAuditStatus, auditStatus);
+        }
+
+        // 排序处理:1-最新发布, 2-价格最高, 3-面积最大
+        if (sortType == null || sortType == 1) {
+            // 默认:最新发布(按创建时间降序)
+            wrapper.orderByDesc(StoreRenovationRequirement::getCreatedTime);
+        } else if (sortType == 2) {
+            // 价格最高(按装修预算降序)
+            wrapper.orderByDesc(StoreRenovationRequirement::getRenovationBudget);
+            wrapper.orderByDesc(StoreRenovationRequirement::getCreatedTime); // 价格相同则按时间排序
+        } else if (sortType == 3) {
+            // 面积最大(按房屋面积降序)
+            wrapper.orderByDesc(StoreRenovationRequirement::getHouseArea);
+            wrapper.orderByDesc(StoreRenovationRequirement::getCreatedTime); // 面积相同则按时间排序
+        } else {
+            // 无效的排序类型,使用默认排序
+            wrapper.orderByDesc(StoreRenovationRequirement::getCreatedTime);
+        }
+
+        IPage<StoreRenovationRequirement> requirementPage = this.page(new Page<>(page.getCurrent(), page.getSize()), wrapper);
+
+        // 转换为DTO
+        IPage<StoreRenovationRequirementDto> dtoPage = requirementPage.convert(requirement -> {
+            StoreRenovationRequirementDto dto = new StoreRenovationRequirementDto();
+            BeanUtils.copyProperties(requirement, dto);
+
+            // 处理附件URL字符串:逗号拼接转List
+            if (StringUtils.hasText(requirement.getAttachmentUrls())) {
+                List<String> attachmentUrls = Arrays.asList(requirement.getAttachmentUrls().split(","));
+                dto.setAttachmentUrls(attachmentUrls);
+            } else {
+                dto.setAttachmentUrls(new ArrayList<>());
+            }
+
+            return dto;
+        });
+
+        return dtoPage;
+    }
+
+    @Override
+    public void incrementViewCount(Integer id) {
+        log.info("StoreRenovationRequirementServiceImpl.incrementViewCount?id={}", id);
+        LambdaUpdateWrapper<StoreRenovationRequirement> wrapper = new LambdaUpdateWrapper<>();
+        wrapper.eq(StoreRenovationRequirement::getId, id);
+        wrapper.setSql("view_count = view_count + 1");
+        this.update(wrapper);
+    }
+
+    @Override
+    public void incrementInquiryCount(Integer id) {
+        log.info("StoreRenovationRequirementServiceImpl.incrementInquiryCount?id={}", id);
+        LambdaUpdateWrapper<StoreRenovationRequirement> wrapper = new LambdaUpdateWrapper<>();
+        wrapper.eq(StoreRenovationRequirement::getId, id);
+        wrapper.setSql("inquiry_count = inquiry_count + 1");
+        this.update(wrapper);
+    }
+
+    @Override
+    public boolean updateStatus(Integer id, Integer status) {
+        log.info("StoreRenovationRequirementServiceImpl.updateStatus?id={}, status={}", id, status);
+        LambdaUpdateWrapper<StoreRenovationRequirement> wrapper = new LambdaUpdateWrapper<>();
+        wrapper.eq(StoreRenovationRequirement::getId, id);
+        wrapper.set(StoreRenovationRequirement::getStatus, status);
+        wrapper.set(StoreRenovationRequirement::getUpdatedTime, new Date());
+
+        try {
+            com.alibaba.fastjson.JSONObject userInfo = JwtUtil.getCurrentUserInfo();
+            if (userInfo != null) {
+                wrapper.set(StoreRenovationRequirement::getUpdatedUserId, userInfo.getInteger("userId"));
+            }
+        } catch (Exception e) {
+            log.warn("获取当前用户ID失败: {}", e.getMessage());
+        }
+
+        return this.update(wrapper);
+    }
+
+    @Override
+    public boolean auditRequirement(Integer id, Integer auditStatus, String auditReason, Integer auditUserId) {
+        log.info("StoreRenovationRequirementServiceImpl.auditRequirement?id={}, auditStatus={}, auditReason={}, auditUserId={}",
+                id, auditStatus, auditReason, auditUserId);
+
+        if (auditStatus == 2 && !StringUtils.hasText(auditReason)) {
+            throw new RuntimeException("审核失败时必须填写审核原因");
+        }
+
+        LambdaUpdateWrapper<StoreRenovationRequirement> wrapper = new LambdaUpdateWrapper<>();
+        wrapper.eq(StoreRenovationRequirement::getId, id);
+        wrapper.set(StoreRenovationRequirement::getAuditStatus, auditStatus);
+        wrapper.set(StoreRenovationRequirement::getAuditTime, new Date());
+        wrapper.set(StoreRenovationRequirement::getAuditUserId, auditUserId);
+        wrapper.set(StoreRenovationRequirement::getUpdatedTime, new Date());
+
+        if (auditStatus == 2) {
+            wrapper.set(StoreRenovationRequirement::getAuditReason, auditReason);
+        } else {
+            wrapper.set(StoreRenovationRequirement::getAuditReason, null);
+        }
+
+        return this.update(wrapper);
+    }
+
+    @Override
+    public boolean deleteRequirement(Integer id) {
+        log.info("StoreRenovationRequirementServiceImpl.deleteRequirement?id={}", id);
+        return this.removeById(id);
+    }
+
+    @Override
+    public List<StoreRenovationRequirementDto> getRequirementListByStoreId(Integer storeId) {
+        log.info("StoreRenovationRequirementServiceImpl.getRequirementListByStoreId?storeId={}", storeId);
+        LambdaQueryWrapper<StoreRenovationRequirement> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(StoreRenovationRequirement::getStoreId, storeId);
+        wrapper.eq(StoreRenovationRequirement::getDeleteFlag, 0);
+        wrapper.orderByDesc(StoreRenovationRequirement::getCreatedTime);
+
+        List<StoreRenovationRequirement> requirements = this.list(wrapper);
+
+        return requirements.stream().map(requirement -> {
+            StoreRenovationRequirementDto dto = new StoreRenovationRequirementDto();
+            BeanUtils.copyProperties(requirement, dto);
+
+            // 处理附件URL字符串:逗号拼接转List
+            if (StringUtils.hasText(requirement.getAttachmentUrls())) {
+                List<String> attachmentUrls = Arrays.asList(requirement.getAttachmentUrls().split(","));
+                dto.setAttachmentUrls(attachmentUrls);
+            } else {
+                dto.setAttachmentUrls(new ArrayList<>());
+            }
+
+            return dto;
+        }).collect(Collectors.toList());
+    }
+}
+