From 28cf87ac8afff15d1e362152e8aa65dfca1f7a04 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 5 May 2026 21:56:05 +0800 Subject: [PATCH] feat(im): 重构普通消息类型,和 openim 的消息编号对齐 --- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ImCommonConstants.java | 11 ----------- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageTypeEnum.java | 62 ++++++++++++++++++++++++++++++++++++++++++++++---------------- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java | 50 +++++++++++++++++++------------------------------- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageService.java | 19 ------------------- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java | 11 ----------- yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImplTest.java | 10 +++------- yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImplTest.java | 4 ++-- yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImplTest.java | 27 ++------------------------- 8 files changed, 72 insertions(+), 122 deletions(-) diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ImCommonConstants.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ImCommonConstants.java index 5c0d806..10e9134 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ImCommonConstants.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ImCommonConstants.java @@ -54,15 +54,4 @@ public interface ImCommonConstants { */ int MESSAGE_GROUP_PULL_MAX_DAYS = 30; - // ==================== 好友相关提示消息 ==================== - - /** - * 好友添加系统提示 - */ - String FRIEND_ADD_TIP_MESSAGE = "你们已成为好友,现在可以开始聊天了"; - /** - * 好友删除系统提示(仅自己可见,单边推送) - */ - String FRIEND_DELETE_TIP_MESSAGE = "你已删除好友"; - } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageTypeEnum.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageTypeEnum.java index 4ffc803..0c4fce5 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageTypeEnum.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageTypeEnum.java @@ -17,20 +17,49 @@ import java.util.Arrays; @RequiredArgsConstructor public enum ImMessageTypeEnum implements ArrayValuable { - // ========== 用户聊天消息 ========== - TEXT(0, "文本", true, true), // 对应 TextMessage 类 - IMAGE(1, "图片", true, true), // 对应 ImageMessage 类 - FILE(2, "文件", true, true), // 对应 FileMessage 类 - VOICE(3, "语音", true, true), // 对应 AudioMessage 类 - VIDEO(4, "视频", true, true), // 对应 VideoMessage 类 - - // ========== 信号类 ========== - RECALL(10, "撤回", true, false), // 对应 RecallMessage 类 - READ(11, "已读", false, false), // 暂无 - RECEIPT(12, "回执", false, false), // 暂无 + // ========== 用户聊天消息(101-105 直接复用 OpenIM 段位编号) ========== + /** + * 对应 OpenIM:Text 101 + * 对应自己的类:TextMessage + */ + TEXT(101, "文本", true, true), + /** + * 对应 OpenIM:Picture 102 + * 对应自己的类:ImageMessage + */ + IMAGE(102, "图片", true, true), + /** + * 对应 OpenIM:Sound 103 + * 对应自己的类:AudioMessage + */ + VOICE(103, "语音", true, true), + /** + * 对应 OpenIM:Video 104 + * 对应自己的类:VideoMessage + */ + VIDEO(104, "视频", true, true), + /** + * 对应 OpenIM:File 105 + * 对应自己的类:FileMessage + */ + FILE(105, "文件", true, true), - // ========== 系统提示 ========== - TIP_TEXT(21, "系统提示", true, false), // 对应 TextMessage 类 + // ========== 信号类(2101 / 2200 直接复用 OpenIM 段位编号;2201 自有扩展) ========== + /** + * 对应 OpenIM:RevokeNotification 2101 + * 对应自己的类:RecallMessage + */ + RECALL(2101, "撤回", true, false), + /** + * 对应 OpenIM:HasReadReceipt 2200 + * 对应自己的类:无(payload 走 ImXxxMessageDTO 顶层字段) + */ + RECEIPT(2200, "回执", false, false), + /** + * 对应 OpenIM:无(自有扩展,OpenIM 走 ConversationChangeNotification 1300 路径) + * 对应自己的类:无(payload 走 ImXxxMessageDTO 顶层字段) + */ + READ(2201, "已读", false, false), // ========== 好友通知(1201-1210 直接复用 OpenIM 段位编号) ========== /** @@ -52,11 +81,12 @@ public enum ImMessageTypeEnum implements ArrayValuable { */ FRIEND_REQUEST_RECEIVED(1203, "收到新的好友申请", false, false), /** - * 对应 OpenIM:FriendAddedNotification 1204 + * 对应 OpenIM:FriendAddedNotification 1204(OpenIM friendAdded.isSendMsg=false 默认不入消息流;本系统改为入库当会话气泡) * 对应自己的类:FriendAddNotification - * 场景:双方建立好友关系(同意申请 / 管理员导入),推给 A、B 双方多端 + * 场景:双方建立好友关系,单条入库(sender=fromUserId, receiver=toUserId);双向 WebSocket 自动覆盖双方多端 + * 注意:silentReAddFriend 单边语义场景,发送时显式 setPersistent(false) 覆盖默认值 */ - FRIEND_ADD(1204, "新增好友", false, false), + FRIEND_ADD(1204, "新增好友", true, false), /** * 对应 OpenIM:FriendDeletedNotification 1205 * 对应自己的类:FriendDeleteNotification diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java index c47ad40..65283e0 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java @@ -11,6 +11,7 @@ import cn.iocoder.yudao.module.im.dal.mysql.friend.ImFriendMapper; import cn.iocoder.yudao.module.im.enums.friend.ImFriendStateEnum; import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; import cn.iocoder.yudao.module.im.service.message.ImPrivateMessageService; +import cn.iocoder.yudao.module.im.service.message.dto.ImPrivateMessageSendDTO; import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO; import cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend.*; @@ -32,8 +33,6 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.FRIEND_STATE; import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_NOT_BLOCKED; import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_NOT_FRIEND; -import static cn.iocoder.yudao.module.im.enums.ImCommonConstants.FRIEND_ADD_TIP_MESSAGE; -import static cn.iocoder.yudao.module.im.enums.ImCommonConstants.FRIEND_DELETE_TIP_MESSAGE; /** * IM 好友关系 Service 实现类 @@ -130,19 +129,12 @@ public class ImFriendServiceImpl implements ImFriendService { addFriend0(fromUserId, toUserId, request.getDisplayName(), request.getAddSource()); addFriend0(toUserId, fromUserId, null, request.getAddSource()); - // 2.1 推 TIP 系统消息(双方私聊会话里看到「你们已成为好友」);TIP 走会话入库,FRIEND_ADD 走事件通知 - privateMessageService.sendTipPrivateMessage(fromUserId, toUserId, FRIEND_ADD_TIP_MESSAGE); - - // 2.2.1 推 FRIEND_ADD 给 fromUser 多端 - FriendAddNotification toFrom = (FriendAddNotification) new FriendAddNotification() - .setOperatorUserId(toUserId).setFriendUserId(toUserId); - websocketService.sendPrivateMessageAsync(fromUserId, ImPrivateMessageDTO.ofFriendNotification( - ImMessageTypeEnum.FRIEND_ADD.getType(), toUserId, fromUserId, toFrom)); - // 2.2.2 推 FRIEND_ADD 给 toUser 多端 - FriendAddNotification toTo = (FriendAddNotification) new FriendAddNotification() - .setOperatorUserId(fromUserId).setFriendUserId(fromUserId); - websocketService.sendPrivateMessageAsync(toUserId, ImPrivateMessageDTO.ofFriendNotification( - ImMessageTypeEnum.FRIEND_ADD.getType(), fromUserId, toUserId, toTo)); + // 2. 发送 FRIEND_ADD 入库(双方拉历史都能看到「你们已成为好友」会话气泡)+ 双向 WebSocket 自动覆盖双方多端 + // operatorUserId=fromUserId 标记申请发起方;前端按 (currentUserId === operatorUserId) 区分视角,文案固定不依赖此字段 + FriendAddNotification payload = (FriendAddNotification) new FriendAddNotification() + .setOperatorUserId(fromUserId).setFriendUserId(toUserId); + privateMessageService.sendPrivateMessage(fromUserId, new ImPrivateMessageSendDTO() + .setReceiverId(toUserId).setType(ImMessageTypeEnum.FRIEND_ADD.getType()).setContent(payload)); } @Override @@ -155,15 +147,14 @@ public class ImFriendServiceImpl implements ImFriendService { // 1. 单边重新启用我侧好友关系 addFriend0(userId, friendUserId, displayName, addSource); - // 2. 推 TIP「你们已成为好友」走单边语义(persistent=false):不入库 + 仅推 userId 多端,对方完全不感知 - privateMessageService.sendTipPrivateMessage(userId, friendUserId, FRIEND_ADD_TIP_MESSAGE, false); - - // 3. 仅推 FRIEND_ADD 给 userId 多端(不通知对方,保持「对方一直把我当好友」的错觉) - // operatorUserId 填 friendUserId(对方):让 userId 多端 UI 呈现「对方加了我」的视觉效果,与 silent 语义对齐 + // 2. 走 sendPrivateMessage + persistent=false:不入库 + 仅推 userId 多端(对方完全不感知,保持「对方一直把我当好友」错觉) + // operatorUserId 填 friendUserId(对方):让 userId 多端 UI 呈现「对方加了我」视角,与 silent 语义对齐 + // 前端按 type=FRIEND_ADD 渲染会话气泡(瞬时,不入库刷新即消失) FriendAddNotification payload = (FriendAddNotification) new FriendAddNotification() .setOperatorUserId(friendUserId).setFriendUserId(friendUserId); - websocketService.sendPrivateMessageAsync(userId, ImPrivateMessageDTO.ofFriendNotification( - ImMessageTypeEnum.FRIEND_ADD.getType(), friendUserId, userId, payload)); + privateMessageService.sendPrivateMessage(userId, new ImPrivateMessageSendDTO() + .setReceiverId(friendUserId).setType(ImMessageTypeEnum.FRIEND_ADD.getType()) + .setContent(payload).setPersistent(false)); } @Override @@ -176,17 +167,14 @@ public class ImFriendServiceImpl implements ImFriendService { // 1. 单边软删:仅 userId 视角的关系置 DISABLE;friendUserId 视角不动 deleteFriend0(userId, friendUserId); - // 2. 仅 clear=false 时推 TIP「你已删除好友」(单边 persistent=false); - // clear=true / null 时跳过 —— 避免 TIP 晚于 FRIEND_DELETE 到达把刚清的会话复活 - if (Boolean.FALSE.equals(clear)) { - privateMessageService.sendTipPrivateMessage(userId, friendUserId, FRIEND_DELETE_TIP_MESSAGE, false); - } - - // 3. 推 FRIEND_DELETE 给 userId 多端做同步(friendUserId 不感知);clear 透传让多端清理动作一致 + // 2. 走 sendPrivateMessage + persistent=false:不入库 + 仅推 userId 多端(friendUserId 不感知);clear 透传让多端清理动作一致 + // clear=false 时前端按 type=FRIEND_DELETE 渲染「你已删除好友」会话气泡(瞬时); + // clear=true 时前端按 clear 字段直接清会话,跳过气泡渲染 FriendDeleteNotification payload = ((FriendDeleteNotification) new FriendDeleteNotification() .setOperatorUserId(userId).setFriendUserId(friendUserId)).setClear(clear); - websocketService.sendPrivateMessageAsync(userId, ImPrivateMessageDTO.ofFriendNotification( - ImMessageTypeEnum.FRIEND_DELETE.getType(), userId, userId, payload)); + privateMessageService.sendPrivateMessage(userId, new ImPrivateMessageSendDTO() + .setReceiverId(friendUserId).setType(ImMessageTypeEnum.FRIEND_DELETE.getType()) + .setContent(payload).setPersistent(false)); } @Override diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageService.java index edac5df..b16257f 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageService.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageService.java @@ -38,25 +38,6 @@ public interface ImPrivateMessageService { ImPrivateMessageDO sendPrivateMessage(Long senderId, ImPrivateMessageSendDTO dto); /** - * 【系统调用】发送系统提示消息(TIP_TEXT) - * - * @param senderId 发送人编号 - * @param receiverId 接收人编号 - * @param content 提示文本 - */ - void sendTipPrivateMessage(Long senderId, Long receiverId, String content); - - /** - * 【系统调用】发送系统提示消息(TIP_TEXT),可覆盖 persistent - * - * @param senderId 发送人编号 - * @param receiverId 接收人编号 - * @param content 提示文本 - * @param persistent null 走默认;false 单边(不入库 + 仅推 sender 多端,对方不感知);true 强制双向 + 入库 - */ - void sendTipPrivateMessage(Long senderId, Long receiverId, String content, Boolean persistent); - - /** * 【用户调用】撤回私聊消息 * * @param userId 当前用户编号 diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java index 0ce65f1..a0ba4f2 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java @@ -123,17 +123,6 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { } @Override - public void sendTipPrivateMessage(Long senderId, Long receiverId, String content) { - sendTipPrivateMessage(senderId, receiverId, content, null); - } - - @Override - public void sendTipPrivateMessage(Long senderId, Long receiverId, String content, Boolean persistent) { - sendPrivateMessage(senderId, new ImPrivateMessageSendDTO().setReceiverId(receiverId) - .setType(ImMessageTypeEnum.TIP_TEXT.getType()).setContent(content).setPersistent(persistent)); - } - - @Override @Transactional(rollbackFor = Exception.class) public ImPrivateMessageDO recallPrivateMessage(Long userId, Long messageId) { // 1.1 校验消息存在 diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImplTest.java index 9edeedd..3d4c503 100644 --- a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImplTest.java +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImplTest.java @@ -178,10 +178,8 @@ public class ImFriendServiceImplTest extends BaseMockitoUnitTest { verify(adminUserApi).validateUser(2L); // 断言:双向插入 2 条好友关系 verify(imFriendMapper, times(2)).insert(any(ImFriendDO.class)); - // 断言:发送提示消息 + 给双方各推送一次好友添加事件 - verify(privateMessageService).sendTipPrivateMessage(eq(1L), eq(2L), anyString()); - verify(imWebSocketService).sendPrivateMessageAsync(eq(1L), any(ImPrivateMessageDTO.class)); - verify(imWebSocketService).sendPrivateMessageAsync(eq(2L), any(ImPrivateMessageDTO.class)); + // 断言:FRIEND_ADD 单条入库(双向 WebSocket 推送由 sendPrivateMessage 内部完成,mock 下不可见) + verify(privateMessageService).sendPrivateMessage(eq(1L), any()); } } @@ -250,10 +248,8 @@ public class ImFriendServiceImplTest extends BaseMockitoUnitTest { // 断言:双向更新为 DISABLE verify(imFriendMapper, times(2)).updateById(any(ImFriendDO.class)); - // 断言:推送提示消息 + 双方各推送一次好友删除事件 - verify(privateMessageService).sendTipPrivateMessage(eq(1L), eq(2L), anyString()); + // 断言:仅给 userId 多端推送 FRIEND_DELETE 事件(friendUserId 不感知) verify(imWebSocketService).sendPrivateMessageAsync(eq(1L), any(ImPrivateMessageDTO.class)); - verify(imWebSocketService).sendPrivateMessageAsync(eq(2L), any(ImPrivateMessageDTO.class)); } } diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImplTest.java index 618b3b1..c654634 100644 --- a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImplTest.java +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImplTest.java @@ -562,9 +562,9 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { // 调用 ImGroupMessageDO result = groupMessageService.recallGroupMessage(1L, 50L); - // 断言:返回 TIP_TEXT 消息 + // 断言:返回撤回消息 assertNotNull(result); - // 验证:更新原消息状态 + 插入 tipMessage + // 验证:更新原消息状态 + 插入 RecallMessage verify(groupMessageMapper).updateById(any(ImGroupMessageDO.class)); verify(groupMessageMapper).insert(any(ImGroupMessageDO.class)); // 验证:给 2 个活跃成员推送撤回提示 diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImplTest.java index 6061d50..ebb655e 100644 --- a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImplTest.java +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImplTest.java @@ -206,9 +206,9 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { // 调用 ImPrivateMessageDO result = privateMessageService.recallPrivateMessage(1L, 10L); - // 断言:返回 TIP_TEXT 消息 + // 断言:返回撤回消息 assertNotNull(result); - // 验证:更新原消息状态 + 插入 tipMessage + // 验证:更新原消息状态 + 插入 RecallMessage verify(privateMessageMapper).updateById(any(ImPrivateMessageDO.class)); verify(privateMessageMapper).insert(any(ImPrivateMessageDO.class)); // 验证推送了消息(给接收方和发送方) @@ -375,29 +375,6 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { verify(imWebSocketService).sendPrivateMessageAsync(eq(2L), any(ImPrivateMessageDTO.class)); } - // ========== sendTipPrivateMessage ========== - - @Test - public void testSendTipPrivateMessage_success() { - // 调用 - privateMessageService.sendTipPrivateMessage(1L, 2L, "好友添加成功"); - - // 断言:插入一条 TIP_TEXT 提示消息 - ArgumentCaptor captor = ArgumentCaptor.forClass(ImPrivateMessageDO.class); - verify(privateMessageMapper).insert(captor.capture()); - ImPrivateMessageDO tip = captor.getValue(); - assertEquals(1L, tip.getSenderId()); - assertEquals(2L, tip.getReceiverId()); - assertEquals(ImMessageTypeEnum.TIP_TEXT.getType(), tip.getType()); - assertEquals("好友添加成功", tip.getContent()); - assertEquals(ImMessageStatusEnum.UNREAD.getStatus(), tip.getStatus()); - assertNotNull(tip.getClientMessageId()); - assertNotNull(tip.getSendTime()); - // 断言:给双方各推送一次 - verify(imWebSocketService).sendPrivateMessageAsync(eq(1L), any(ImPrivateMessageDTO.class)); - verify(imWebSocketService).sendPrivateMessageAsync(eq(2L), any(ImPrivateMessageDTO.class)); - } - // ========== getPrivateMessageList ========== @Test -- libgit2 0.21.4