Commit 2ce3a99318ebf726c593d32d6d33d5b8562b479e

Authored by YunaiV
1 parent 04ea1c9d

✨ feat(im): 优化消息撤回的逻辑

yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java
... ... @@ -20,6 +20,7 @@ public interface ErrorCodeConstants {
20 20 ErrorCode MESSAGE_RECEIVER_NOT_EXISTS = new ErrorCode(1_040_300_001, "接收人不存在");
21 21 ErrorCode MESSAGE_RECALL_DENIED = new ErrorCode(1_040_300_002, "只能撤回自己发送的消息");
22 22 ErrorCode MESSAGE_ALREADY_RECALLED = new ErrorCode(1_040_300_003, "消息已撤回");
  23 + ErrorCode MESSAGE_RECALL_TIMEOUT = new ErrorCode(1_040_300_007, "超过 {} 分钟的消息无法撤回");
23 24 ErrorCode MESSAGE_SENSITIVE_WORD_BLOCKED = new ErrorCode(1_040_300_004, "消息包含敏感词,无法发送");
24 25 ErrorCode MESSAGE_PULL_SIZE_EXCEEDED = new ErrorCode(1_040_300_005, "单次拉取消息数量超出限制");
25 26 ErrorCode MESSAGE_CLIENT_ID_DUPLICATE = new ErrorCode(1_040_300_006, "消息已发送,请勿重复提交");
... ...
yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/ImCommonConstants.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.enums;
  2 +
  3 +/**
  4 + * IM 通用常量
  5 + *
  6 + * @author 芋道源码
  7 + */
  8 +public interface ImCommonConstants {
  9 +
  10 + /**
  11 + * pull 最大拉取数量
  12 + */
  13 + int MESSAGE_MAX_PULL_SIZE = 1000;
  14 +
  15 + /**
  16 + * 消息撤回时间限制(分钟)
  17 + */
  18 + int MESSAGE_RECALL_TIMEOUT_MINUTES = 5;
  19 +
  20 +}
... ...
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/RecallMessage.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
  2 +
  3 +import lombok.Data;
  4 +import lombok.experimental.Accessors;
  5 +
  6 +/**
  7 + * 撤回提示消息内容
  8 + */
  9 +@Data
  10 +@Accessors(chain = true)
  11 +public class RecallMessage {
  12 +
  13 + /**
  14 + * 被撤回的原消息编号
  15 + */
  16 + private Long messageId;
  17 +
  18 +}
... ...
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageService.java
... ... @@ -44,7 +44,8 @@ public interface ImPrivateMessageService {
44 44 *
45 45 * @param userId 当前用户编号
46 46 * @param messageId 消息编号
  47 + * @return 撤回后的消息
47 48 */
48   - void recallPrivateMessage(Long userId, Long messageId);
  49 + ImPrivateMessageDO recallPrivateMessage(Long userId, Long messageId);
49 50  
50 51 }
... ...
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java
1 1 package cn.iocoder.yudao.module.im.service.message;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.IdUtil;
  5 +import cn.hutool.core.util.ObjUtil;
4 6 import cn.hutool.extra.spring.SpringUtil;
5 7 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
  8 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
6 9 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
7 10 import cn.iocoder.yudao.framework.websocket.core.sender.WebSocketMessageSender;
8 11 import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO;
9 12 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO;
  13 +import cn.iocoder.yudao.module.im.dal.dataobject.message.content.RecallMessage;
10 14 import cn.iocoder.yudao.module.im.dal.mysql.message.ImPrivateMessageMapper;
11   -import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
12 15 import cn.iocoder.yudao.module.im.enums.message.ImMessageSceneEnum;
  16 +import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
13 17 import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum;
14 18 import cn.iocoder.yudao.module.im.service.friend.ImFriendService;
15 19 import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService;
16 20 import cn.iocoder.yudao.module.im.websocket.ImMessageReadMessage;
17   -import cn.iocoder.yudao.module.im.websocket.ImMessageRecallMessage;
18 21 import cn.iocoder.yudao.module.im.websocket.ImMessageReceiptMessage;
19 22 import cn.iocoder.yudao.module.im.websocket.ImPrivateMessageSendMessage;
20 23 import jakarta.annotation.Resource;
... ... @@ -29,6 +32,8 @@ import java.util.List;
29 32 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
30 33 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
31 34 import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*;
  35 +import static cn.iocoder.yudao.module.im.enums.ImCommonConstants.MESSAGE_MAX_PULL_SIZE;
  36 +import static cn.iocoder.yudao.module.im.enums.ImCommonConstants.MESSAGE_RECALL_TIMEOUT_MINUTES;
32 37  
33 38 /**
34 39 * IM 私聊消息 Service 实现类
... ... @@ -40,11 +45,6 @@ import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*;
40 45 @Slf4j
41 46 public class ImPrivateMessageServiceImpl implements ImPrivateMessageService {
42 47  
43   - /**
44   - * pull 最大拉取数量
45   - */
46   - private static final int MAX_PULL_SIZE = 1000;
47   -
48 48 @Resource
49 49 private ImPrivateMessageMapper privateMessageMapper;
50 50  
... ... @@ -85,7 +85,7 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService {
85 85  
86 86 @Override
87 87 public List<ImPrivateMessageDO> pullPrivateMessageList(Long userId, Long minId, Integer size) {
88   - if (size > MAX_PULL_SIZE) {
  88 + if (size > MESSAGE_MAX_PULL_SIZE) {
89 89 throw exception(MESSAGE_PULL_SIZE_EXCEEDED);
90 90 }
91 91 List<ImPrivateMessageDO> messages = privateMessageMapper.selectListByMinId(userId, minId, size);
... ... @@ -111,37 +111,42 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService {
111 111 getSelf().readPrivateMessageEvent(userId, receiverId);
112 112 }
113 113  
114   - // DONE @芋艿:已 review,逻辑正确
115 114 @Override
116   - public void recallPrivateMessage(Long userId, Long messageId) {
  115 + public ImPrivateMessageDO recallPrivateMessage(Long userId, Long messageId) {
117 116 // 1.1 校验消息存在
118 117 ImPrivateMessageDO message = privateMessageMapper.selectById(messageId);
119 118 if (message == null) {
120 119 throw exception(MESSAGE_NOT_EXISTS);
121 120 }
122 121 // 1.2 只能撤回自己发送的消息
123   - // DONE @AI:Notequals。结论:这里使用 !equals() 是正确的,ObjUtil.notEqual 也可以但无必要引入
124   - if (!message.getSenderId().equals(userId)) {
  122 + if (ObjUtil.notEqual(message.getSenderId(), userId)) {
125 123 throw exception(MESSAGE_RECALL_DENIED);
126 124 }
127 125 // 1.3 不能重复撤回
128 126 if (ImMessageStatusEnum.RECALL.getStatus().equals(message.getStatus())) {
129 127 throw exception(MESSAGE_ALREADY_RECALLED);
130 128 }
  129 + // 1.4 只允许撤回限定时间内的消息
  130 + if (message.getSendTime().plusMinutes(MESSAGE_RECALL_TIMEOUT_MINUTES).isBefore(LocalDateTime.now())) {
  131 + throw exception(MESSAGE_RECALL_TIMEOUT, MESSAGE_RECALL_TIMEOUT_MINUTES);
  132 + }
131 133  
132   - // 2. 更新消息状态为撤回
  134 + // 2. 更新原消息状态为撤回
133 135 privateMessageMapper.updateById(new ImPrivateMessageDO().setId(messageId)
134 136 .setStatus(ImMessageStatusEnum.RECALL.getStatus()));
135 137  
136   - // 3. 发送 RECALL 事件
137   - ImMessageRecallMessage recallMessage = new ImMessageRecallMessage()
138   - .setMessageId(messageId).setSenderId(userId).setScene(ImMessageSceneEnum.PRIVATE.getScene());
139   - // 3.1 通知接收方
140   - webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), message.getReceiverId(),
141   - ImMessageRecallMessage.TYPE, recallMessage);
142   - // 3.2 通知发送方自己的其他终端
143   - webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), userId,
144   - ImMessageRecallMessage.TYPE, recallMessage);
  138 + // 3. 插入一条 TIP_TEXT 消息作为撤回提示
  139 + RecallMessage recallContent = new RecallMessage()
  140 + .setMessageId(messageId);
  141 + ImPrivateMessageDO tipMessage = new ImPrivateMessageDO().setClientMessageId(IdUtil.fastSimpleUUID())
  142 + .setSenderId(userId).setReceiverId(message.getReceiverId())
  143 + .setType(ImMessageTypeEnum.TIP_TEXT.getType()).setContent(JsonUtils.toJsonString(recallContent))
  144 + .setStatus(ImMessageStatusEnum.UNREAD.getStatus()).setSendTime(LocalDateTime.now());
  145 + privateMessageMapper.insert(tipMessage);
  146 +
  147 + // 4. 异步推送撤回提示消息(前端据此更新原消息状态 + 插入撤回提示)
  148 + getSelf().sendPrivateMessageEvent(tipMessage);
  149 + return tipMessage;
145 150 }
146 151  
147 152 /**
... ...