Ver código fonte

意见反馈模块功能开发(用商家端)1.0

panzhilin 5 dias atrás
pai
commit
229cb791a9
21 arquivos alterados com 648 adições e 273 exclusões
  1. 11 24
      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. 21 17
      alien-entity/src/main/java/shop/alien/entity/store/LifeImg.java
  4. 8 0
      alien-entity/src/main/java/shop/alien/entity/store/LifeLog.java
  5. 5 5
      alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackDto.java
  6. 33 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/UserReplyDto.java
  7. 23 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/FeedbackTypeVo.java
  8. 13 4
      alien-entity/src/main/java/shop/alien/entity/store/vo/LifeFeedbackVo.java
  9. 0 15
      alien-entity/src/main/java/shop/alien/mapper/LifeFeedbackMapper.java
  10. 23 0
      alien-entity/src/main/java/shop/alien/mapper/LifeFeedbackReplyMapper.java
  11. 7 0
      alien-entity/src/main/java/shop/alien/mapper/LifeImgMapper.java
  12. 18 41
      alien-entity/src/main/resources/mapper/LifeFeedbackMapper.xml
  13. 32 0
      alien-entity/src/main/resources/mapper/LifeFeedbackReplyMapper.xml
  14. 38 19
      alien-entity/src/main/resources/mapper/LifeImgMapper.xml
  15. 7 23
      alien-store/src/main/java/shop/alien/store/controller/LifeFeedbackController.java
  16. 20 0
      alien-store/src/main/java/shop/alien/store/service/LifeFeedbackReplyService.java
  17. 5 14
      alien-store/src/main/java/shop/alien/store/service/LifeFeedbackService.java
  18. 7 1
      alien-store/src/main/java/shop/alien/store/service/LifeImgService.java
  19. 28 0
      alien-store/src/main/java/shop/alien/store/service/impl/LifeFeedbackReplyServiceImpl.java
  20. 289 109
      alien-store/src/main/java/shop/alien/store/service/impl/LifeFeedbackServiceImpl.java
  21. 11 1
      alien-store/src/main/java/shop/alien/store/service/impl/LifeImgServiceImpl.java

+ 11 - 24
alien-entity/src/main/java/shop/alien/entity/store/LifeFeedback.java

@@ -28,15 +28,15 @@ public class LifeFeedback implements Serializable {
     @TableField("user_id")
     private Integer userId;
 
-    @ApiModelProperty(value = "反馈来源:1-用户端,2-商家端")
+    @ApiModelProperty(value = "反馈来源:0-用户端,1-商家端")
     @TableField("feedback_source")
     private Integer feedbackSource;
 
-    @ApiModelProperty(value = "反馈方式:1-主动反馈,2-平台回复")
+    @ApiModelProperty(value = "反馈方式:0-用户反馈,1-AI识别")
     @TableField("feedback_way")
     private Integer feedbackWay;
 
-    @ApiModelProperty(value = "反馈类型:1-优化建议,2-问题")
+    @ApiModelProperty(value = "反馈类型:0-bug反馈,1-优化反馈,2-新增功能反馈")
     @TableField("feedback_type")
     private Integer feedbackType;
 
@@ -53,35 +53,22 @@ public class LifeFeedback implements Serializable {
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private Date feedbackTime;
 
-    @ApiModelProperty(value = "跟进工作人员ID(关联life_sys)")
-    @TableField("follow_up_staff")
-    private Integer followUpStaff;
+    @ApiModelProperty(value = "工作人员ID(关联life_sys)")
+    @TableField("staff_id")
+    private Integer staffId;
 
-    @ApiModelProperty(value = "处理状态:0-待处理,1-处理中,2-已完成")
+    @ApiModelProperty(value = "处理状态:0-处理中,1-已解决,2-未分配,3-无需解决")
     @TableField("handle_status")
     private Integer handleStatus;
 
-    @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
-    @TableField("delete_flag")
-    @TableLogic
-    private Integer deleteFlag;
-
     @ApiModelProperty(value = "创建时间")
-    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    @TableField(value = "create_time", fill = FieldFill.INSERT)
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private Date createdTime;
-
-    @ApiModelProperty(value = "创建人ID")
-    @TableField("created_user_id")
-    private Integer createdUserId;
+    private Date createTime;
 
     @ApiModelProperty(value = "修改时间")
-    @TableField(value = "updated_time", fill = FieldFill.UPDATE)
+    @TableField(value = "update_time", fill = FieldFill.UPDATE)
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private Date updatedTime;
-
-    @ApiModelProperty(value = "修改人ID")
-    @TableField("updated_user_id")
-    private Integer updatedUserId;
+    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;
+}
+

+ 21 - 17
alien-entity/src/main/java/shop/alien/entity/store/LifeImg.java

@@ -11,12 +11,12 @@ import java.io.Serializable;
 import java.util.Date;
 
 /**
- * 反馈图片表
+ * 反馈附件表(图片和视频)
  */
 @Data
 @JsonInclude
 @TableName("life_img")
-@ApiModel(value = "LifeImg对象", description = "反馈图片")
+@ApiModel(value = "LifeImg对象", description = "反馈附件(图片和视频)")
 public class LifeImg implements Serializable {
     private static final long serialVersionUID = 1L;
 
@@ -28,33 +28,37 @@ public class LifeImg implements Serializable {
     @TableField("feedback_id")
     private Integer feedbackId;
 
-    @ApiModelProperty(value = "图片URL")
+    @ApiModelProperty(value = "文件URL(图片或视频)")
     @TableField("img_url")
     private String imgUrl;
 
-    @ApiModelProperty(value = "图片排序")
-    @TableField("img_sort")
-    private Integer imgSort;
+    @ApiModelProperty(value = "缩略图URL(视频的封面图)")
+    @TableField("thumbnail_url")
+    private String thumbnailUrl;
 
-    @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
-    @TableField("delete_flag")
-    @TableLogic
-    private Integer deleteFlag;
+    @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 = "created_time", fill = FieldFill.INSERT)
+    @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 createdTime;
+    private Date updateTime;
 
     @ApiModelProperty(value = "创建人ID")
     @TableField("created_user_id")
     private Integer createdUserId;
 
-    @ApiModelProperty(value = "修改时间")
-    @TableField(value = "updated_time", fill = FieldFill.UPDATE)
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private Date updatedTime;
-
     @ApiModelProperty(value = "修改人ID")
     @TableField("updated_user_id")
     private Integer updatedUserId;

+ 8 - 0
alien-entity/src/main/java/shop/alien/entity/store/LifeLog.java

@@ -19,8 +19,16 @@ public class LifeLog {
     @TableId(value = "id", type = IdType.AUTO)
     private String id;
 
+    @ApiModelProperty(value = "反馈ID(关联life_feedback)")
+    @TableField("feedback_id")
+    private Integer feedbackId;
+
     private String context;
 
+    @ApiModelProperty(value = "操作类型:0-创建反馈工单,1-分配跟踪人员,2-回复用户,3-问题解决状态")
+    @TableField("type")
+    private String type;
+
     @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
     @TableField("delete_flag")
     @TableLogic

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

@@ -18,13 +18,13 @@ public class LifeFeedbackDto implements Serializable {
     @ApiModelProperty(value = "用户ID")
     private Integer userId;
 
-    @ApiModelProperty(value = "反馈来源:1-用户端,2-商家端")
+    @ApiModelProperty(value = "反馈来源:0-用户端,1-商家端")
     private Integer feedbackSource;
 
-    @ApiModelProperty(value = "反馈方式:1-主动反馈,2-平台回复")
+    @ApiModelProperty(value = "反馈方式:0-用户反馈,1-AI识别")
     private Integer feedbackWay;
 
-    @ApiModelProperty(value = "反馈类型:1-优化建议,2-问题")
+    @ApiModelProperty(value = "反馈类型:0-bug反馈,1-优化反馈,2-新增功能反馈")
     private Integer feedbackType;
 
     @ApiModelProperty(value = "反馈内容")
@@ -33,7 +33,7 @@ public class LifeFeedbackDto implements Serializable {
     @ApiModelProperty(value = "联系方式(手机号或邮箱)")
     private String contactWay;
 
-    @ApiModelProperty(value = "图片URL列表")
-    private List<String> imgUrlList;
+    @ApiModelProperty(value = "文件URL列表(图片和视频,系统会自动识别类型。视频会自动匹配封面图)")
+    private List<String> fileUrlList;
 }
 

+ 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;
+}
+

+ 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;
+}
+

+ 13 - 4
alien-entity/src/main/java/shop/alien/entity/store/vo/LifeFeedbackVo.java

@@ -23,15 +23,18 @@ public class LifeFeedbackVo implements Serializable {
     @ApiModelProperty(value = "用户ID")
     private Integer userId;
 
-    @ApiModelProperty(value = "反馈来源:1-用户端,2-商家端")
+    @ApiModelProperty(value = "反馈来源:0-用户端,1-商家端")
     private Integer feedbackSource;
 
-    @ApiModelProperty(value = "反馈方式:1-主动反馈,2-平台回复")
+    @ApiModelProperty(value = "反馈方式:0-用户反馈,1-AI识别")
     private Integer feedbackWay;
 
-    @ApiModelProperty(value = "反馈类型:1-优化建议,2-问题")
+    @ApiModelProperty(value = "反馈类型:0-bug反馈,1-优化反馈,2-新增功能反馈")
     private Integer feedbackType;
 
+    @ApiModelProperty(value = "反馈类型名称")
+    private String feedbackTypeName;
+
     @ApiModelProperty(value = "反馈内容")
     private String content;
 
@@ -42,15 +45,21 @@ public class LifeFeedbackVo implements Serializable {
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private Date feedbackTime;
 
-    @ApiModelProperty(value = "处理状态:0-待处理,1-处理中,2-已完成")
+    @ApiModelProperty(value = "处理状态:0-处理中,1-已解决,2-未分配,3-无需解决")
     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;
 }

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

@@ -8,9 +8,6 @@ import org.apache.ibatis.annotations.Param;
 import shop.alien.entity.store.LifeFeedback;
 import shop.alien.entity.store.vo.LifeFeedbackVo;
 
-import java.util.Date;
-import java.util.List;
-
 /**
  * 意见反馈 Mapper 接口
  */
@@ -48,17 +45,5 @@ public interface LifeFeedbackMapper extends BaseMapper<LifeFeedback> {
      */
     Integer countPendingFeedback(@Param("feedbackSource") Integer feedbackSource);
 
-    /**
-     * 查询平台回复列表
-     * @param userId 用户ID
-     * @param feedbackSource 反馈来源
-     * @param startTime 开始时间
-     * @return 平台回复列表
-     */
-    List<LifeFeedbackVo> selectPlatformReplies(
-            @Param("userId") Integer userId,
-            @Param("feedbackSource") Integer feedbackSource,
-            @Param("startTime") Date startTime
-    );
 }
 

+ 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);
+}
+

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

@@ -40,5 +40,12 @@ public interface LifeImgMapper extends BaseMapper<LifeImg> {
      * @return 图片URL列表
      */
     List<String> selectImgUrlsByFeedbackId(@Param("feedbackId") Integer feedbackId);
+
+    /**
+     * 查询反馈的视频URL列表
+     * @param feedbackId 反馈ID
+     * @return 视频URL列表
+     */
+    List<String> selectVideoUrlsByFeedbackId(@Param("feedbackId") Integer feedbackId);
 }
 

+ 18 - 41
alien-entity/src/main/resources/mapper/LifeFeedbackMapper.xml

@@ -14,23 +14,21 @@
         <result column="content" property="content" />
         <result column="contact_way" property="contactWay" />
         <result column="feedback_time" property="feedbackTime" />
-        <result column="follow_up_staff" property="followUpStaff" />
+        <result column="staff_id" property="staffId" />
         <result column="handle_status" property="handleStatus" />
-        <result column="delete_flag" property="deleteFlag" />
-        <result column="created_time" property="createdTime" />
-        <result column="created_user_id" property="createdUserId" />
-        <result column="updated_time" property="updatedTime" />
-        <result column="updated_user_id" property="updatedUserId" />
+        <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, follow_up_staff, handle_status,
-        delete_flag, created_time, created_user_id, updated_time, updated_user_id
+        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,
@@ -41,12 +39,12 @@
             f.content,
             f.contact_way AS contactWay,
             f.feedback_time AS feedbackTime,
-            f.follow_up_staff AS followUpStaff,
+            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.follow_up_staff = s.id
-        WHERE f.delete_flag = 0
+        LEFT JOIN life_sys s ON f.staff_id = s.id
+        WHERE 1=1
         <if test="userId != null">
             AND f.user_id = #{userId}
         </if>
@@ -59,10 +57,13 @@
         <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,
@@ -73,47 +74,23 @@
             f.content,
             f.contact_way AS contactWay,
             f.feedback_time AS feedbackTime,
-            f.follow_up_staff AS followUpStaff,
+            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.follow_up_staff = s.id
-        WHERE f.id = #{feedbackId} AND f.delete_flag = 0
+        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 delete_flag = 0
-        AND handle_status = 0
+        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.follow_up_staff AS followUpStaff,
-            f.handle_status AS handleStatus,
-            s.user_name AS staffName
-        FROM life_feedback f
-        LEFT JOIN life_sys s ON f.follow_up_staff = s.id
-        WHERE f.user_id = #{userId}
-        AND f.feedback_source = #{feedbackSource}
-        AND f.feedback_way = 2
-        AND f.feedback_time >= #{startTime}
-        AND f.delete_flag = 0
-        ORDER BY f.feedback_time ASC
-    </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>
+

+ 38 - 19
alien-entity/src/main/resources/mapper/LifeImgMapper.xml

@@ -9,18 +9,19 @@
         <id column="id" property="id" />
         <result column="feedback_id" property="feedbackId" />
         <result column="img_url" property="imgUrl" />
-        <result column="img_sort" property="imgSort" />
-        <result column="delete_flag" property="deleteFlag" />
-        <result column="created_time" property="createdTime" />
+        <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_time" property="updatedTime" />
         <result column="updated_user_id" property="updatedUserId" />
     </resultMap>
 
     <!-- 基础字段 -->
     <sql id="Base_Column_List">
-        id, feedback_id, img_url, img_sort, 
-        delete_flag, created_time, created_user_id, updated_time, updated_user_id
+        id, feedback_id, img_url, thumbnail_url, file_type, upload_time, 
+        create_time, update_time, created_user_id, updated_user_id
     </sql>
 
     <!-- 根据反馈ID查询图片列表 -->
@@ -29,32 +30,32 @@
             <include refid="Base_Column_List" />
         FROM life_img
         WHERE feedback_id = #{feedbackId}
-        AND delete_flag = 0
-        ORDER BY img_sort ASC
     </select>
 
-    <!-- 批量插入图片 -->
+    <!-- 批量插入附件(图片和视频) -->
     <insert id="batchInsert" parameterType="java.util.List">
         INSERT INTO life_img (
-            feedback_id, img_url, img_sort, 
-            delete_flag, created_time, created_user_id
+            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.imgSort},
-                0,
+                #{item.thumbnailUrl},
+                #{item.fileType},
+                #{item.uploadTime},
                 NOW(),
-                #{item.createdUserId}
+                NOW(),
+                #{item.createdUserId},
+                #{item.updatedUserId}
             )
         </foreach>
     </insert>
 
     <!-- 根据反馈ID删除图片 -->
     <delete id="deleteByFeedbackId">
-        UPDATE life_img 
-        SET delete_flag = 1
+        DELETE FROM life_img 
         WHERE feedback_id = #{feedbackId}
     </delete>
 
@@ -63,9 +64,27 @@
         SELECT img_url
         FROM life_img
         WHERE feedback_id = #{feedbackId}
-        AND delete_flag = 0
-        ORDER BY img_sort ASC
+        AND file_type = 1
+        ORDER BY upload_time ASC
     </select>
 
-</mapper>
+    <!-- 查询反馈的视频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>

+ 7 - 23
alien-store/src/main/java/shop/alien/store/controller/LifeFeedbackController.java

@@ -6,8 +6,8 @@ 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.FeedbackReplyDto;
 import shop.alien.entity.store.dto.LifeFeedbackDto;
+import shop.alien.entity.store.dto.UserReplyDto;
 import shop.alien.entity.store.vo.LifeFeedbackVo;
 import shop.alien.store.service.LifeFeedbackService;
 
@@ -31,17 +31,10 @@ public class LifeFeedbackController {
         return lifeFeedbackService.submitFeedback(dto);
     }
 
-    @ApiOperation(value = "平台回复反馈", httpMethod = "POST")
-    @PostMapping("/reply")
-    public R<String> replyFeedback(@RequestBody FeedbackReplyDto dto) {
-        log.info("LifeFeedbackController.replyFeedback, dto={}", dto);
-        return lifeFeedbackService.replyFeedback(dto);
-    }
-
     @ApiOperation(value = "查询用户历史反馈列表", httpMethod = "GET")
     @ApiImplicitParams({
             @ApiImplicitParam(name = "userId", value = "用户ID", dataType = "Integer", paramType = "query", required = true),
-            @ApiImplicitParam(name = "feedbackSource", value = "反馈来源:1-用户端,2-商家端", 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)
     })
@@ -67,20 +60,11 @@ public class LifeFeedbackController {
         return lifeFeedbackService.getFeedbackDetail(feedbackId);
     }
 
-    @ApiOperation(value = "更新反馈处理状态", httpMethod = "POST")
-    @ApiImplicitParams({
-            @ApiImplicitParam(name = "feedbackId", value = "反馈ID", dataType = "Integer", paramType = "query", required = true),
-            @ApiImplicitParam(name = "handleStatus", value = "处理状态:0-待处理,1-处理中,2-已完成", dataType = "Integer", paramType = "query", required = true),
-            @ApiImplicitParam(name = "staffId", value = "跟进工作人员ID", dataType = "Integer", paramType = "query", required = false)
-    })
-    @PostMapping("/updateStatus")
-    public R<String> updateHandleStatus(
-            @RequestParam("feedbackId") Integer feedbackId,
-            @RequestParam("handleStatus") Integer handleStatus,
-            @RequestParam(value = "staffId", required = false) Integer staffId) {
-        log.info("LifeFeedbackController.updateHandleStatus, feedbackId={}, handleStatus={}, staffId={}", 
-                feedbackId, handleStatus, staffId);
-        return lifeFeedbackService.updateHandleStatus(feedbackId, handleStatus, staffId);
+    @ApiOperation(value = "用户回复", httpMethod = "POST")
+    @PostMapping("/userReply")
+    public R<String> userReply(@RequestBody UserReplyDto dto) {
+        log.info("LifeFeedbackController.userReply, dto={}", dto);
+        return lifeFeedbackService.userReply(dto);
     }
 }
 

+ 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);
+}
+

+ 5 - 14
alien-store/src/main/java/shop/alien/store/service/LifeFeedbackService.java

@@ -4,8 +4,8 @@ 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.FeedbackReplyDto;
 import shop.alien.entity.store.dto.LifeFeedbackDto;
+import shop.alien.entity.store.dto.UserReplyDto;
 import shop.alien.entity.store.vo.LifeFeedbackVo;
 
 /**
@@ -21,13 +21,6 @@ public interface LifeFeedbackService extends IService<LifeFeedback> {
     R<String> submitFeedback(LifeFeedbackDto dto);
 
     /**
-     * 平台回复反馈
-     * @param dto 回复信息
-     * @return 回复结果
-     */
-    R<String> replyFeedback(FeedbackReplyDto dto);
-
-    /**
      * 查询用户历史反馈列表
      * @param userId 用户ID
      * @param feedbackSource 反馈来源
@@ -45,12 +38,10 @@ public interface LifeFeedbackService extends IService<LifeFeedback> {
     R<LifeFeedbackVo> getFeedbackDetail(Integer feedbackId);
 
     /**
-     * 更新反馈处理状态
-     * @param feedbackId 反馈ID
-     * @param handleStatus 处理状态
-     * @param staffId 跟进人员ID
-     * @return 更新结果
+     * 用户回复
+     * @param dto 用户回复信息
+     * @return 回复结果
      */
-    R<String> updateHandleStatus(Integer feedbackId, Integer handleStatus, Integer staffId);
+    R<String> userReply(UserReplyDto dto);
 }
 

+ 7 - 1
alien-store/src/main/java/shop/alien/store/service/LifeImgService.java

@@ -37,5 +37,11 @@ public interface LifeImgService extends IService<LifeImg> {
      * @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);
+    }
+}
+

+ 289 - 109
alien-store/src/main/java/shop/alien/store/service/impl/LifeFeedbackServiceImpl.java

@@ -8,21 +8,24 @@ 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.FeedbackReplyDto;
 import shop.alien.entity.store.dto.LifeFeedbackDto;
+import shop.alien.entity.store.dto.UserReplyDto;
 import shop.alien.entity.store.vo.LifeFeedbackVo;
 import shop.alien.mapper.LifeFeedbackMapper;
 import shop.alien.mapper.LifeLogMapper;
 import shop.alien.store.service.LifeFeedbackService;
+import shop.alien.store.service.LifeFeedbackReplyService;
 import shop.alien.store.service.LifeImgService;
+import org.springframework.util.CollectionUtils;
 
 import java.util.Date;
 import java.util.List;
+import java.util.ArrayList;
 
 /**
  * 意见反馈 Service实现类
@@ -36,6 +39,7 @@ public class LifeFeedbackServiceImpl extends ServiceImpl<LifeFeedbackMapper, Lif
     private final LifeFeedbackMapper lifeFeedbackMapper;
     private final LifeImgService lifeImgService;
     private final LifeLogMapper lifeLogMapper;
+    private final LifeFeedbackReplyService lifeFeedbackReplyService;
 
     @Override
     public R<String> submitFeedback(LifeFeedbackDto dto) {
@@ -57,34 +61,89 @@ public class LifeFeedbackServiceImpl extends ServiceImpl<LifeFeedbackMapper, Lif
             // 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.setCreatedTime(new Date());
-            feedback.setCreatedUserId(dto.getUserId());
+            feedback.setHandleStatus(0); // 处理中
+            feedback.setCreateTime(new Date());
 
             boolean saveResult = this.save(feedback);
             if (!saveResult) {
                 return R.fail("提交反馈失败");
             }
 
-            // 3. 保存附件图片(使用批量插入)
-            if (!CollectionUtils.isEmpty(dto.getImgUrlList())) {
-                List<LifeImg> imgList = new java.util.ArrayList<>();
-                int sort = 1;
-                for (String imgUrl : dto.getImgUrlList()) {
-                    LifeImg img = new LifeImg();
-                    img.setFeedbackId(feedback.getId());
-                    img.setImgUrl(imgUrl);
-                    img.setImgSort(sort++);
-                    img.setCreatedTime(new Date());
-                    img.setCreatedUserId(dto.getUserId());
-                    imgList.add(img);
+            // 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);
+                    }
                 }
-                lifeImgService.batchSave(imgList);
+                
+                // 处理视频:自动匹配封面图
+                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("用户提交反馈,ID:" + feedback.getId());
+            // 4. 记录日志(只记录详细内容)
+            saveLog(feedback.getId(), feedback.getContent(), "0");
 
             return R.success("提交成功");
         } catch (Exception e) {
@@ -94,75 +153,23 @@ public class LifeFeedbackServiceImpl extends ServiceImpl<LifeFeedbackMapper, Lif
     }
 
     @Override
-    public R<String> replyFeedback(FeedbackReplyDto dto) {
-        try {
-            // 1. 参数校验
-            if (dto.getFeedbackId() == null) {
-                return R.fail("反馈ID不能为空");
-            }
-            if (dto.getStaffId() == 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. 创建平台回复记录(使用MyBatis Plus的save方法)
-            LifeFeedback reply = new LifeFeedback();
-            reply.setUserId(originalFeedback.getUserId());
-            reply.setFeedbackSource(originalFeedback.getFeedbackSource());
-            reply.setFeedbackWay(2); // 平台回复
-            reply.setFeedbackType(originalFeedback.getFeedbackType());
-            reply.setContent(dto.getContent());
-            reply.setFeedbackTime(new Date());
-            reply.setFollowUpStaff(dto.getStaffId());
-            reply.setHandleStatus(2); // 已完成
-            reply.setCreatedTime(new Date());
-            reply.setCreatedUserId(dto.getStaffId());
-
-            boolean saveResult = this.save(reply);
-            if (!saveResult) {
-                return R.fail("回复失败");
-            }
-
-            // 4. 更新原始反馈的处理状态和跟进人员(使用MyBatis Plus的updateById方法)
-            LifeFeedback updateFeedback = new LifeFeedback();
-            updateFeedback.setId(dto.getFeedbackId());
-            updateFeedback.setHandleStatus(1); // 处理中
-            updateFeedback.setFollowUpStaff(dto.getStaffId());
-            updateFeedback.setUpdatedTime(new Date());
-            updateFeedback.setUpdatedUserId(dto.getStaffId());
-            this.updateById(updateFeedback);
-
-            // 5. 记录日志
-            saveLog("平台回复反馈,原始反馈ID:" + dto.getFeedbackId() + ",回复ID:" + reply.getId());
-
-            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, 1, null
+                    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;
@@ -181,18 +188,102 @@ public class LifeFeedbackServiceImpl extends ServiceImpl<LifeFeedbackMapper, Lif
                 return R.fail("反馈记录不存在");
             }
 
-            // 2. 查询附件图片
-            List<String> imgUrls = lifeImgService.getImgUrlsByFeedbackId(feedbackId);
+            // 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. 查询平台回复列表(如果是主动反馈)
-            if (vo.getFeedbackWay() == 1) {
-                List<LifeFeedbackVo> replyList = lifeFeedbackMapper.selectPlatformReplies(
-                        vo.getUserId(), vo.getFeedbackSource(), vo.getFeedbackTime()
-                );
-                vo.setPlatformReplies(replyList);
+            // 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);
@@ -201,40 +292,129 @@ public class LifeFeedbackServiceImpl extends ServiceImpl<LifeFeedbackMapper, Lif
     }
 
     @Override
-    public R<String> updateHandleStatus(Integer feedbackId, Integer handleStatus, Integer staffId) {
+    public R<String> userReply(UserReplyDto dto) {
         try {
-            // 使用MyBatis Plus的updateById方法
-            LifeFeedback feedback = new LifeFeedback();
-            feedback.setId(feedbackId);
-            feedback.setHandleStatus(handleStatus);
-            feedback.setFollowUpStaff(staffId);
-            feedback.setUpdatedTime(new Date());
-            feedback.setUpdatedUserId(staffId);
-
-            boolean result = this.updateById(feedback);
-            if (result) {
-                saveLog("更新反馈处理状态,反馈ID:" + feedbackId + ",状态:" + handleStatus);
-                return R.success("更新成功");
-            }
-            return R.fail("更新失败");
+            // 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());
+            log.error("用户回复失败", e);
+            return R.fail("用户回复失败:" + e.getMessage());
         }
     }
 
     /**
      * 保存操作日志
+     * @param feedbackId 反馈ID
+     * @param context 日志内容
+     * @param type 操作类型:0-创建反馈工单,1-分配跟踪人员,2-回复用户,3-问题解决状态
      */
-    private void saveLog(String context) {
+    private void saveLog(Integer feedbackId, String context, String type) {
         try {
-            LifeLog log = new LifeLog();
-            log.setContext(context);
-            log.setCreatedTime(new Date());
-            lifeLogMapper.insert(log);
+            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");
+    }
+
 }
 

+ 11 - 1
alien-store/src/main/java/shop/alien/store/service/impl/LifeImgServiceImpl.java

@@ -30,6 +30,12 @@ public class LifeImgServiceImpl extends ServiceImpl<LifeImgMapper, LifeImg> impl
         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;
     }
 
@@ -42,5 +48,9 @@ public class LifeImgServiceImpl extends ServiceImpl<LifeImgMapper, LifeImg> impl
     public List<String> getImgUrlsByFeedbackId(Integer feedbackId) {
         return lifeImgMapper.selectImgUrlsByFeedbackId(feedbackId);
     }
-}
 
+    @Override
+    public List<String> getVideoUrlsByFeedbackId(Integer feedbackId) {
+        return lifeImgMapper.selectVideoUrlsByFeedbackId(feedbackId);
+    }
+}