Преглед на файлове

feat:中台意见反馈回复及通知

penghao преди 5 дни
родител
ревизия
f359940e97

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

@@ -28,7 +28,7 @@ public class LifeFeedbackReply implements Serializable {
     @TableField("feedback_id")
     private Integer feedbackId;
 
-    @ApiModelProperty(value = "回复类型:0-平台回复,1-我的回复")
+    @ApiModelProperty(value = "回复类型:0-平台回复,1-用户回复")
     @TableField("reply_type")
     private Integer replyType;
 

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

@@ -23,7 +23,7 @@ public class LifeFeedbackReplyWebDto implements Serializable {
     @ApiModelProperty(value = "操作人员ID")
     private Integer operatorId;
 
-    @ApiModelProperty(value = "用户回复内容")
+    @ApiModelProperty(value = "用户回复内容")
     private String userReply;
 }
 

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

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

@@ -71,5 +71,8 @@ public class LifeFeedbackDetailVo implements Serializable {
 
     @ApiModelProperty(value = "操作日志列表")
     private List<FeedbackLogVo> logs;
+
+    @ApiModelProperty(value = "回复列表(平台回复和用户回复)")
+    private List<FeedbackReplyVo> replies;
 }
 

+ 2 - 6
alien-entity/src/main/resources/mapper/LifeFeedbackMapper.xml

@@ -122,11 +122,9 @@
             CASE f.handle_status
                 WHEN 0 THEN '处理中'
                 WHEN 1 THEN '已解决'
-                WHEN 2 THEN '未分配'
-                WHEN 3 THEN '无需解决'
             END AS handleStatusName
         FROM life_feedback f
-        LEFT JOIN life_user u ON f.user_id = u.id
+        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">
@@ -175,11 +173,9 @@
             CASE f.handle_status
                 WHEN 0 THEN '处理中'
                 WHEN 1 THEN '已解决'
-                WHEN 2 THEN '未分配'
-                WHEN 3 THEN '无需解决'
             END AS handleStatusName
         FROM life_feedback f
-        LEFT JOIN life_user u ON f.user_id = u.id
+        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>

+ 158 - 2
alien-store/src/main/java/shop/alien/store/service/impl/LifeFeedbackServiceImpl.java

@@ -8,6 +8,7 @@ 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;
@@ -15,12 +16,21 @@ 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 org.springframework.util.CollectionUtils;
+import com.alibaba.fastjson2.JSONObject;
 
 import java.util.Date;
 import java.util.List;
@@ -39,6 +49,10 @@ public class LifeFeedbackServiceImpl extends ServiceImpl<LifeFeedbackMapper, Lif
     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) {
@@ -464,6 +478,48 @@ public class LifeFeedbackServiceImpl extends ServiceImpl<LifeFeedbackMapper, Lif
             }
             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);
@@ -489,13 +545,29 @@ public class LifeFeedbackServiceImpl extends ServiceImpl<LifeFeedbackMapper, Lif
                 return R.fail("反馈记录不存在");
             }
 
-            // 3. 记录回复日志(类型3-回复用户)
+            // 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);
@@ -553,5 +625,89 @@ public class LifeFeedbackServiceImpl extends ServiceImpl<LifeFeedbackMapper, Lif
             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);
+        }
+    }
 }