浏览代码

Merge remote-tracking branch 'origin/sit-shenzhen' into sit

liyafei 5 天之前
父节点
当前提交
19737a90b4
共有 38 个文件被更改,包括 2526 次插入5 次删除
  1. 74 0
      alien-entity/src/main/java/shop/alien/entity/store/LifeFeedback.java
  2. 49 0
      alien-entity/src/main/java/shop/alien/entity/store/LifeFeedbackReply.java
  3. 66 0
      alien-entity/src/main/java/shop/alien/entity/store/LifeImg.java
  4. 18 3
      alien-entity/src/main/java/shop/alien/entity/store/LifeLog.java
  5. 26 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/FeedbackReplyDto.java
  6. 26 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackAssignDto.java
  7. 39 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackDto.java
  8. 35 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackQueryDto.java
  9. 29 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackReplyWebDto.java
  10. 26 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackStatusDto.java
  11. 33 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/UserReplyDto.java
  12. 29 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/FeedbackAttachmentVo.java
  13. 41 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/FeedbackLogVo.java
  14. 45 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/FeedbackReplyVo.java
  15. 23 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/FeedbackTypeVo.java
  16. 78 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LifeFeedbackDetailVo.java
  17. 62 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LifeFeedbackListVo.java
  18. 66 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LifeFeedbackVo.java
  19. 26 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LifeStaffListVo.java
  20. 7 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityDTO.java
  21. 80 0
      alien-entity/src/main/java/shop/alien/mapper/LifeFeedbackMapper.java
  22. 23 0
      alien-entity/src/main/java/shop/alien/mapper/LifeFeedbackReplyMapper.java
  23. 51 0
      alien-entity/src/main/java/shop/alien/mapper/LifeImgMapper.java
  24. 24 0
      alien-entity/src/main/java/shop/alien/mapper/LifeLogMapper.java
  25. 207 0
      alien-entity/src/main/resources/mapper/LifeFeedbackMapper.xml
  26. 32 0
      alien-entity/src/main/resources/mapper/LifeFeedbackReplyMapper.xml
  27. 90 0
      alien-entity/src/main/resources/mapper/LifeImgMapper.xml
  28. 57 0
      alien-second/src/main/java/shop/alien/second/util/JsonUtils.java
  29. 2 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/controller/OperationalActivityController.java
  30. 35 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/feign/AlienAIFeign.java
  31. 68 2
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/OperationalActivityServiceImpl.java
  32. 117 0
      alien-store/src/main/java/shop/alien/store/controller/LifeFeedbackController.java
  33. 20 0
      alien-store/src/main/java/shop/alien/store/service/LifeFeedbackReplyService.java
  34. 78 0
      alien-store/src/main/java/shop/alien/store/service/LifeFeedbackService.java
  35. 47 0
      alien-store/src/main/java/shop/alien/store/service/LifeImgService.java
  36. 28 0
      alien-store/src/main/java/shop/alien/store/service/impl/LifeFeedbackReplyServiceImpl.java
  37. 713 0
      alien-store/src/main/java/shop/alien/store/service/impl/LifeFeedbackServiceImpl.java
  38. 56 0
      alien-store/src/main/java/shop/alien/store/service/impl/LifeImgServiceImpl.java

+ 74 - 0
alien-entity/src/main/java/shop/alien/entity/store/LifeFeedback.java

@@ -0,0 +1,74 @@
+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 java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 意见反馈表
+ */
+@Data
+@JsonInclude
+@TableName("life_feedback")
+@ApiModel(value = "LifeFeedback对象", description = "意见反馈")
+public class LifeFeedback implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "主键ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "用户ID")
+    @TableField("user_id")
+    private Integer userId;
+
+    @ApiModelProperty(value = "反馈来源:0-用户端,1-商家端")
+    @TableField("feedback_source")
+    private Integer feedbackSource;
+
+    @ApiModelProperty(value = "反馈方式:0-用户反馈,1-AI识别")
+    @TableField("feedback_way")
+    private Integer feedbackWay;
+
+    @ApiModelProperty(value = "反馈类型:0-bug反馈,1-优化反馈,2-新增功能反馈")
+    @TableField("feedback_type")
+    private Integer feedbackType;
+
+    @ApiModelProperty(value = "反馈内容")
+    @TableField("content")
+    private String content;
+
+    @ApiModelProperty(value = "联系方式(手机号或邮箱)")
+    @TableField("contact_way")
+    private String contactWay;
+
+    @ApiModelProperty(value = "反馈时间")
+    @TableField("feedback_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date feedbackTime;
+
+    @ApiModelProperty(value = "处理状态:0-处理中,1-已解决")
+    @TableField("handle_status")
+    private Integer handleStatus;
+
+    @ApiModelProperty(value = "跟进人员ID(关联life_sys表的id)")
+    @TableField("staff_id")
+    private Integer staffId;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "create_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createTime;
+
+    @ApiModelProperty(value = "修改时间")
+    @TableField(value = "update_time", fill = FieldFill.UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updateTime;
+}
+

+ 49 - 0
alien-entity/src/main/java/shop/alien/entity/store/LifeFeedbackReply.java

@@ -0,0 +1,49 @@
+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 java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 反馈回复表
+ */
+@Data
+@JsonInclude
+@TableName("life_feedback_reply")
+@ApiModel(value = "LifeFeedbackReply对象", description = "反馈回复")
+public class LifeFeedbackReply implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "回复ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "关联的反馈ID")
+    @TableField("feedback_id")
+    private Integer feedbackId;
+
+    @ApiModelProperty(value = "回复类型:0-平台回复,1-用户回复")
+    @TableField("reply_type")
+    private Integer replyType;
+
+    @ApiModelProperty(value = "回复内容")
+    @TableField("reply_content")
+    private String replyContent;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "create_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createTime;
+
+    @ApiModelProperty(value = "更新时间")
+    @TableField(value = "update_time", fill = FieldFill.UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updateTime;
+}
+

+ 66 - 0
alien-entity/src/main/java/shop/alien/entity/store/LifeImg.java

@@ -0,0 +1,66 @@
+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 java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 反馈附件表(图片和视频)
+ */
+@Data
+@JsonInclude
+@TableName("life_img")
+@ApiModel(value = "LifeImg对象", description = "反馈附件(图片和视频)")
+public class LifeImg implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "主键ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "反馈ID(关联life_feedback)")
+    @TableField("feedback_id")
+    private Integer feedbackId;
+
+    @ApiModelProperty(value = "文件URL(图片或视频)")
+    @TableField("img_url")
+    private String imgUrl;
+
+    @ApiModelProperty(value = "缩略图URL(视频的封面图)")
+    @TableField("thumbnail_url")
+    private String thumbnailUrl;
+
+    @ApiModelProperty(value = "文件类型:1-图片,2-视频")
+    @TableField("file_type")
+    private Integer fileType;
+
+    @ApiModelProperty(value = "上传时间")
+    @TableField("upload_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date uploadTime;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "create_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createTime;
+
+    @ApiModelProperty(value = "修改时间")
+    @TableField(value = "update_time", fill = FieldFill.UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updateTime;
+
+    @ApiModelProperty(value = "创建人ID")
+    @TableField("created_user_id")
+    private Integer createdUserId;
+
+    @ApiModelProperty(value = "修改人ID")
+    @TableField("updated_user_id")
+    private Integer updatedUserId;
+}
+

+ 18 - 3
alien-entity/src/main/java/shop/alien/entity/store/LifeLog.java

@@ -3,24 +3,39 @@ 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 java.io.Serializable;
 import java.util.Date;
 
 /**
- * 日志
+ * 意见反馈日志
  */
 @Data
 @JsonInclude
 @TableName("life_log")
-public class LifeLog {
+@ApiModel(value = "LifeLog对象", description = "意见反馈日志")
+public class LifeLog implements Serializable {
+    private static final long serialVersionUID = 1L;
 
+    @ApiModelProperty(value = "主键ID")
     @TableId(value = "id", type = IdType.AUTO)
-    private String id;
+    private Integer id;
 
+    @ApiModelProperty(value = "意见反馈主表ID")
+    @TableField("feedback_id")
+    private Integer feedbackId;
+
+    @ApiModelProperty(value = "日志内容")
+    @TableField("context")
     private String context;
 
+    @ApiModelProperty(value = "操作类型:0-创建反馈工单,1-分配跟踪人员,2-回复用户,3-问题解决状态")
+    @TableField("type")
+    private String type;
+
     @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
     @TableField("delete_flag")
     @TableLogic

+ 26 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/FeedbackReplyDto.java

@@ -0,0 +1,26 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 平台回复反馈DTO
+ */
+@Data
+@ApiModel(value = "FeedbackReplyDto对象", description = "平台回复反馈DTO")
+public class FeedbackReplyDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "原始反馈ID")
+    private Integer feedbackId;
+
+    @ApiModelProperty(value = "平台工作人员ID")
+    private Integer staffId;
+
+    @ApiModelProperty(value = "回复内容")
+    private String content;
+}
+

+ 26 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackAssignDto.java

@@ -0,0 +1,26 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 中台分配跟踪人员DTO
+ */
+@Data
+@ApiModel(value = "LifeFeedbackAssignDto对象", description = "中台分配跟踪人员DTO")
+public class LifeFeedbackAssignDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "反馈ID", required = true)
+    private Integer feedbackId;
+
+    @ApiModelProperty(value = "跟踪人员ID(关联life_sys表的id)", required = true)
+    private Integer staffId;
+
+    @ApiModelProperty(value = "操作人员ID")
+    private Integer operatorId;
+}
+

+ 39 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackDto.java

@@ -0,0 +1,39 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 意见反馈提交DTO
+ */
+@Data
+@ApiModel(value = "LifeFeedbackDto对象", description = "意见反馈提交DTO")
+public class LifeFeedbackDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "用户ID")
+    private Integer userId;
+
+    @ApiModelProperty(value = "反馈来源:0-用户端,1-商家端")
+    private Integer feedbackSource;
+
+    @ApiModelProperty(value = "反馈方式:0-用户反馈,1-AI识别")
+    private Integer feedbackWay;
+
+    @ApiModelProperty(value = "反馈类型:0-bug反馈,1-优化反馈,2-新增功能反馈")
+    private Integer feedbackType;
+
+    @ApiModelProperty(value = "反馈内容")
+    private String content;
+
+    @ApiModelProperty(value = "联系方式(手机号或邮箱)")
+    private String contactWay;
+
+    @ApiModelProperty(value = "文件URL列表(图片和视频,系统会自动识别类型。视频会自动匹配封面图)")
+    private List<String> fileUrlList;
+}
+

+ 35 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackQueryDto.java

@@ -0,0 +1,35 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 中台意见反馈查询DTO
+ */
+@Data
+@ApiModel(value = "LifeFeedbackQueryDto对象", description = "中台意见反馈查询DTO")
+public class LifeFeedbackQueryDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "反馈类型:0-bug反馈,1-优化反馈,2-新增功能反馈")
+    private Integer feedbackType;
+
+    @ApiModelProperty(value = "处理状态:0-处理中,1-已解决,2-未分配,3-无需解决")
+    private Integer handleStatus;
+
+    @ApiModelProperty(value = "反馈来源:0-用户端,1-商家端")
+    private Integer feedbackSource;
+
+    @ApiModelProperty(value = "反馈方式:0-用户反馈,1-AI识别")
+    private Integer feedbackWay;
+
+    @ApiModelProperty(value = "页码", required = true)
+    private Integer page = 1;
+
+    @ApiModelProperty(value = "每页数量", required = true)
+    private Integer size = 10;
+}
+

+ 29 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackReplyWebDto.java

@@ -0,0 +1,29 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 中台回复用户DTO
+ */
+@Data
+@ApiModel(value = "LifeFeedbackReplyWebDto对象", description = "中台回复用户DTO")
+public class LifeFeedbackReplyWebDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "反馈ID", required = true)
+    private Integer feedbackId;
+
+    @ApiModelProperty(value = "回复内容", required = true)
+    private String content;
+
+    @ApiModelProperty(value = "操作人员ID")
+    private Integer operatorId;
+
+    @ApiModelProperty(value = "用户回复内容")
+    private String userReply;
+}
+

+ 26 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackStatusDto.java

@@ -0,0 +1,26 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 中台更新反馈处理状态DTO
+ */
+@Data
+@ApiModel(value = "LifeFeedbackStatusDto对象", description = "中台更新反馈处理状态DTO")
+public class LifeFeedbackStatusDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "反馈ID", required = true)
+    private Integer feedbackId;
+
+    @ApiModelProperty(value = "处理状态:0-处理中,1-已解决", required = true)
+    private Integer handleStatus;
+
+    // @ApiModelProperty(value = "操作人员ID")
+    // private Integer operatorId;
+}
+

+ 33 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/UserReplyDto.java

@@ -0,0 +1,33 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 用户回复DTO
+ */
+@Data
+@ApiModel(value = "UserReplyDto对象", description = "用户回复DTO")
+public class UserReplyDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "用户ID", required = true)
+    private Integer userId;
+
+    @ApiModelProperty(value = "反馈来源:0-用户端,1-商家端", required = true)
+    private Integer feedbackSource;
+
+    @ApiModelProperty(value = "原始反馈ID(反馈详情页的反馈ID)", required = true)
+    private Integer feedbackId;
+
+    @ApiModelProperty(value = "回复内容", required = true)
+    private String content;
+
+    @ApiModelProperty(value = "文件URL列表(图片和视频,系统会自动识别类型。视频会自动匹配封面图)")
+    private List<String> fileUrlList;
+}
+

+ 29 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/FeedbackAttachmentVo.java

@@ -0,0 +1,29 @@
+package shop.alien.entity.store.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 反馈附件VO
+ */
+@Data
+@ApiModel(value = "FeedbackAttachmentVo对象", description = "反馈附件VO")
+public class FeedbackAttachmentVo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "附件ID")
+    private Integer id;
+
+    @ApiModelProperty(value = "文件类型:1-图片,2-视频")
+    private Integer fileType;
+
+    @ApiModelProperty(value = "文件URL")
+    private String fileUrl;
+
+    @ApiModelProperty(value = "缩略图URL(视频的封面图)")
+    private String thumbnailUrl;
+}
+

+ 41 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/FeedbackLogVo.java

@@ -0,0 +1,41 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 反馈操作日志VO
+ */
+@Data
+@ApiModel(value = "FeedbackLogVo对象", description = "反馈操作日志VO")
+public class FeedbackLogVo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "日志ID")
+    private Integer id;
+
+    @ApiModelProperty(value = "操作类型:0-问题解决状态,1-分配跟踪人员,3-回复用户")
+    private Integer type;
+
+    @ApiModelProperty(value = "操作类型名称")
+    private String typeName;
+
+    @ApiModelProperty(value = "日志内容")
+    private String context;
+
+    @ApiModelProperty(value = "子内容(用于回复用户时显示用户回复)")
+    private String subContext;
+
+    @ApiModelProperty(value = "操作时间")
+    @JsonFormat(pattern = "yyyy/MM/dd HH:mm", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "操作人姓名")
+    private String operatorName;
+}
+

+ 45 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/FeedbackReplyVo.java

@@ -0,0 +1,45 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 反馈回复VO
+ */
+@Data
+@ApiModel(value = "FeedbackReplyVo对象", description = "反馈回复VO")
+public class FeedbackReplyVo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "回复ID")
+    private Integer id;
+
+    @ApiModelProperty(value = "反馈ID")
+    private Integer feedbackId;
+
+    @ApiModelProperty(value = "回复类型:0-平台回复,1-用户回复")
+    private Integer replyType;
+
+    @ApiModelProperty(value = "回复类型名称")
+    private String replyTypeName;
+
+    @ApiModelProperty(value = "回复内容")
+    private String replyContent;
+
+    @ApiModelProperty(value = "创建时间")
+    @JsonFormat(pattern = "yyyy/MM/dd HH:mm", timezone = "GMT+8")
+    private Date createTime;
+
+    @ApiModelProperty(value = "图片列表")
+    private List<String> imgUrlList;
+
+    @ApiModelProperty(value = "视频列表")
+    private List<String> videoUrlList;
+}
+

+ 23 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/FeedbackTypeVo.java

@@ -0,0 +1,23 @@
+package shop.alien.entity.store.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 反馈类型VO
+ */
+@Data
+@ApiModel(value = "FeedbackTypeVo对象", description = "反馈类型VO")
+public class FeedbackTypeVo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "反馈类型值:0-bug反馈,1-优化反馈,2-新增功能反馈")
+    private Integer value;
+
+    @ApiModelProperty(value = "反馈类型名称")
+    private String label;
+}
+

+ 78 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/LifeFeedbackDetailVo.java

@@ -0,0 +1,78 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 中台意见反馈详情VO
+ */
+@Data
+@ApiModel(value = "LifeFeedbackDetailVo对象", description = "中台意见反馈详情VO")
+public class LifeFeedbackDetailVo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "反馈ID")
+    private Integer id;
+
+    @ApiModelProperty(value = "用户昵称")
+    private String nickName;
+
+    @ApiModelProperty(value = "账号(手机号)")
+    private String phone;
+
+    @ApiModelProperty(value = "跟踪人员姓名+联系方式")
+    private String staffInfo;
+
+    @ApiModelProperty(value = "跟踪人员ID")
+    private Integer staffId;
+
+    @ApiModelProperty(value = "反馈来源:0-用户端,1-商家端")
+    private Integer feedbackSource;
+
+    @ApiModelProperty(value = "反馈来源名称")
+    private String feedbackSourceName;
+
+    @ApiModelProperty(value = "反馈方式:0-用户反馈,1-AI识别")
+    private Integer feedbackWay;
+
+    @ApiModelProperty(value = "反馈方式名称")
+    private String feedbackWayName;
+
+    @ApiModelProperty(value = "反馈类型:0-bug反馈,1-优化反馈,2-新增功能反馈")
+    private Integer feedbackType;
+
+    @ApiModelProperty(value = "反馈类型名称")
+    private String feedbackTypeName;
+
+    @ApiModelProperty(value = "反馈时间")
+    @JsonFormat(pattern = "yyyy/MM/dd HH:mm", timezone = "GMT+8")
+    private Date feedbackTime;
+
+    @ApiModelProperty(value = "问题描述")
+    private String content;
+
+    @ApiModelProperty(value = "联系方式")
+    private String contactWay;
+
+    @ApiModelProperty(value = "处理状态:0-处理中,1-已解决,2-未分配,3-无需解决")
+    private Integer handleStatus;
+
+    @ApiModelProperty(value = "处理状态名称")
+    private String handleStatusName;
+
+    @ApiModelProperty(value = "附件列表")
+    private List<FeedbackAttachmentVo> attachments;
+
+    @ApiModelProperty(value = "操作日志列表")
+    private List<FeedbackLogVo> logs;
+
+    @ApiModelProperty(value = "回复列表(平台回复和用户回复)")
+    private List<FeedbackReplyVo> replies;
+}
+

+ 62 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/LifeFeedbackListVo.java

@@ -0,0 +1,62 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 中台意见反馈列表VO
+ */
+@Data
+@ApiModel(value = "LifeFeedbackListVo对象", description = "中台意见反馈列表VO")
+public class LifeFeedbackListVo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "反馈ID")
+    private Integer id;
+
+    @ApiModelProperty(value = "用户昵称")
+    private String nickName;
+
+    @ApiModelProperty(value = "账号(手机号)")
+    private String phone;
+
+    @ApiModelProperty(value = "反馈类型:0-bug反馈,1-优化反馈,2-新增功能反馈")
+    private Integer feedbackType;
+
+    @ApiModelProperty(value = "反馈类型名称")
+    private String feedbackTypeName;
+
+    @ApiModelProperty(value = "反馈方式:0-用户反馈,1-AI识别")
+    private Integer feedbackWay;
+
+    @ApiModelProperty(value = "反馈方式名称")
+    private String feedbackWayName;
+
+    @ApiModelProperty(value = "反馈来源:0-用户端,1-商家端")
+    private Integer feedbackSource;
+
+    @ApiModelProperty(value = "反馈来源名称")
+    private String feedbackSourceName;
+
+    @ApiModelProperty(value = "反馈时间")
+    @JsonFormat(pattern = "yyyy/MM/dd HH:mm", timezone = "GMT+8")
+    private Date feedbackTime;
+
+    @ApiModelProperty(value = "跟踪人员姓名+部门")
+    private String staffInfo;
+
+    @ApiModelProperty(value = "跟踪人员ID")
+    private Integer staffId;
+
+    @ApiModelProperty(value = "处理状态:0-处理中,1-已解决,2-未分配,3-无需解决")
+    private Integer handleStatus;
+
+    @ApiModelProperty(value = "处理状态名称")
+    private String handleStatusName;
+}
+

+ 66 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/LifeFeedbackVo.java

@@ -0,0 +1,66 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 意见反馈展示VO
+ */
+@Data
+@ApiModel(value = "LifeFeedbackVo对象", description = "意见反馈展示VO")
+public class LifeFeedbackVo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "反馈ID")
+    private Integer id;
+
+    @ApiModelProperty(value = "用户ID")
+    private Integer userId;
+
+    @ApiModelProperty(value = "反馈来源:0-用户端,1-商家端")
+    private Integer feedbackSource;
+
+    @ApiModelProperty(value = "反馈方式:0-用户反馈,1-AI识别")
+    private Integer feedbackWay;
+
+    @ApiModelProperty(value = "反馈类型:0-bug反馈,1-优化反馈,2-新增功能反馈")
+    private Integer feedbackType;
+
+    @ApiModelProperty(value = "反馈类型名称")
+    private String feedbackTypeName;
+
+    @ApiModelProperty(value = "反馈内容")
+    private String content;
+
+    @ApiModelProperty(value = "联系方式")
+    private String contactWay;
+
+    @ApiModelProperty(value = "反馈时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date feedbackTime;
+
+    @ApiModelProperty(value = "处理状态:0-处理中,1-已解决")
+    private Integer handleStatus;
+
+    @ApiModelProperty(value = "跟进工作人员ID")
+    private Integer staffId;
+
+    @ApiModelProperty(value = "跟进工作人员姓名")
+    private String staffName;
+
+    @ApiModelProperty(value = "附件图片列表")
+    private List<String> imgUrlList;
+
+    @ApiModelProperty(value = "附件视频列表")
+    private List<String> videoUrlList;
+
+    @ApiModelProperty(value = "平台反馈建议列表")
+    private List<LifeFeedbackVo> platformReplies;
+}
+

+ 26 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/LifeStaffListVo.java

@@ -0,0 +1,26 @@
+package shop.alien.entity.store.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 中台跟踪人员列表VO
+ */
+@Data
+@ApiModel(value = "LifeStaffListVo对象", description = "中台跟踪人员列表VO")
+public class LifeStaffListVo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "人员ID")
+    private Integer id;
+
+    @ApiModelProperty(value = "人员信息(姓名)")
+    private String staffInfo;
+
+    @ApiModelProperty(value = "姓名")
+    private String realName;
+}
+

+ 7 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityDTO.java

@@ -2,6 +2,7 @@ package shop.alien.entity.storePlatform.vo;
 
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.databind.JsonNode;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
@@ -73,5 +74,11 @@ public class StoreOperationalActivityDTO {
     @ApiModelProperty(value = "页数")
     private Integer pageSize;
 
+    @ApiModelProperty(value = "上传图片的方式: 1-用户本地上传,2-使用用户输入的描述AI生成图片")
+    private Integer uploadImgType;
+
+    @ApiModelProperty(value = "用户输入的AI描述")
+    private JsonNode imgDescribe;
+
 }
 

+ 80 - 0
alien-entity/src/main/java/shop/alien/mapper/LifeFeedbackMapper.java

@@ -0,0 +1,80 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import shop.alien.entity.store.LifeFeedback;
+import shop.alien.entity.store.vo.LifeFeedbackDetailVo;
+import shop.alien.entity.store.vo.LifeFeedbackListVo;
+import shop.alien.entity.store.vo.LifeFeedbackVo;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 意见反馈 Mapper 接口
+ */
+@Mapper
+public interface LifeFeedbackMapper extends BaseMapper<LifeFeedback> {
+
+    /**
+     * 查询用户反馈列表(带工作人员名称)
+     * @param page 分页对象
+     * @param userId 用户ID
+     * @param feedbackSource 反馈来源
+     * @param feedbackWay 反馈方式
+     * @param handleStatus 处理状态
+     * @return 反馈列表
+     */
+    IPage<LifeFeedbackVo> selectFeedbackListWithStaff(
+            Page<LifeFeedbackVo> page,
+            @Param("userId") Integer userId,
+            @Param("feedbackSource") Integer feedbackSource,
+            @Param("feedbackWay") Integer feedbackWay,
+            @Param("handleStatus") Integer handleStatus
+    );
+
+    /**
+     * 查询反馈详情(带工作人员名称)
+     * @param feedbackId 反馈ID
+     * @return 反馈详情
+     */
+    LifeFeedbackVo selectFeedbackDetail(@Param("feedbackId") Integer feedbackId);
+
+    /**
+     * 统计待处理反馈数量
+     * @param feedbackSource 反馈来源
+     * @return 待处理数量
+     */
+    Integer countPendingFeedback(@Param("feedbackSource") Integer feedbackSource);
+
+
+    // ==================== Web中台接口 ====================
+
+    /**
+     * 中台-查询意见反馈列表
+     * @param page 分页对象
+     * @param feedbackType 反馈类型
+     * @param handleStatus 处理状态
+     * @param feedbackSource 反馈来源
+     * @param feedbackWay 反馈方式
+     * @return 反馈列表
+     */
+    IPage<LifeFeedbackListVo> selectWebFeedbackList(
+            Page<LifeFeedbackListVo> page,
+            @Param("feedbackType") Integer feedbackType,
+            @Param("handleStatus") Integer handleStatus,
+            @Param("feedbackSource") Integer feedbackSource,
+            @Param("feedbackWay") Integer feedbackWay
+    );
+
+    /**
+     * 中台-查询反馈详情
+     * @param feedbackId 反馈ID
+     * @return 反馈详情
+     */
+    LifeFeedbackDetailVo selectWebFeedbackDetail(@Param("feedbackId") Integer feedbackId);
+}
+

+ 23 - 0
alien-entity/src/main/java/shop/alien/mapper/LifeFeedbackReplyMapper.java

@@ -0,0 +1,23 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import shop.alien.entity.store.LifeFeedbackReply;
+
+import java.util.List;
+
+/**
+ * 反馈回复 Mapper 接口
+ */
+@Mapper
+public interface LifeFeedbackReplyMapper extends BaseMapper<LifeFeedbackReply> {
+
+    /**
+     * 根据反馈ID查询回复列表
+     * @param feedbackId 反馈ID
+     * @return 回复列表
+     */
+    List<LifeFeedbackReply> selectByFeedbackId(@Param("feedbackId") Integer feedbackId);
+}
+

+ 51 - 0
alien-entity/src/main/java/shop/alien/mapper/LifeImgMapper.java

@@ -0,0 +1,51 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import shop.alien.entity.store.LifeImg;
+
+import java.util.List;
+
+/**
+ * 反馈图片 Mapper 接口
+ */
+@Mapper
+public interface LifeImgMapper extends BaseMapper<LifeImg> {
+
+    /**
+     * 根据反馈ID查询图片列表
+     * @param feedbackId 反馈ID
+     * @return 图片列表
+     */
+    List<LifeImg> selectByFeedbackId(@Param("feedbackId") Integer feedbackId);
+
+    /**
+     * 批量插入图片
+     * @param list 图片列表
+     * @return 插入数量
+     */
+    Integer batchInsert(@Param("list") List<LifeImg> list);
+
+    /**
+     * 根据反馈ID删除图片(逻辑删除)
+     * @param feedbackId 反馈ID
+     * @return 删除数量
+     */
+    Integer deleteByFeedbackId(@Param("feedbackId") Integer feedbackId);
+
+    /**
+     * 查询反馈的图片URL列表
+     * @param feedbackId 反馈ID
+     * @return 图片URL列表
+     */
+    List<String> selectImgUrlsByFeedbackId(@Param("feedbackId") Integer feedbackId);
+
+    /**
+     * 查询反馈的视频URL列表
+     * @param feedbackId 反馈ID
+     * @return 视频URL列表
+     */
+    List<String> selectVideoUrlsByFeedbackId(@Param("feedbackId") Integer feedbackId);
+}
+

+ 24 - 0
alien-entity/src/main/java/shop/alien/mapper/LifeLogMapper.java

@@ -2,11 +2,35 @@ package shop.alien.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
 import shop.alien.entity.store.LifeLog;
+import shop.alien.entity.store.vo.FeedbackLogVo;
+
+import java.util.List;
 
 /**
  * 日志
  */
 @Mapper
 public interface LifeLogMapper extends BaseMapper<LifeLog> {
+
+    /**
+     * 根据反馈ID查询操作日志列表
+     * @param feedbackId 反馈ID
+     * @return 日志列表
+     */
+    @Select("SELECT l.id, l.type, l.context, l.created_time AS createdTime, " +
+            "CASE l.type " +
+            "   WHEN 0 THEN '问题解决状态' " +
+            "   WHEN 1 THEN '分配跟踪人员' " +
+            "   WHEN 2 THEN '创建反馈工单' " +
+            "   WHEN 3 THEN '回复用户' " +
+            "END AS typeName, " +
+            "s.user_name AS operatorName " +
+            "FROM life_log l " +
+            "LEFT JOIN life_sys s ON l.created_user_id = s.id " +
+            "WHERE l.feedback_id = #{feedbackId} AND l.delete_flag = 0 " +
+            "ORDER BY l.created_time DESC")
+    List<FeedbackLogVo> selectLogsByFeedbackId(@Param("feedbackId") Integer feedbackId);
 }

+ 207 - 0
alien-entity/src/main/resources/mapper/LifeFeedbackMapper.xml

@@ -0,0 +1,207 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="shop.alien.mapper.LifeFeedbackMapper">
+
+    <!-- 通用结果映射 -->
+    <resultMap id="BaseResultMap" type="shop.alien.entity.store.LifeFeedback">
+        <id column="id" property="id" />
+        <result column="user_id" property="userId" />
+        <result column="feedback_source" property="feedbackSource" />
+        <result column="feedback_way" property="feedbackWay" />
+        <result column="feedback_type" property="feedbackType" />
+        <result column="content" property="content" />
+        <result column="contact_way" property="contactWay" />
+        <result column="feedback_time" property="feedbackTime" />
+        <result column="staff_id" property="staffId" />
+        <result column="handle_status" property="handleStatus" />
+        <result column="create_time" property="createTime" />
+        <result column="update_time" property="updateTime" />
+    </resultMap>
+
+    <!-- 基础字段 -->
+    <sql id="Base_Column_List">
+        id, user_id, feedback_source, feedback_way, feedback_type, 
+        content, contact_way, feedback_time, staff_id, handle_status,
+        create_time, update_time
+    </sql>
+
+    <!-- 查询用户反馈列表(带工作人员名称) -->
+    <!-- 只查询原始反馈,排除用户回复(用户回复的feedback_time晚于原始反馈) -->
+    <select id="selectFeedbackListWithStaff" resultType="shop.alien.entity.store.vo.LifeFeedbackVo">
+        SELECT 
+            f.id,
+            f.user_id AS userId,
+            f.feedback_source AS feedbackSource,
+            f.feedback_way AS feedbackWay,
+            f.feedback_type AS feedbackType,
+            f.content,
+            f.contact_way AS contactWay,
+            f.feedback_time AS feedbackTime,
+            f.staff_id AS staffId,
+            f.handle_status AS handleStatus,
+            s.user_name AS staffName
+        FROM life_feedback f
+        LEFT JOIN life_sys s ON f.staff_id = s.id
+        WHERE 1=1
+        <if test="userId != null">
+            AND f.user_id = #{userId}
+        </if>
+        <if test="feedbackSource != null">
+            AND f.feedback_source = #{feedbackSource}
+        </if>
+        <if test="feedbackWay != null">
+            AND f.feedback_way = #{feedbackWay}
+        </if>
+        <if test="handleStatus != null">
+            AND f.handle_status = #{handleStatus}
+        </if>
+        <!-- 只查询原始反馈:feedback_way=0(用户反馈)或feedback_way=1(AI识别) -->
+        <!-- 回复记录已存储在life_feedback_reply表中,life_feedback表中只包含原始反馈 -->
+        AND (f.feedback_way = 0 OR f.feedback_way = 1)
+        ORDER BY f.feedback_time DESC
+    </select>
+
+    <!-- 查询反馈详情(带工作人员名称) -->
+    <select id="selectFeedbackDetail" resultType="shop.alien.entity.store.vo.LifeFeedbackVo">
+        SELECT 
+            f.id,
+            f.user_id AS userId,
+            f.feedback_source AS feedbackSource,
+            f.feedback_way AS feedbackWay,
+            f.feedback_type AS feedbackType,
+            f.content,
+            f.contact_way AS contactWay,
+            f.feedback_time AS feedbackTime,
+            f.staff_id AS staffId,
+            f.handle_status AS handleStatus,
+            s.user_name AS staffName
+        FROM life_feedback f
+        LEFT JOIN life_sys s ON f.staff_id = s.id
+        WHERE f.id = #{feedbackId}
+    </select>
+
+    <!-- 统计处理中反馈数量 -->
+    <select id="countPendingFeedback" resultType="java.lang.Integer">
+        SELECT COUNT(1)
+        FROM life_feedback
+        WHERE handle_status = 0
+        <if test="feedbackSource != null">
+            AND feedback_source = #{feedbackSource}
+        </if>
+    </select>
+
+    <!-- 查询平台回复列表 -->
+    <select id="selectPlatformReplies" resultType="shop.alien.entity.store.vo.LifeFeedbackVo">
+        SELECT
+            f.id,
+            f.user_id AS userId,
+            f.feedback_source AS feedbackSource,
+            f.feedback_way AS feedbackWay,
+            f.feedback_type AS feedbackType,
+            f.content,
+            f.feedback_time AS feedbackTime,
+            f.staff_id AS staffId,
+            f.handle_status AS handleStatus,
+            s.user_name AS staffName
+        FROM life_feedback f
+        LEFT JOIN life_sys s ON f.staff_id = s.id
+        WHERE f.user_id = #{userId}
+        AND f.feedback_source = #{feedbackSource}
+        AND f.feedback_way = 1
+        AND f.feedback_time >= #{startTime}
+        ORDER BY f.feedback_time ASC
+    </select>
+
+    <!-- ==================== Web中台接口 ==================== -->
+
+    <!-- 中台-查询意见反馈列表 -->
+    <select id="selectWebFeedbackList" resultType="shop.alien.entity.store.vo.LifeFeedbackListVo">
+        SELECT
+            f.id,
+            u.user_name AS nickName,
+            u.user_phone AS phone,
+            f.feedback_type AS feedbackType,
+            CASE f.feedback_type
+                WHEN 0 THEN 'bug反馈'
+                WHEN 1 THEN '优化反馈'
+                WHEN 2 THEN '新增功能反馈'
+            END AS feedbackTypeName,
+            f.feedback_way AS feedbackWay,
+            CASE f.feedback_way
+                WHEN 0 THEN '用户反馈'
+                WHEN 1 THEN 'AI识别'
+            END AS feedbackWayName,
+            f.feedback_source AS feedbackSource,
+            CASE f.feedback_source
+                WHEN 0 THEN '用户端'
+                WHEN 1 THEN '商家端'
+            END AS feedbackSourceName,
+            f.feedback_time AS feedbackTime,
+            f.staff_id AS staffId,
+            CONCAT(IFNULL(s.user_name, '')) AS staffInfo,
+            f.handle_status AS handleStatus,
+            CASE f.handle_status
+                WHEN 0 THEN '处理中'
+                WHEN 1 THEN '已解决'
+            END AS handleStatusName
+        FROM life_feedback f
+        LEFT JOIN store_user u ON f.user_id = u.id
+        LEFT JOIN life_sys s ON f.staff_id = s.id
+        WHERE 1=1
+        <if test="feedbackType != null">
+            AND f.feedback_type = #{feedbackType}
+        </if>
+        <if test="handleStatus != null">
+            AND f.handle_status = #{handleStatus}
+        </if>
+        <if test="feedbackSource != null">
+            AND f.feedback_source = #{feedbackSource}
+        </if>
+        <if test="feedbackWay != null">
+            AND f.feedback_way = #{feedbackWay}
+        </if>
+        ORDER BY f.feedback_time DESC
+    </select>
+
+    <!-- 中台-查询反馈详情 -->
+    <select id="selectWebFeedbackDetail" resultType="shop.alien.entity.store.vo.LifeFeedbackDetailVo">
+        SELECT
+            f.id,
+            u.user_name AS nickName,
+            u.user_phone AS phone,
+            f.staff_id AS staffId,
+            CONCAT(IFNULL(s.user_name, '')) AS staffInfo,
+            f.feedback_source AS feedbackSource,
+            CASE f.feedback_source
+                WHEN 0 THEN '用户端'
+                WHEN 1 THEN '商家端'
+            END AS feedbackSourceName,
+            f.feedback_way AS feedbackWay,
+            CASE f.feedback_way
+                WHEN 0 THEN '用户反馈'
+                WHEN 1 THEN 'AI识别'
+            END AS feedbackWayName,
+            f.feedback_type AS feedbackType,
+            CASE f.feedback_type
+                WHEN 0 THEN 'bug反馈'
+                WHEN 1 THEN '优化反馈'
+                WHEN 2 THEN '新增功能反馈'
+            END AS feedbackTypeName,
+            f.feedback_time AS feedbackTime,
+            f.content,
+            f.contact_way AS contactWay,
+            f.handle_status AS handleStatus,
+            CASE f.handle_status
+                WHEN 0 THEN '处理中'
+                WHEN 1 THEN '已解决'
+            END AS handleStatusName
+        FROM life_feedback f
+        LEFT JOIN store_user u ON f.user_id = u.id
+        LEFT JOIN life_sys s ON f.staff_id = s.id
+        WHERE f.id = #{feedbackId}
+    </select>
+
+</mapper>
+

+ 32 - 0
alien-entity/src/main/resources/mapper/LifeFeedbackReplyMapper.xml

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="shop.alien.mapper.LifeFeedbackReplyMapper">
+
+    <!-- 通用结果映射 -->
+    <resultMap id="BaseResultMap" type="shop.alien.entity.store.LifeFeedbackReply">
+        <id column="id" property="id" />
+        <result column="feedback_id" property="feedbackId" />
+        <result column="reply_type" property="replyType" />
+        <result column="reply_content" property="replyContent" />
+        <result column="create_time" property="createTime" />
+        <result column="update_time" property="updateTime" />
+    </resultMap>
+
+    <!-- 基础字段 -->
+    <sql id="Base_Column_List">
+        id, feedback_id, reply_type, reply_content, create_time, update_time
+    </sql>
+
+    <!-- 根据反馈ID查询回复列表 -->
+    <select id="selectByFeedbackId" resultMap="BaseResultMap">
+        SELECT 
+            <include refid="Base_Column_List" />
+        FROM life_feedback_reply
+        WHERE feedback_id = #{feedbackId}
+        ORDER BY create_time ASC
+    </select>
+
+</mapper>
+

+ 90 - 0
alien-entity/src/main/resources/mapper/LifeImgMapper.xml

@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="shop.alien.mapper.LifeImgMapper">
+
+    <!-- 通用结果映射 -->
+    <resultMap id="BaseResultMap" type="shop.alien.entity.store.LifeImg">
+        <id column="id" property="id" />
+        <result column="feedback_id" property="feedbackId" />
+        <result column="img_url" property="imgUrl" />
+        <result column="thumbnail_url" property="thumbnailUrl" />
+        <result column="file_type" property="fileType" />
+        <result column="upload_time" property="uploadTime" />
+        <result column="create_time" property="createTime" />
+        <result column="update_time" property="updateTime" />
+        <result column="created_user_id" property="createdUserId" />
+        <result column="updated_user_id" property="updatedUserId" />
+    </resultMap>
+
+    <!-- 基础字段 -->
+    <sql id="Base_Column_List">
+        id, feedback_id, img_url, thumbnail_url, file_type, upload_time,
+        create_time, update_time, created_user_id, updated_user_id
+    </sql>
+
+    <!-- 根据反馈ID查询图片列表 -->
+    <select id="selectByFeedbackId" resultMap="BaseResultMap">
+        SELECT 
+            <include refid="Base_Column_List" />
+        FROM life_img
+        WHERE feedback_id = #{feedbackId}
+    </select>
+
+    <!-- 批量插入附件(图片和视频) -->
+    <insert id="batchInsert" parameterType="java.util.List">
+        INSERT INTO life_img (
+            feedback_id, img_url, thumbnail_url, file_type, upload_time,
+            create_time, update_time, created_user_id, updated_user_id
+        ) VALUES
+        <foreach collection="list" item="item" separator=",">
+            (
+                #{item.feedbackId},
+                #{item.imgUrl},
+                #{item.thumbnailUrl},
+                #{item.fileType},
+                #{item.uploadTime},
+                NOW(),
+                NOW(),
+                #{item.createdUserId},
+                #{item.updatedUserId}
+            )
+        </foreach>
+    </insert>
+
+    <!-- 根据反馈ID删除图片 -->
+    <delete id="deleteByFeedbackId">
+        DELETE FROM life_img
+        WHERE feedback_id = #{feedbackId}
+    </delete>
+
+    <!-- 查询反馈的图片URL列表 -->
+    <select id="selectImgUrlsByFeedbackId" resultType="java.lang.String">
+        SELECT img_url
+        FROM life_img
+        WHERE feedback_id = #{feedbackId}
+        AND file_type = 1
+        ORDER BY upload_time ASC
+    </select>
+
+    <!-- 查询反馈的视频URL列表 -->
+    <select id="selectVideoUrlsByFeedbackId" resultType="java.lang.String">
+        SELECT img_url
+        FROM life_img
+        WHERE feedback_id = #{feedbackId}
+        AND file_type = 2
+        ORDER BY upload_time ASC
+    </select>
+
+    <!-- 查询反馈的视频信息(包含视频URL和缩略图URL) -->
+    <select id="selectVideoInfoByFeedbackId" resultMap="BaseResultMap">
+        SELECT
+            <include refid="Base_Column_List" />
+        FROM life_img
+        WHERE feedback_id = #{feedbackId}
+        AND file_type = 2
+        ORDER BY upload_time ASC
+    </select>
+
+</mapper>

+ 57 - 0
alien-second/src/main/java/shop/alien/second/util/JsonUtils.java

@@ -31,4 +31,61 @@ public class JsonUtils {
             throw new RuntimeException("解析JSON失败", e);
         }
     }
+
+
+    /**
+     * 从图片对象中获取URL(支持多种可能的字段名)
+     * 例如:从 banner_image 或 vertical_image 对象中获取 url 或 imgUrl
+     *
+     * @param jsonString JSON字符串
+     * @param imageObjectKey 图片对象的key,如 "banner_image" 或 "vertical_image"
+     * @return 图片URL字符串,如果不存在则返回null
+     */
+    public static String getImageUrlFromObject(String jsonString, String imageObjectKey) {
+        try {
+            JsonNode jsonNode = objectMapper.readTree(jsonString);
+            JsonNode dataNode = jsonNode.get("data");
+            if (dataNode == null) {
+                return null;
+            }
+            
+            JsonNode imageNode = dataNode.get(imageObjectKey);
+            if (imageNode == null || !imageNode.isObject()) {
+                return null;
+            }
+            
+            // 尝试常见的URL字段名
+            String[] possibleUrlFields = {"url", "imgUrl", "imageUrl", "img_url", "image_url"};
+            for (String field : possibleUrlFields) {
+                JsonNode urlNode = imageNode.get(field);
+                if (urlNode != null && urlNode.isTextual()) {
+                    return urlNode.asText();
+                }
+            }
+            
+            return null;
+        } catch (Exception e) {
+            throw new RuntimeException("解析图片URL失败,对象key: " + imageObjectKey, e);
+        }
+    }
+
+    /**
+     * 获取banner图片URL
+     *
+     * @param jsonString JSON字符串
+     * @return banner图片URL
+     */
+    public static String getBannerImageUrl(String jsonString) {
+        return getImageUrlFromObject(jsonString, "banner_image");
+    }
+
+    /**
+     * 获取竖版图片URL
+     *
+     * @param jsonString JSON字符串
+     * @return 竖版图片URL
+     */
+    public static String getVerticalImageUrl(String jsonString) {
+        return getImageUrlFromObject(jsonString, "vertical_image");
+    }
 }

+ 2 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/controller/OperationalActivityController.java

@@ -36,6 +36,8 @@ public class OperationalActivityController {
     public R<String> createActivity(@RequestBody StoreOperationalActivityDTO dto) {
         log.info("OperationalActivityController.createActivity: dto={}", dto);
         try {
+            log.warn("用户输入的图片描述{}",dto.getImgDescribe());
+
             int result = activityService.createActivity(dto);
             if (result > 0) {
                 return R.success("活动创建成功");

+ 35 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/feign/AlienAIFeign.java

@@ -0,0 +1,35 @@
+package shop.alien.storeplatform.feign;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.http.MediaType;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.bind.annotation.*;
+
+@FeignClient(url = "${feign.alienAI.url}", name = "alien-AI")
+public interface AlienAIFeign {
+
+    /**
+     * 登录接口 - 获取access_token
+     *
+     * @param username 用户名
+     * @param password 密码
+     * @return JsonNode 响应体,包含access_token
+     */
+    @PostMapping(value = "/ai/user-auth-core/api/v1/auth/login",
+            consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
+            produces = MediaType.APPLICATION_JSON_VALUE)
+    JsonNode login(@RequestBody MultiValueMap<String, String> formData);
+
+    /**
+     * 使用 JsonNode 灵活调用接口 - 生成促销图片
+     *
+     * @param authorization Bearer token,格式为 "Bearer {access_token}"
+     * @param requestBody JsonNode 请求体,可以灵活构建
+     * @return JsonNode 响应体,可以灵活解析
+     */
+    @PostMapping(value = "/ai/life-manager/api/v1/promotion_image/generate",
+                 consumes = MediaType.APPLICATION_JSON_VALUE,
+                 produces = MediaType.APPLICATION_JSON_VALUE)
+    JsonNode generatePromotionImage(@RequestHeader("Authorization") String authorization, @RequestBody JsonNode requestBody);
+}

+ 68 - 2
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/OperationalActivityServiceImpl.java

@@ -5,10 +5,16 @@ 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.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
 import shop.alien.entity.store.LifeDiscountCoupon;
 import shop.alien.entity.store.LifeUser;
 import shop.alien.entity.store.StoreImg;
@@ -19,6 +25,7 @@ import shop.alien.entity.storePlatform.vo.StoreOperationalActivityVO;
 import shop.alien.mapper.LifeDiscountCouponMapper;
 import shop.alien.mapper.StoreImgMapper;
 import shop.alien.mapper.storePlantform.StoreOperationalActivityMapper;
+import shop.alien.storeplatform.feign.AlienAIFeign;
 import shop.alien.storeplatform.service.OperationalActivityService;
 
 import java.text.SimpleDateFormat;
@@ -55,13 +62,21 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
 
     private final LifeDiscountCouponMapper lifeDiscountCouponMapper;
 
+    private final AlienAIFeign alienAIFeign;
+
+    @Value("${ai.aiAccount}")
+    private String aiAccount;
+
+    @Value("${ai.aiPassword}")
+    private String aiPassword;
+
     @Override
     public int createActivity(StoreOperationalActivityDTO dto) {
         log.info("OperationalActivityServiceImpl.createActivity: dto={}", dto);
-        
+
         StoreOperationalActivity activity = new StoreOperationalActivity();
         BeanUtils.copyProperties(dto, activity);
-        
+
         // 设置默认值
         if (activity.getParticipationLimit() == null) {
             activity.setParticipationLimit(0);
@@ -71,6 +86,57 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
         }
         Integer result = activityMapper.insert(activity);
         if (result > 0) {
+            // 使用用户描述让AI生成图片。
+            if (dto.getUploadImgType()==2) {
+                try {
+                    // 先调用登录接口获取access_token
+                    MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
+                    formData.add("username", aiAccount);
+                    formData.add("password", aiPassword);
+                    JsonNode loginResponse = alienAIFeign.login(formData);
+
+                    String accessToken = null;
+                    if (loginResponse != null && loginResponse.has("data")) {
+                        JsonNode data = loginResponse.get("data");
+                        if (data.has("access_token")) {
+                            accessToken = data.get("access_token").asText();
+                        }
+                    }
+                    
+                    if (accessToken == null || accessToken.isEmpty()) {
+                        log.error("获取AI服务access_token失败,无法生成促销图片");
+                    } else {
+                        ObjectMapper objectMapper = new ObjectMapper();
+                        ObjectNode requestBody = objectMapper.createObjectNode();
+                        requestBody.put("text", dto.getImgDescribe());
+                        // 调用接口,传递Bearer token
+                        String authorization = "Bearer " + accessToken;
+                        JsonNode response = alienAIFeign.generatePromotionImage(authorization, requestBody);
+                        // 解析响应
+                        if (response.has("data")) {
+                            JsonNode data = response.get("data");
+                            // 提取横向图(banner_image)的图片URL
+                            if (data.has("banner_image")) {
+                                JsonNode bannerImage = data.get("banner_image");
+                                if (bannerImage.has("image_url") && !bannerImage.get("image_url").isNull()) {
+                                    String bannerImageUrl = bannerImage.get("image_url").asText();
+                                    dto.getActivityTitleImg().setImgUrl(bannerImageUrl);
+                                }
+                            }
+                            // 提取竖向图(vertical_image)的图片URL
+                            if (data.has("vertical_image")) {
+                                JsonNode verticalImage = data.get("vertical_image");
+                                if (verticalImage.has("image_url") && !verticalImage.get("image_url").isNull()) {
+                                    String verticalImageUrl = verticalImage.get("image_url").asText();
+                                    dto.getActivityDetailImg().setImgUrl(verticalImageUrl);
+                                }
+                            }
+                        }
+                    }
+                } catch (Exception e) {
+                    log.error("调用AI服务生成促销图片失败", e);
+                }
+            }
             dto.getActivityTitleImg().setBusinessId(activity.getId());
             dto.getActivityTitleImg().setImgType(26);
             imgMapper.insert(dto.getActivityTitleImg());

+ 117 - 0
alien-store/src/main/java/shop/alien/store/controller/LifeFeedbackController.java

@@ -0,0 +1,117 @@
+package shop.alien.store.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiOperation;
+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.LifeFeedbackDto;
+import shop.alien.entity.store.dto.UserReplyDto;
+import shop.alien.entity.store.dto.*;
+import shop.alien.entity.store.vo.LifeFeedbackDetailVo;
+import shop.alien.entity.store.vo.LifeFeedbackListVo;
+import shop.alien.entity.store.vo.LifeFeedbackVo;
+import shop.alien.store.service.LifeFeedbackService;
+
+/**
+ * 意见反馈 Controller
+ */
+@Api(tags = {"意见反馈模块"})
+@Slf4j
+@CrossOrigin
+@RestController
+@RequestMapping("/feedback")
+@RequiredArgsConstructor
+public class LifeFeedbackController {
+
+    private final LifeFeedbackService lifeFeedbackService;
+
+    @ApiOperation(value = "提交反馈", httpMethod = "POST")
+    @PostMapping("/submit")
+    public R<String> submitFeedback(@RequestBody LifeFeedbackDto dto) {
+        log.info("LifeFeedbackController.submitFeedback, dto={}", dto);
+        return lifeFeedbackService.submitFeedback(dto);
+    }
+
+    @ApiOperation(value = "查询用户历史反馈列表", httpMethod = "GET")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "feedbackSource", value = "反馈来源:0-用户端,1-商家端", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "page", value = "页码", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "size", value = "每页数量", dataType = "int", paramType = "query", required = true)
+    })
+    @GetMapping("/list")
+    public R<IPage<LifeFeedbackVo>> getFeedbackList(
+            @RequestParam("userId") Integer userId,
+            @RequestParam("feedbackSource") Integer feedbackSource,
+            @RequestParam(value = "page", defaultValue = "1") int page,
+            @RequestParam(value = "size", defaultValue = "10") int size) {
+        log.info("LifeFeedbackController.getFeedbackList, userId={}, feedbackSource={}, page={}, size={}", 
+                userId, feedbackSource, page, size);
+        IPage<LifeFeedbackVo> result = lifeFeedbackService.getFeedbackList(userId, feedbackSource, page, size);
+        return R.data(result);
+    }
+
+    @ApiOperation(value = "查询反馈详情", httpMethod = "GET")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "feedbackId", value = "反馈ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/detail")
+    public R<LifeFeedbackVo> getFeedbackDetail(@RequestParam("feedbackId") Integer feedbackId) {
+        log.info("LifeFeedbackController.getFeedbackDetail, feedbackId={}", feedbackId);
+        return lifeFeedbackService.getFeedbackDetail(feedbackId);
+    }
+
+    @ApiOperation(value = "用户回复", httpMethod = "POST")
+    @PostMapping("/userReply")
+    public R<String> userReply(@RequestBody UserReplyDto dto) {
+        log.info("LifeFeedbackController.userReply, dto={}", dto);
+        return lifeFeedbackService.userReply(dto);
+    }
+
+    // ==================== 中台接口 ====================
+
+    @ApiOperation(value = "中台-查询意见反馈列表", httpMethod = "GET")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "feedbackType", value = "反馈类型:0-优化建议,1-问题", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "handleStatus", value = "处理状态:0-处理中,1-已解决,2-未分配,3-无需解决", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "feedbackSource", value = "反馈来源:0-用户端,1-商家端", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "feedbackWay", value = "反馈方式:0-用户反馈,1-AI识别", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "page", value = "页码", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "size", value = "每页数量", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/platform/list")
+    public R<IPage<LifeFeedbackListVo>> getWebFeedbackList(LifeFeedbackQueryDto queryDto) {
+        log.info("LifeFeedbackController.getWebFeedbackList, queryDto={}", queryDto);
+        return lifeFeedbackService.getWebFeedbackList(queryDto);
+    }
+
+    @ApiOperation(value = "中台-查询反馈详情", httpMethod = "GET")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "feedbackId", value = "反馈ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/platform/detail")
+    public R<LifeFeedbackDetailVo> getWebFeedbackDetail(@RequestParam("feedbackId") Integer feedbackId) {
+        log.info("LifeFeedbackController.getWebFeedbackDetail, feedbackId={}", feedbackId);
+        return lifeFeedbackService.getWebFeedbackDetail(feedbackId);
+    }
+
+    @ApiOperation(value = "中台-回复用户", httpMethod = "POST")
+    @PostMapping("/platform/reply")
+    public R<String> webReplyUser(@RequestBody LifeFeedbackReplyWebDto replyDto) {
+        log.info("LifeFeedbackController.webReplyUser, replyDto={}", replyDto);
+        return lifeFeedbackService.webReplyUser(replyDto);
+    }
+
+    @ApiOperation(value = "中台-更新处理状态", httpMethod = "POST")
+    @PostMapping("/platform/updateStatus")
+    public R<String> updateWebFeedbackStatus(@RequestBody LifeFeedbackStatusDto statusDto) {
+        log.info("LifeFeedbackController.updateWebFeedbackStatus, statusDto={}", statusDto);
+        return lifeFeedbackService.updateWebFeedbackStatus(statusDto);
+    }
+}
+

+ 20 - 0
alien-store/src/main/java/shop/alien/store/service/LifeFeedbackReplyService.java

@@ -0,0 +1,20 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.store.LifeFeedbackReply;
+
+import java.util.List;
+
+/**
+ * 反馈回复 Service
+ */
+public interface LifeFeedbackReplyService extends IService<LifeFeedbackReply> {
+
+    /**
+     * 根据反馈ID查询回复列表
+     * @param feedbackId 反馈ID
+     * @return 回复列表
+     */
+    List<LifeFeedbackReply> getByFeedbackId(Integer feedbackId);
+}
+

+ 78 - 0
alien-store/src/main/java/shop/alien/store/service/LifeFeedbackService.java

@@ -0,0 +1,78 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.LifeFeedback;
+import shop.alien.entity.store.dto.*;
+import shop.alien.entity.store.vo.LifeFeedbackDetailVo;
+import shop.alien.entity.store.vo.LifeFeedbackListVo;
+import shop.alien.entity.store.vo.LifeFeedbackVo;
+
+/**
+ * 意见反馈 Service
+ */
+public interface LifeFeedbackService extends IService<LifeFeedback> {
+
+    /**
+     * 提交反馈
+     * @param dto 反馈信息
+     * @return 反馈结果
+     */
+    R<String> submitFeedback(LifeFeedbackDto dto);
+
+    /**
+     * 查询用户历史反馈列表
+     * @param userId 用户ID
+     * @param feedbackSource 反馈来源
+     * @param page 页码
+     * @param size 每页数量
+     * @return 反馈列表
+     */
+    IPage<LifeFeedbackVo> getFeedbackList(Integer userId, Integer feedbackSource, int page, int size);
+
+    /**
+     * 查询反馈详情
+     * @param feedbackId 反馈ID
+     * @return 反馈详情
+     */
+    R<LifeFeedbackVo> getFeedbackDetail(Integer feedbackId);
+
+    /**
+     * 用户回复
+     * @param dto 用户回复信息
+     * @return 回复结果
+     */
+    R<String> userReply(UserReplyDto dto);
+
+    // ==================== 中台接口 ====================
+
+    /**
+     * 中台-查询意见反馈列表
+     * @param queryDto 查询条件
+     * @return 反馈列表
+     */
+    R<IPage<LifeFeedbackListVo>> getWebFeedbackList(LifeFeedbackQueryDto queryDto);
+
+    /**
+     * 中台-查询反馈详情
+     * @param feedbackId 反馈ID
+     * @return 反馈详情
+     */
+    R<LifeFeedbackDetailVo> getWebFeedbackDetail(Integer feedbackId);
+
+    /**
+     * 中台-回复用户
+     * @param replyDto 回复信息
+     * @return 回复结果
+     */
+    R<String> webReplyUser(LifeFeedbackReplyWebDto replyDto);
+
+    /**
+     * 中台-更新反馈处理状态
+     * @param statusDto 状态信息
+     * @return 更新结果
+     */
+    R<String> updateWebFeedbackStatus(LifeFeedbackStatusDto statusDto);
+}
+

+ 47 - 0
alien-store/src/main/java/shop/alien/store/service/LifeImgService.java

@@ -0,0 +1,47 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.store.LifeImg;
+
+import java.util.List;
+
+/**
+ * 反馈图片 Service
+ */
+public interface LifeImgService extends IService<LifeImg> {
+
+    /**
+     * 根据反馈ID查询图片列表
+     * @param feedbackId 反馈ID
+     * @return 图片列表
+     */
+    List<LifeImg> getByFeedbackId(Integer feedbackId);
+
+    /**
+     * 批量保存图片
+     * @param imgList 图片列表
+     * @return 是否成功
+     */
+    boolean batchSave(List<LifeImg> imgList);
+
+    /**
+     * 根据反馈ID删除图片
+     * @param feedbackId 反馈ID
+     * @return 是否成功
+     */
+    boolean removeByFeedbackId(Integer feedbackId);
+
+    /**
+     * 查询反馈的图片URL列表
+     * @param feedbackId 反馈ID
+     * @return 图片URL列表
+     */
+    List<String> getImgUrlsByFeedbackId(Integer feedbackId);
+
+    /**
+     * 查询反馈的视频URL列表
+     * @param feedbackId 反馈ID
+     * @return 视频URL列表
+     */
+    List<String> getVideoUrlsByFeedbackId(Integer feedbackId);
+}

+ 28 - 0
alien-store/src/main/java/shop/alien/store/service/impl/LifeFeedbackReplyServiceImpl.java

@@ -0,0 +1,28 @@
+package shop.alien.store.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import shop.alien.entity.store.LifeFeedbackReply;
+import shop.alien.mapper.LifeFeedbackReplyMapper;
+import shop.alien.store.service.LifeFeedbackReplyService;
+
+import java.util.List;
+
+/**
+ * 反馈回复 Service实现类
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class LifeFeedbackReplyServiceImpl extends ServiceImpl<LifeFeedbackReplyMapper, LifeFeedbackReply> implements LifeFeedbackReplyService {
+
+    private final LifeFeedbackReplyMapper lifeFeedbackReplyMapper;
+
+    @Override
+    public List<LifeFeedbackReply> getByFeedbackId(Integer feedbackId) {
+        return lifeFeedbackReplyMapper.selectByFeedbackId(feedbackId);
+    }
+}
+

+ 713 - 0
alien-store/src/main/java/shop/alien/store/service/impl/LifeFeedbackServiceImpl.java

@@ -0,0 +1,713 @@
+package shop.alien.store.service.impl;
+
+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.CollectionUtils;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.LifeFeedback;
+import shop.alien.entity.store.LifeFeedbackReply;
+import shop.alien.entity.store.LifeImg;
+import shop.alien.entity.store.LifeLog;
+import shop.alien.entity.store.dto.*;
+import shop.alien.entity.store.vo.*;
+import shop.alien.entity.store.vo.FeedbackReplyVo;
+import shop.alien.mapper.LifeFeedbackMapper;
+import shop.alien.mapper.LifeLogMapper;
+import shop.alien.mapper.LifeNoticeMapper;
+import shop.alien.mapper.LifeUserMapper;
+import shop.alien.mapper.StoreUserMapper;
+import shop.alien.entity.store.LifeNotice;
+import shop.alien.entity.store.LifeUser;
+import shop.alien.entity.store.StoreUser;
+import shop.alien.entity.store.vo.WebSocketVo;
+import shop.alien.store.config.WebSocketProcess;
+import shop.alien.store.service.LifeFeedbackService;
+import shop.alien.store.service.LifeFeedbackReplyService;
+import shop.alien.store.service.LifeImgService;
+import com.alibaba.fastjson2.JSONObject;
+
+import java.util.Date;
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * 意见反馈 Service实现类
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+@Transactional(rollbackFor = Exception.class)
+public class LifeFeedbackServiceImpl extends ServiceImpl<LifeFeedbackMapper, LifeFeedback> implements LifeFeedbackService {
+
+    private final LifeFeedbackMapper lifeFeedbackMapper;
+    private final LifeImgService lifeImgService;
+    private final LifeLogMapper lifeLogMapper;
+    private final LifeFeedbackReplyService lifeFeedbackReplyService;
+    private final LifeNoticeMapper lifeNoticeMapper;
+    private final LifeUserMapper lifeUserMapper;
+    private final StoreUserMapper storeUserMapper;
+    private final WebSocketProcess webSocketProcess;
+
+    @Override
+    public R<String> submitFeedback(LifeFeedbackDto dto) {
+        try {
+            // 1. 参数校验
+            if (dto.getUserId() == null) {
+                return R.fail("用户ID不能为空");
+            }
+            if (dto.getFeedbackSource() == null) {
+                return R.fail("反馈来源不能为空");
+            }
+            if (dto.getFeedbackType() == null) {
+                return R.fail("反馈类型不能为空");
+            }
+            if (dto.getContent() == null || dto.getContent().trim().isEmpty()) {
+                return R.fail("反馈内容不能为空");
+            }
+
+            // 2. 创建反馈记录(使用MyBatis Plus的save方法)
+            LifeFeedback feedback = new LifeFeedback();
+            BeanUtils.copyProperties(dto, feedback);
+            // 如果feedbackWay为空,默认为用户主动反馈(0)
+            if (feedback.getFeedbackWay() == null) {
+                feedback.setFeedbackWay(0);
+            }
+            feedback.setFeedbackTime(new Date());
+            feedback.setHandleStatus(0); // 处理中
+            feedback.setCreateTime(new Date());
+
+            boolean saveResult = this.save(feedback);
+            if (!saveResult) {
+                return R.fail("提交反馈失败");
+            }
+
+            // 3. 保存附件(图片和视频)
+            List<LifeImg> fileList = new ArrayList<>();
+            // 收集所有视频的截图URL,避免重复保存为普通图片
+            List<String> videoThumbnailUrls = new ArrayList<>();
+
+            if (!CollectionUtils.isEmpty(dto.getFileUrlList())) {
+                // 先处理视频,找到所有视频及其封面图
+                List<String> videoUrls = new ArrayList<>();
+                List<String> imageUrls = new ArrayList<>();
+
+                // 分类:区分视频和图片
+                for (String fileUrl : dto.getFileUrlList()) {
+                    if (isVideoUrl(fileUrl)) {
+                        videoUrls.add(fileUrl);
+                    } else if (isImageUrl(fileUrl)) {
+                        imageUrls.add(fileUrl);
+                    }
+                }
+
+                // 处理视频:自动匹配封面图
+                for (String videoUrl : videoUrls) {
+                    LifeImg video = new LifeImg();
+                    video.setFeedbackId(feedback.getId());
+                    video.setImgUrl(videoUrl);
+                    video.setFileType(2); // 2-视频
+                    video.setUploadTime(new Date());
+
+                    // 从fileUrlList中查找对应的封面图URL(通过文件名匹配)
+                    // 视频URL格式: .../video/xxx123456.mp4
+                    // 封面图URL格式: .../video/xxx123456.jpg 或 .../image/xxx123456.jpg
+                    String videoFileName = videoUrl.substring(videoUrl.lastIndexOf('/') + 1);
+                    String videoNameWithoutExt = videoFileName.substring(0, videoFileName.lastIndexOf('.'));
+
+                    // 在图片列表中查找匹配的封面图
+                    for (String imgUrl : imageUrls) {
+                        String imgFileName = imgUrl.substring(imgUrl.lastIndexOf('/') + 1);
+                        if (imgFileName.contains(".")) {
+                            String imgNameWithoutExt = imgFileName.substring(0, imgFileName.lastIndexOf('.'));
+                            // 如果文件名(不含扩展名)相同,且是图片格式,则认为是该视频的封面图
+                            if (videoNameWithoutExt.equals(imgNameWithoutExt) && isImageUrl(imgUrl)) {
+                                video.setThumbnailUrl(imgUrl);
+                                videoThumbnailUrls.add(imgUrl); // 记录已使用的封面图URL
+                                break;
+                            }
+                        }
+                    }
+
+                    fileList.add(video);
+                }
+
+                // 处理图片(排除已作为视频封面的URL)
+                for (String imgUrl : imageUrls) {
+                    // 如果该URL已被用作视频封面,则跳过,不重复保存
+                    if (!videoThumbnailUrls.contains(imgUrl)) {
+                        LifeImg img = new LifeImg();
+                        img.setFeedbackId(feedback.getId());
+                        img.setImgUrl(imgUrl);
+                        img.setFileType(1); // 1-图片
+                        img.setUploadTime(new Date());
+                        fileList.add(img);
+                    }
+                }
+            }
+
+            if (!fileList.isEmpty()) {
+                lifeImgService.batchSave(fileList);
+            }
+
+            // 4. 记录日志(只记录详细内容)
+            saveLog(feedback.getId(), feedback.getContent(), "0");
+
+            return R.success("提交成功");
+        } catch (Exception e) {
+            log.error("提交反馈失败", e);
+            return R.fail("提交反馈失败:" + e.getMessage());
+        }
+    }
+
+    @Override
+    public IPage<LifeFeedbackVo> getFeedbackList(Integer userId, Integer feedbackSource, int page, int size) {
+        try {
+            // 使用自定义SQL查询(已包含工作人员名称)
+            // 查询用户反馈(feedbackWay=0)和AI识别(feedbackWay=1)的记录
+            Page<LifeFeedbackVo> pageParam = new Page<>(page, size);
+            IPage<LifeFeedbackVo> voPage = lifeFeedbackMapper.selectFeedbackListWithStaff(
+                    pageParam, userId, feedbackSource, null, null
+            );
+
+            // 为每条记录查询附件(图片和视频)并设置反馈类型名称
+            voPage.getRecords().forEach(vo -> {
+                List<String> imgUrls = lifeImgService.getImgUrlsByFeedbackId(vo.getId());
+                vo.setImgUrlList(imgUrls);
+                List<String> videoUrls = lifeImgService.getVideoUrlsByFeedbackId(vo.getId());
+                vo.setVideoUrlList(videoUrls);
+                // 设置反馈类型名称
+                vo.setFeedbackTypeName(getFeedbackTypeName(vo.getFeedbackType()));
+            });
+
+            return voPage;
+        } catch (Exception e) {
+            log.error("查询反馈列表失败", e);
+            return new Page<>(page, size);
+        }
+    }
+
+    @Override
+    public R<LifeFeedbackVo> getFeedbackDetail(Integer feedbackId) {
+        try {
+            // 1. 使用自定义SQL查询反馈详情(已包含工作人员名称)
+            LifeFeedbackVo vo = lifeFeedbackMapper.selectFeedbackDetail(feedbackId);
+            if (vo == null) {
+                return R.fail("反馈记录不存在");
+            }
+
+            // 2. 查询附件(图片和视频)
+            // 查询所有附件,然后过滤出原始反馈的附件(排除回复附件)
+            List<LifeImg> allImgs = lifeImgService.getByFeedbackId(feedbackId);
+            List<String> imgUrls = new ArrayList<>();
+            List<String> videoUrls = new ArrayList<>();
+            Date feedbackTime = vo.getFeedbackTime();
+            if (feedbackTime != null) {
+                long feedbackTimeMs = feedbackTime.getTime();
+                for (LifeImg img : allImgs) {
+                    if (img.getUploadTime() != null) {
+                        long imgTimeMs = img.getUploadTime().getTime();
+                        // 判断附件是否属于原始反馈(时间差在5分钟内,且早于最早的回复)
+                        // 简化处理:如果是反馈后5分钟内的附件,认为是原始反馈的附件
+                        List<LifeFeedbackReply> replyList = lifeFeedbackReplyService.getByFeedbackId(feedbackId);
+                        boolean isOriginalFeedback = true;
+                        if (!replyList.isEmpty()) {
+                            Date firstReplyTime = replyList.get(0).getCreateTime();
+                            // 如果附件时间在最早回复时间之后,则不属于原始反馈
+                            if (img.getUploadTime().after(firstReplyTime)) {
+                                isOriginalFeedback = false;
+                            } else {
+                                long timeDiff = Math.abs(imgTimeMs - feedbackTimeMs);
+                                if (timeDiff > 5 * 60 * 1000) { // 超过5分钟
+                                    isOriginalFeedback = false;
+                                }
+                            }
+                        } else {
+                            long timeDiff = Math.abs(imgTimeMs - feedbackTimeMs);
+                            if (timeDiff > 5 * 60 * 1000) { // 超过5分钟
+                                isOriginalFeedback = false;
+                            }
+                        }
+
+                        if (isOriginalFeedback) {
+                            if (img.getFileType() == 1) {
+                                imgUrls.add(img.getImgUrl());
+                            } else if (img.getFileType() == 2) {
+                                videoUrls.add(img.getImgUrl());
+                            }
+                        }
+                    }
+                }
+            } else {
+                // 如果没有反馈时间,使用简单方式:只查询图片和视频
+                for (LifeImg img : allImgs) {
+                    if (img.getFileType() == 1) {
+                        imgUrls.add(img.getImgUrl());
+                    } else if (img.getFileType() == 2) {
+                        videoUrls.add(img.getImgUrl());
+                    }
+                }
+            }
+            vo.setImgUrlList(imgUrls);
+            vo.setVideoUrlList(videoUrls);
+            // 设置反馈类型名称
+            vo.setFeedbackTypeName(getFeedbackTypeName(vo.getFeedbackType()));
+
+            // 3. 查询回复列表(从life_feedback_reply表)
+            List<LifeFeedbackReply> replyList = lifeFeedbackReplyService.getByFeedbackId(feedbackId);
+            // 转换为VO格式(使用之前查询的allImgs)
+            List<LifeFeedbackVo> replyVoList = new ArrayList<>();
+            for (LifeFeedbackReply reply : replyList) {
+                LifeFeedbackVo replyVo = new LifeFeedbackVo();
+                replyVo.setId(reply.getId());
+                replyVo.setContent(reply.getReplyContent());
+                replyVo.setFeedbackTime(reply.getCreateTime());
+                // reply_type: 0-平台回复, 1-我的回复
+                replyVo.setStaffId(reply.getReplyType() == 0 ? 1 : null); // 平台回复有staffId,用户回复为null
+                // 查询回复的附件(通过时间判断:上传时间在回复创建时间前后5分钟内)
+                List<String> replyImgUrls = new ArrayList<>();
+                List<String> replyVideoUrls = new ArrayList<>();
+                Date replyTime = reply.getCreateTime();
+                long replyTimeMs = replyTime.getTime();
+                for (LifeImg img : allImgs) {
+                    if (img.getUploadTime() != null) {
+                        long imgTimeMs = img.getUploadTime().getTime();
+                        // 判断附件是否属于该回复(时间差在5分钟内)
+                        long timeDiff = Math.abs(imgTimeMs - replyTimeMs);
+                        if (timeDiff <= 5 * 60 * 1000) { // 5分钟
+                            if (img.getFileType() == 1) {
+                                replyImgUrls.add(img.getImgUrl());
+                            } else if (img.getFileType() == 2) {
+                                replyVideoUrls.add(img.getImgUrl());
+                            }
+                        }
+                    }
+                }
+                replyVo.setImgUrlList(replyImgUrls);
+                replyVo.setVideoUrlList(replyVideoUrls);
+                replyVoList.add(replyVo);
+            }
+
+            // 4. 按时间升序排序回复
+            replyVoList.sort((a, b) -> a.getFeedbackTime().compareTo(b.getFeedbackTime()));
+            vo.setPlatformReplies(replyVoList);
+
+            return R.data(vo);
+        } catch (Exception e) {
+            log.error("查询反馈详情失败", e);
+            return R.fail("查询反馈详情失败:" + e.getMessage());
+        }
+    }
+
+    @Override
+    public R<String> userReply(UserReplyDto dto) {
+        try {
+            // 1. 参数校验
+            if (dto.getUserId() == null) {
+                return R.fail("用户ID不能为空");
+            }
+            if (dto.getFeedbackSource() == null) {
+                return R.fail("反馈来源不能为空");
+            }
+            if (dto.getFeedbackId() == null) {
+                return R.fail("反馈ID不能为空");
+            }
+            if (dto.getContent() == null || dto.getContent().trim().isEmpty()) {
+                return R.fail("回复内容不能为空");
+            }
+
+            // 2. 查询原始反馈(用于验证反馈是否存在)
+            LifeFeedback originalFeedback = lifeFeedbackMapper.selectById(dto.getFeedbackId());
+            if (originalFeedback == null) {
+                return R.fail("反馈记录不存在");
+            }
+
+            // 3. 创建用户回复记录(保存到life_feedback_reply表)
+            LifeFeedbackReply userReply = new LifeFeedbackReply();
+            userReply.setFeedbackId(dto.getFeedbackId());
+            userReply.setReplyType(1); // 1-我的回复(用户回复)
+            userReply.setReplyContent(dto.getContent());
+            userReply.setCreateTime(new Date());
+            userReply.setUpdateTime(new Date());
+
+            boolean saveResult = lifeFeedbackReplyService.save(userReply);
+            if (!saveResult) {
+                return R.fail("回复失败");
+            }
+
+            // 4. 记录日志(只记录内容)
+            saveLog(dto.getFeedbackId(), dto.getContent(), "2");
+
+            return R.success("回复成功");
+        } catch (Exception e) {
+            log.error("用户回复失败", e);
+            return R.fail("用户回复失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 保存操作日志
+     * @param feedbackId 反馈ID
+     * @param context 日志内容
+     * @param type 操作类型:0-创建反馈工单,1-分配跟踪人员,2-回复用户,3-问题解决状态
+     */
+    private void saveLog(Integer feedbackId, String context, String type) {
+        try {
+            LifeLog lifeLog = new LifeLog();
+            lifeLog.setFeedbackId(feedbackId);
+            lifeLog.setContext(context);
+            lifeLog.setType(type);
+            lifeLog.setCreatedTime(new Date());
+            lifeLogMapper.insert(lifeLog);
+        } catch (Exception e) {
+            log.error("保存日志失败", e);
+        }
+    }
+
+    /**
+     * 获取反馈类型名称
+     * @param feedbackType 反馈类型:0-bug反馈,1-优化反馈,2-新增功能反馈
+     * @return 反馈类型名称
+     */
+    private String getFeedbackTypeName(Integer feedbackType) {
+        if (feedbackType == null) {
+            return "";
+        }
+        switch (feedbackType) {
+            case 0:
+                return "bug反馈";
+            case 1:
+                return "优化反馈";
+            case 2:
+                return "新增功能反馈";
+            default:
+                return "";
+        }
+    }
+
+    /**
+     * 判断URL是否为视频
+     * @param url 文件URL
+     * @return true-视频,false-非视频
+     */
+    private boolean isVideoUrl(String url) {
+        if (url == null || url.isEmpty()) {
+            return false;
+        }
+        String lowerUrl = url.toLowerCase();
+        return lowerUrl.endsWith(".mp4") ||
+               lowerUrl.endsWith(".avi") ||
+               lowerUrl.endsWith(".flv") ||
+               lowerUrl.endsWith(".mkv") ||
+               lowerUrl.endsWith(".rmvb") ||
+               lowerUrl.endsWith(".wmv") ||
+               lowerUrl.endsWith(".3gp") ||
+               lowerUrl.endsWith(".mov");
+    }
+
+    /**
+     * 判断URL是否为图片
+     * @param url 文件URL
+     * @return true-图片,false-非图片
+     */
+    private boolean isImageUrl(String url) {
+        if (url == null || url.isEmpty()) {
+            return false;
+        }
+        String lowerUrl = url.toLowerCase();
+        return lowerUrl.endsWith(".jpg") ||
+               lowerUrl.endsWith(".jpeg") ||
+               lowerUrl.endsWith(".png") ||
+               lowerUrl.endsWith(".bmp") ||
+               lowerUrl.endsWith(".webp") ||
+               lowerUrl.endsWith(".gif") ||
+               lowerUrl.endsWith(".svg");
+    }
+
+
+    // ==================== 中台接口实现 ====================
+
+    @Override
+    public R<IPage<LifeFeedbackListVo>> getWebFeedbackList(LifeFeedbackQueryDto queryDto) {
+        try {
+            Page<LifeFeedbackListVo> pageParam = new Page<>(queryDto.getPage(), queryDto.getSize());
+            IPage<LifeFeedbackListVo> result = lifeFeedbackMapper.selectWebFeedbackList(
+                    pageParam,
+                    queryDto.getFeedbackType(),
+                    queryDto.getHandleStatus(),
+                    queryDto.getFeedbackSource(),
+                    queryDto.getFeedbackWay()
+            );
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("中台-查询意见反馈列表失败", e);
+            return R.fail("查询失败:" + e.getMessage());
+        }
+    }
+
+    @Override
+    public R<LifeFeedbackDetailVo> getWebFeedbackDetail(Integer feedbackId) {
+        try {
+            if (feedbackId == null) {
+                return R.fail("反馈ID不能为空");
+            }
+
+            // 1. 查询反馈详情
+            LifeFeedbackDetailVo detail = lifeFeedbackMapper.selectWebFeedbackDetail(feedbackId);
+            if (detail == null) {
+                return R.fail("反馈记录不存在");
+            }
+
+            // 2. 查询附件列表(图片/视频)
+            List<FeedbackAttachmentVo> attachments = new ArrayList<>();
+            List<LifeImg> imgList = lifeImgService.getByFeedbackId(feedbackId);
+            if (!CollectionUtils.isEmpty(imgList)) {
+                for (LifeImg img : imgList) {
+                    FeedbackAttachmentVo attachment = new FeedbackAttachmentVo();
+                    attachment.setId(img.getId());
+                    attachment.setFileType(img.getFileType() != null ? img.getFileType() : 1);
+                    attachment.setFileUrl(img.getImgUrl());
+                    attachment.setThumbnailUrl(img.getThumbnailUrl());
+                    attachments.add(attachment);
+                }
+            }
+            detail.setAttachments(attachments);
+
+            // 3. 查询回复列表(平台回复和用户回复)
+            List<LifeFeedbackReply> replyList = lifeFeedbackReplyService.getByFeedbackId(feedbackId);
+            List<FeedbackReplyVo> replyVoList = new ArrayList<>();
+            for (LifeFeedbackReply reply : replyList) {
+                FeedbackReplyVo replyVo = new FeedbackReplyVo();
+                replyVo.setId(reply.getId());
+                replyVo.setFeedbackId(reply.getFeedbackId());
+                replyVo.setReplyType(reply.getReplyType());
+                replyVo.setReplyTypeName(reply.getReplyType() == 0 ? "平台回复" : "用户回复");
+                replyVo.setReplyContent(reply.getReplyContent());
+                replyVo.setCreateTime(reply.getCreateTime());
+                
+                // 查询回复的附件(通过时间判断:上传时间在回复创建时间前后5分钟内)
+                List<String> replyImgUrls = new ArrayList<>();
+                List<String> replyVideoUrls = new ArrayList<>();
+                Date replyTime = reply.getCreateTime();
+                if (replyTime != null && !CollectionUtils.isEmpty(imgList)) {
+                    long replyTimeMs = replyTime.getTime();
+                    for (LifeImg img : imgList) {
+                        if (img.getUploadTime() != null) {
+                            long imgTimeMs = img.getUploadTime().getTime();
+                            // 判断附件是否属于该回复(时间差在5分钟内)
+                            long timeDiff = Math.abs(imgTimeMs - replyTimeMs);
+                            if (timeDiff <= 5 * 60 * 1000) { // 5分钟
+                                if (img.getFileType() == 1) {
+                                    replyImgUrls.add(img.getImgUrl());
+                                } else if (img.getFileType() == 2) {
+                                    replyVideoUrls.add(img.getImgUrl());
+                                }
+                            }
+                        }
+                    }
+                }
+                replyVo.setImgUrlList(replyImgUrls);
+                replyVo.setVideoUrlList(replyVideoUrls);
+                replyVoList.add(replyVo);
+            }
+            
+            // 按时间升序排序回复
+            replyVoList.sort((a, b) -> a.getCreateTime().compareTo(b.getCreateTime()));
+            detail.setReplies(replyVoList);
+
+            return R.data(detail);
+        } catch (Exception e) {
+            log.error("中台-查询反馈详情失败", e);
+            return R.fail("查询失败:" + e.getMessage());
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R<String> webReplyUser(LifeFeedbackReplyWebDto replyDto) {
+        try {
+            // 1. 参数校验
+            if (replyDto.getFeedbackId() == null) {
+                return R.fail("反馈ID不能为空");
+            }
+            if (replyDto.getContent() == null || replyDto.getContent().trim().isEmpty()) {
+                return R.fail("回复内容不能为空");
+            }
+
+            // 2. 查询原始反馈
+            LifeFeedback feedback = lifeFeedbackMapper.selectById(replyDto.getFeedbackId());
+            if (feedback == null) {
+                return R.fail("反馈记录不存在");
+            }
+
+            // 3. 保存平台回复到life_feedback_reply表
+            LifeFeedbackReply platformReply = new LifeFeedbackReply();
+            platformReply.setFeedbackId(replyDto.getFeedbackId());
+            platformReply.setReplyType(0); // 0-平台回复
+            platformReply.setReplyContent(replyDto.getContent());
+            platformReply.setCreateTime(new Date());
+            platformReply.setUpdateTime(new Date());
+            
+            boolean saveResult = lifeFeedbackReplyService.save(platformReply);
+            if (!saveResult) {
+                return R.fail("保存回复失败");
+            }
+
+            // 4. 记录回复日志(类型3-回复用户)
+            String logContent = replyDto.getContent();
+            if (replyDto.getUserReply() != null && !replyDto.getUserReply().trim().isEmpty()) {
+                logContent = replyDto.getContent() + "||用户回复:" + replyDto.getUserReply();
+            }
+            saveFeedbackLog(replyDto.getFeedbackId(), 3, logContent);
+
+            // 5. 发送通知给用户
+            sendFeedbackReplyNotice(feedback, replyDto.getContent());
+
+            return R.success("回复成功");
+        } catch (Exception e) {
+            log.error("中台-回复用户失败", e);
+            return R.fail("回复失败:" + e.getMessage());
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R<String> updateWebFeedbackStatus(LifeFeedbackStatusDto statusDto) {
+        try {
+            // 1. 参数校验
+            if (statusDto.getFeedbackId() == null) {
+                return R.fail("反馈ID不能为空");
+            }
+
+            // 2. 更新状态为已解决
+            LifeFeedback updateFeedback = new LifeFeedback();
+            updateFeedback.setId(statusDto.getFeedbackId());
+            updateFeedback.setHandleStatus(1); // 已解决
+            updateFeedback.setUpdateTime(new Date());
+
+            boolean result = this.updateById(updateFeedback);
+            if (!result) {
+                return R.fail("更新失败");
+            }
+
+            // 3. 记录日志(类型0-问题解决状态)
+            String logContent = "问题已解决";
+            saveFeedbackLog(statusDto.getFeedbackId(), 0, logContent);
+
+            return R.success("更新成功");
+        } catch (Exception e) {
+            log.error("中台-更新反馈状态失败", e);
+            return R.fail("更新失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 保存反馈操作日志
+     * @param feedbackId 反馈ID
+     * @param type 操作类型:0-问题解决状态,1-分配跟踪人员,2-创建反馈工单,3-回复用户
+     * @param context 日志内容
+     */
+    private void saveFeedbackLog(Integer feedbackId, Integer type, String context) {
+        try {
+            LifeLog lifeLog = new LifeLog();
+            lifeLog.setFeedbackId(feedbackId);
+            lifeLog.setType(String.valueOf(type));
+            lifeLog.setContext(context);
+            lifeLog.setCreatedTime(new Date());
+            lifeLog.setDeleteFlag(0);
+            lifeLogMapper.insert(lifeLog);
+        } catch (Exception e) {
+            log.error("保存反馈日志失败", e);
+        }
+    }
+
+    /**
+     * 发送平台回复通知给用户
+     * @param feedback 反馈记录
+     * @param replyContent 回复内容
+     */
+    private void sendFeedbackReplyNotice(LifeFeedback feedback, String replyContent) {
+        try {
+            String receiverId = null;
+            
+            // 根据反馈来源判断是用户端还是商家端
+            if (feedback.getFeedbackSource() == null) {
+                log.warn("反馈来源为空,无法发送通知,feedbackId={}", feedback.getId());
+                return;
+            }
+            
+            if (feedback.getFeedbackSource() == 0) {
+                // 用户端 - 使用user_手机号格式
+                if (feedback.getUserId() == null) {
+                    log.warn("用户ID为空,无法发送通知,feedbackId={}", feedback.getId());
+                    return;
+                }
+                LifeUser lifeUser = lifeUserMapper.selectById(feedback.getUserId());
+                if (lifeUser == null || lifeUser.getUserPhone() == null || lifeUser.getUserPhone().trim().isEmpty()) {
+                    log.warn("未找到用户信息或手机号为空,无法发送通知,userId={}", feedback.getUserId());
+                    return;
+                }
+                receiverId = "user_" + lifeUser.getUserPhone();
+            } else if (feedback.getFeedbackSource() == 1) {
+                // 商家端 - 使用store_手机号格式
+                if (feedback.getUserId() == null) {
+                    log.warn("商家用户ID为空,无法发送通知,feedbackId={}", feedback.getId());
+                    return;
+                }
+                StoreUser storeUser = storeUserMapper.selectById(feedback.getUserId());
+                if (storeUser == null || storeUser.getPhone() == null || storeUser.getPhone().trim().isEmpty()) {
+                    log.warn("未找到商家用户信息或手机号为空,无法发送通知,userId={}", feedback.getUserId());
+                    return;
+                }
+                receiverId = "store_" + storeUser.getPhone();
+            } else {
+                log.warn("未知的反馈来源,feedbackSource={}, feedbackId={}", feedback.getFeedbackSource(), feedback.getId());
+                return;
+            }
+            
+            // 构建通知消息
+            JSONObject messageJson = new JSONObject();
+            messageJson.put("feedbackId", feedback.getId()); // 添加反馈ID用于区分
+            messageJson.put("message", "平台已回复您的意见反馈:" + replyContent);
+
+            // 创建通知记录
+            LifeNotice lifeNotice = new LifeNotice();
+            lifeNotice.setReceiverId(receiverId);
+            lifeNotice.setContext(messageJson.toJSONString());
+            lifeNotice.setTitle("意见反馈回复通知");
+            lifeNotice.setSenderId("system");
+            lifeNotice.setIsRead(0);
+            lifeNotice.setNoticeType(1); // 1-系统通知
+            lifeNotice.setBusinessId(feedback.getId());
+            
+            // 保存通知
+            lifeNoticeMapper.insert(lifeNotice);
+            
+            // 通过WebSocket发送实时通知
+            WebSocketVo webSocketVo = new WebSocketVo();
+            webSocketVo.setSenderId("system");
+            webSocketVo.setReceiverId(receiverId);
+            webSocketVo.setCategory("notice");
+            webSocketVo.setNoticeType("1");
+            webSocketVo.setIsRead(0);
+            webSocketVo.setText(JSONObject.toJSONString(lifeNotice));
+            
+            try {
+                webSocketProcess.sendMessage(receiverId, JSONObject.toJSONString(webSocketVo));
+                log.info("平台回复通知发送成功,feedbackId={}, receiverId={}", feedback.getId(), receiverId);
+            } catch (Exception e) {
+                log.error("发送WebSocket通知失败,feedbackId={}, receiverId={}, error={}", 
+                        feedback.getId(), receiverId, e.getMessage());
+            }
+            
+        } catch (Exception e) {
+            log.error("发送平台回复通知异常,feedbackId={}, error={}", feedback.getId(), e.getMessage(), e);
+        }
+    }
+}
+

+ 56 - 0
alien-store/src/main/java/shop/alien/store/service/impl/LifeImgServiceImpl.java

@@ -0,0 +1,56 @@
+package shop.alien.store.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import shop.alien.entity.store.LifeImg;
+import shop.alien.mapper.LifeImgMapper;
+import shop.alien.store.service.LifeImgService;
+
+import java.util.List;
+
+/**
+ * 反馈图片 Service实现类
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class LifeImgServiceImpl extends ServiceImpl<LifeImgMapper, LifeImg> implements LifeImgService {
+
+    private final LifeImgMapper lifeImgMapper;
+
+    @Override
+    public List<LifeImg> getByFeedbackId(Integer feedbackId) {
+        return lifeImgMapper.selectByFeedbackId(feedbackId);
+    }
+
+    @Override
+    public boolean batchSave(List<LifeImg> imgList) {
+        if (imgList == null || imgList.isEmpty()) {
+            return false;
+        }
+        // 确保每个附件都有上传时间
+        imgList.forEach(item -> {
+            if (item.getUploadTime() == null) {
+                item.setUploadTime(new java.util.Date());
+            }
+        });
+        return lifeImgMapper.batchInsert(imgList) > 0;
+    }
+
+    @Override
+    public boolean removeByFeedbackId(Integer feedbackId) {
+        return lifeImgMapper.deleteByFeedbackId(feedbackId) > 0;
+    }
+
+    @Override
+    public List<String> getImgUrlsByFeedbackId(Integer feedbackId) {
+        return lifeImgMapper.selectImgUrlsByFeedbackId(feedbackId);
+    }
+
+    @Override
+    public List<String> getVideoUrlsByFeedbackId(Integer feedbackId) {
+        return lifeImgMapper.selectVideoUrlsByFeedbackId(feedbackId);
+    }
+}