|
@@ -11,11 +11,13 @@ import org.springframework.transaction.annotation.Transactional;
|
|
|
import org.springframework.util.CollectionUtils;
|
|
import org.springframework.util.CollectionUtils;
|
|
|
import shop.alien.entity.result.R;
|
|
import shop.alien.entity.result.R;
|
|
|
import shop.alien.entity.store.LifeFeedback;
|
|
import shop.alien.entity.store.LifeFeedback;
|
|
|
|
|
+import shop.alien.entity.store.LifeFeedbackReply;
|
|
|
import shop.alien.entity.store.LifeImg;
|
|
import shop.alien.entity.store.LifeImg;
|
|
|
import shop.alien.entity.store.LifeLog;
|
|
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.LifeFeedbackDto;
|
|
|
|
|
+import shop.alien.entity.store.dto.UserReplyDto;
|
|
|
import shop.alien.entity.store.vo.LifeFeedbackVo;
|
|
import shop.alien.entity.store.vo.LifeFeedbackVo;
|
|
|
|
|
+import shop.alien.mapper.LifeFeedbackReplyMapper;
|
|
|
import shop.alien.mapper.LifeFeedbackMapper;
|
|
import shop.alien.mapper.LifeFeedbackMapper;
|
|
|
import shop.alien.mapper.LifeLogMapper;
|
|
import shop.alien.mapper.LifeLogMapper;
|
|
|
import shop.alien.store.service.LifeFeedbackService;
|
|
import shop.alien.store.service.LifeFeedbackService;
|
|
@@ -36,6 +38,7 @@ public class LifeFeedbackServiceImpl extends ServiceImpl<LifeFeedbackMapper, Lif
|
|
|
private final LifeFeedbackMapper lifeFeedbackMapper;
|
|
private final LifeFeedbackMapper lifeFeedbackMapper;
|
|
|
private final LifeImgService lifeImgService;
|
|
private final LifeImgService lifeImgService;
|
|
|
private final LifeLogMapper lifeLogMapper;
|
|
private final LifeLogMapper lifeLogMapper;
|
|
|
|
|
+ private final LifeFeedbackReplyMapper lifeFeedbackReplyMapper;
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
|
public R<String> submitFeedback(LifeFeedbackDto dto) {
|
|
public R<String> submitFeedback(LifeFeedbackDto dto) {
|
|
@@ -57,34 +60,37 @@ public class LifeFeedbackServiceImpl extends ServiceImpl<LifeFeedbackMapper, Lif
|
|
|
// 2. 创建反馈记录(使用MyBatis Plus的save方法)
|
|
// 2. 创建反馈记录(使用MyBatis Plus的save方法)
|
|
|
LifeFeedback feedback = new LifeFeedback();
|
|
LifeFeedback feedback = new LifeFeedback();
|
|
|
BeanUtils.copyProperties(dto, feedback);
|
|
BeanUtils.copyProperties(dto, feedback);
|
|
|
|
|
+ // 如果feedbackWay为空,默认为用户主动反馈(0)
|
|
|
|
|
+ if (feedback.getFeedbackWay() == null) {
|
|
|
|
|
+ feedback.setFeedbackWay(0);
|
|
|
|
|
+ }
|
|
|
feedback.setFeedbackTime(new Date());
|
|
feedback.setFeedbackTime(new Date());
|
|
|
feedback.setHandleStatus(0); // 待处理
|
|
feedback.setHandleStatus(0); // 待处理
|
|
|
- feedback.setCreatedTime(new Date());
|
|
|
|
|
- feedback.setCreatedUserId(dto.getUserId());
|
|
|
|
|
|
|
+ // createTime 和 updateTime 由 MyBatis Plus 自动填充,无需手动设置
|
|
|
|
|
|
|
|
boolean saveResult = this.save(feedback);
|
|
boolean saveResult = this.save(feedback);
|
|
|
if (!saveResult) {
|
|
if (!saveResult) {
|
|
|
return R.fail("提交反馈失败");
|
|
return R.fail("提交反馈失败");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 3. 保存附件图片(使用批量插入)
|
|
|
|
|
|
|
+ // 3. 保存附件(图片和视频,使用批量插入)
|
|
|
if (!CollectionUtils.isEmpty(dto.getImgUrlList())) {
|
|
if (!CollectionUtils.isEmpty(dto.getImgUrlList())) {
|
|
|
List<LifeImg> imgList = new java.util.ArrayList<>();
|
|
List<LifeImg> imgList = new java.util.ArrayList<>();
|
|
|
int sort = 1;
|
|
int sort = 1;
|
|
|
- for (String imgUrl : dto.getImgUrlList()) {
|
|
|
|
|
|
|
+ for (String fileUrl : dto.getImgUrlList()) {
|
|
|
LifeImg img = new LifeImg();
|
|
LifeImg img = new LifeImg();
|
|
|
img.setFeedbackId(feedback.getId());
|
|
img.setFeedbackId(feedback.getId());
|
|
|
- img.setImgUrl(imgUrl);
|
|
|
|
|
|
|
+ img.setImgUrl(fileUrl);
|
|
|
img.setImgSort(sort++);
|
|
img.setImgSort(sort++);
|
|
|
- img.setCreatedTime(new Date());
|
|
|
|
|
img.setCreatedUserId(dto.getUserId());
|
|
img.setCreatedUserId(dto.getUserId());
|
|
|
|
|
+ // createdTime 由 MyBatis Plus 自动填充
|
|
|
imgList.add(img);
|
|
imgList.add(img);
|
|
|
}
|
|
}
|
|
|
lifeImgService.batchSave(imgList);
|
|
lifeImgService.batchSave(imgList);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 4. 记录日志
|
|
|
|
|
- saveLog("用户提交反馈,ID:" + feedback.getId());
|
|
|
|
|
|
|
+ // 4. 记录日志(只记录内容)
|
|
|
|
|
+ saveLog(feedback.getId(), feedback.getContent(), "0");
|
|
|
|
|
|
|
|
return R.success("提交成功");
|
|
return R.success("提交成功");
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
@@ -94,78 +100,32 @@ public class LifeFeedbackServiceImpl extends ServiceImpl<LifeFeedbackMapper, Lif
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
@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) {
|
|
public IPage<LifeFeedbackVo> getFeedbackList(Integer userId, Integer feedbackSource, int page, int size) {
|
|
|
try {
|
|
try {
|
|
|
// 使用自定义SQL查询(已包含工作人员名称)
|
|
// 使用自定义SQL查询(已包含工作人员名称)
|
|
|
|
|
+ // 只返回原始反馈(feedback_way=0或1),不包括回复
|
|
|
Page<LifeFeedbackVo> pageParam = new Page<>(page, size);
|
|
Page<LifeFeedbackVo> pageParam = new Page<>(page, size);
|
|
|
IPage<LifeFeedbackVo> voPage = lifeFeedbackMapper.selectFeedbackListWithStaff(
|
|
IPage<LifeFeedbackVo> voPage = lifeFeedbackMapper.selectFeedbackListWithStaff(
|
|
|
- pageParam, userId, feedbackSource, 1, null
|
|
|
|
|
|
|
+ pageParam, userId, feedbackSource, null, null
|
|
|
);
|
|
);
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 过滤出原始反馈(feedback_way=0或1)
|
|
|
|
|
+ List<LifeFeedbackVo> originalFeedbacks = voPage.getRecords().stream()
|
|
|
|
|
+ .filter(vo -> vo.getFeedbackWay() == 0 || vo.getFeedbackWay() == 1)
|
|
|
|
|
+ .collect(java.util.stream.Collectors.toList());
|
|
|
|
|
+
|
|
|
// 为每条记录查询附件图片
|
|
// 为每条记录查询附件图片
|
|
|
- voPage.getRecords().forEach(vo -> {
|
|
|
|
|
|
|
+ originalFeedbacks.forEach(vo -> {
|
|
|
List<String> imgUrls = lifeImgService.getImgUrlsByFeedbackId(vo.getId());
|
|
List<String> imgUrls = lifeImgService.getImgUrlsByFeedbackId(vo.getId());
|
|
|
vo.setImgUrlList(imgUrls);
|
|
vo.setImgUrlList(imgUrls);
|
|
|
});
|
|
});
|
|
|
|
|
+
|
|
|
|
|
+ // 重新设置records
|
|
|
|
|
+ Page<LifeFeedbackVo> resultPage = new Page<>(page, size);
|
|
|
|
|
+ resultPage.setRecords(originalFeedbacks);
|
|
|
|
|
+ resultPage.setTotal(originalFeedbacks.size());
|
|
|
|
|
|
|
|
- return voPage;
|
|
|
|
|
|
|
+ return resultPage;
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
log.error("查询反馈列表失败", e);
|
|
log.error("查询反馈列表失败", e);
|
|
|
return new Page<>(page, size);
|
|
return new Page<>(page, size);
|
|
@@ -181,17 +141,70 @@ public class LifeFeedbackServiceImpl extends ServiceImpl<LifeFeedbackMapper, Lif
|
|
|
return R.fail("反馈记录不存在");
|
|
return R.fail("反馈记录不存在");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 2. 查询附件图片
|
|
|
|
|
- List<String> imgUrls = lifeImgService.getImgUrlsByFeedbackId(feedbackId);
|
|
|
|
|
- vo.setImgUrlList(imgUrls);
|
|
|
|
|
-
|
|
|
|
|
- // 3. 查询平台回复列表(如果是主动反馈)
|
|
|
|
|
- if (vo.getFeedbackWay() == 1) {
|
|
|
|
|
- List<LifeFeedbackVo> replyList = lifeFeedbackMapper.selectPlatformReplies(
|
|
|
|
|
- vo.getUserId(), vo.getFeedbackSource(), vo.getFeedbackTime()
|
|
|
|
|
- );
|
|
|
|
|
- vo.setPlatformReplies(replyList);
|
|
|
|
|
|
|
+ // 2. 查询所有附件(包括原始反馈和回复的附件)
|
|
|
|
|
+ List<LifeImg> allImgs = lifeImgService.getByFeedbackId(feedbackId);
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 查询回复列表(从life_feedback_reply表查询)
|
|
|
|
|
+ List<LifeFeedbackReply> replyList = lifeFeedbackReplyMapper.selectByFeedbackId(feedbackId);
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 获取最早回复时间(用于区分原始反馈附件和回复附件)
|
|
|
|
|
+ Date earliestReplyTime = null;
|
|
|
|
|
+ if (!replyList.isEmpty()) {
|
|
|
|
|
+ earliestReplyTime = replyList.stream()
|
|
|
|
|
+ .map(LifeFeedbackReply::getCreateTime)
|
|
|
|
|
+ .min(Date::compareTo)
|
|
|
|
|
+ .orElse(null);
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ // 5. 通过时间逻辑区分附件归属
|
|
|
|
|
+ Date feedbackTime = vo.getFeedbackTime();
|
|
|
|
|
+ List<String> originalImgUrls = new java.util.ArrayList<>();
|
|
|
|
|
+ List<LifeFeedbackVo> platformReplies = new java.util.ArrayList<>();
|
|
|
|
|
+
|
|
|
|
|
+ // 5.1 处理回复列表,为每个回复匹配附件
|
|
|
|
|
+ for (LifeFeedbackReply reply : replyList) {
|
|
|
|
|
+ LifeFeedbackVo replyVo = new LifeFeedbackVo();
|
|
|
|
|
+ replyVo.setId(reply.getId());
|
|
|
|
|
+ replyVo.setContent(reply.getReplyContent());
|
|
|
|
|
+ replyVo.setFeedbackTime(reply.getCreateTime());
|
|
|
|
|
+ replyVo.setFeedbackWay(reply.getReplyType() == 0 ? 2 : 1); // 0-平台回复,1-用户回复
|
|
|
|
|
+ // 平台回复通过staffId区分,用户回复staffId为null
|
|
|
|
|
+ if (reply.getReplyType() == 0) {
|
|
|
|
|
+ replyVo.setStaffName("平台客服"); // 平台回复显示工作人员名称
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 为回复匹配附件(时间差在5分钟内)
|
|
|
|
|
+ List<String> replyImgUrls = new java.util.ArrayList<>();
|
|
|
|
|
+ for (LifeImg img : allImgs) {
|
|
|
|
|
+ if (img.getCreatedTime() != null && reply.getCreateTime() != null) {
|
|
|
|
|
+ long timeDiff = Math.abs(img.getCreatedTime().getTime() - reply.getCreateTime().getTime());
|
|
|
|
|
+ if (timeDiff <= 5 * 60 * 1000) { // 5分钟内
|
|
|
|
|
+ // 如果附件时间晚于最早回复时间,则属于回复
|
|
|
|
|
+ if (earliestReplyTime != null && img.getCreatedTime().after(earliestReplyTime)) {
|
|
|
|
|
+ replyImgUrls.add(img.getImgUrl());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ replyVo.setImgUrlList(replyImgUrls);
|
|
|
|
|
+ platformReplies.add(replyVo);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 5.2 处理原始反馈附件(时间差在反馈时间5分钟内,且早于最早回复时间)
|
|
|
|
|
+ for (LifeImg img : allImgs) {
|
|
|
|
|
+ if (img.getCreatedTime() != null && feedbackTime != null) {
|
|
|
|
|
+ long timeDiff = Math.abs(img.getCreatedTime().getTime() - feedbackTime.getTime());
|
|
|
|
|
+ if (timeDiff <= 5 * 60 * 1000) { // 5分钟内
|
|
|
|
|
+ // 如果早于最早回复时间,或没有回复,则属于原始反馈
|
|
|
|
|
+ if (earliestReplyTime == null || img.getCreatedTime().before(earliestReplyTime)) {
|
|
|
|
|
+ originalImgUrls.add(img.getImgUrl());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ vo.setImgUrlList(originalImgUrls);
|
|
|
|
|
+ vo.setPlatformReplies(platformReplies);
|
|
|
|
|
|
|
|
return R.data(vo);
|
|
return R.data(vo);
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
@@ -201,37 +214,57 @@ public class LifeFeedbackServiceImpl extends ServiceImpl<LifeFeedbackMapper, Lif
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
|
- public R<String> updateHandleStatus(Integer feedbackId, Integer handleStatus, Integer staffId) {
|
|
|
|
|
|
|
+ public R<String> userReply(UserReplyDto dto) {
|
|
|
try {
|
|
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.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表,reply_type=1)
|
|
|
|
|
+ LifeFeedbackReply reply = new LifeFeedbackReply();
|
|
|
|
|
+ reply.setFeedbackId(dto.getFeedbackId());
|
|
|
|
|
+ reply.setReplyType(1); // 1-用户回复
|
|
|
|
|
+ reply.setReplyContent(dto.getContent());
|
|
|
|
|
+ // createTime 和 updateTime 由 MyBatis Plus 自动填充
|
|
|
|
|
+
|
|
|
|
|
+ boolean saveResult = lifeFeedbackReplyMapper.insert(reply) > 0;
|
|
|
|
|
+ if (!saveResult) {
|
|
|
|
|
+ return R.fail("回复失败");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 注意:根据文档,用户回复不保存附件、不更新反馈状态、不记录操作日志
|
|
|
|
|
+
|
|
|
|
|
+ return R.success("回复成功");
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
- log.error("更新反馈处理状态失败", e);
|
|
|
|
|
- return R.fail("更新失败:" + e.getMessage());
|
|
|
|
|
|
|
+ log.error("用户回复失败", e);
|
|
|
|
|
+ return R.fail("用户回复失败:" + e.getMessage());
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 保存操作日志
|
|
* 保存操作日志
|
|
|
*/
|
|
*/
|
|
|
- private void saveLog(String context) {
|
|
|
|
|
|
|
+ private void saveLog(Integer feedbackId, String context, String type) {
|
|
|
try {
|
|
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);
|
|
|
|
|
+ // createdTime 由 MyBatis Plus 自动填充
|
|
|
|
|
+ lifeLogMapper.insert(lifeLog);
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
log.error("保存日志失败", e);
|
|
log.error("保存日志失败", e);
|
|
|
}
|
|
}
|