diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java index b9c5754..42cf289 100644 --- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java +++ b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java @@ -13,14 +13,11 @@ import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivate import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; import cn.iocoder.yudao.module.im.dal.dataobject.message.content.RecallMessage; import cn.iocoder.yudao.module.im.dal.mysql.message.ImPrivateMessageMapper; -import cn.iocoder.yudao.module.im.enums.message.ImMessageSceneEnum; import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; import cn.iocoder.yudao.module.im.service.friend.ImFriendService; import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; -import cn.iocoder.yudao.module.im.websocket.ImMessageReadMessage; -import cn.iocoder.yudao.module.im.websocket.ImMessageReceiptMessage; -import cn.iocoder.yudao.module.im.websocket.ImPrivateMessageSendMessage; +import cn.iocoder.yudao.module.im.websocket.ImPrivateMessageDTO; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Async; @@ -183,7 +180,7 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { ImMessageReceiptMessage receiptMessage = new ImMessageReceiptMessage() .setReceiverId(userId).setScene(ImMessageSceneEnum.PRIVATE.getScene()); webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), receiverId, - ImMessageReceiptMessage.TYPE, receiptMessage); + ImPrivateMessageDTO.TYPE, receiptDto); } @Override diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/websocket/ImPrivateMessageSendMessage.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/websocket/ImPrivateMessageDTO.java index 2c066d2..4779e81 100644 --- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/websocket/ImPrivateMessageSendMessage.java +++ b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/websocket/ImPrivateMessageDTO.java @@ -1,23 +1,34 @@ package cn.iocoder.yudao.module.im.websocket; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; +import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; import lombok.Data; import lombok.experimental.Accessors; import java.time.LocalDateTime; /** - * IM 私聊消息 WebSocket 推送消息 + * IM 私聊消息 WebSocket 统一推送 DTO + *
+ * 发送、已读、回执、撤回都通过 {@link #type} 字段区分, + * 并统一复用 {@link #id} 字段表达目标消息或已读位置。 * * @author 芋道源码 */ @Data @Accessors(chain = true) -public class ImPrivateMessageSendMessage { +public class ImPrivateMessageDTO { - public static final String TYPE = "im-private-message-send"; + public static final String TYPE = "im-private-message"; /** * 消息编号 + *
+ * 普通消息:当前消息 id + * READ:已读位置(我已读到这条消息) + * RECEIPT:已读位置(对方已读到这条消息) + * RECALL:被撤回的原消息 id */ private Long id; /** @@ -34,6 +45,8 @@ public class ImPrivateMessageSendMessage { private Long receiverId; /** * 消息类型 + *
+ * TEXT / IMAGE / FILE / VOICE / VIDEO / READ / RECEIPT / RECALL / TIP_TEXT
*/
private Integer type;
/**
@@ -49,4 +62,44 @@ public class ImPrivateMessageSendMessage {
*/
private LocalDateTime sendTime;
+ // ========== 静态工厂方法 ==========
+
+ /**
+ * 构建发送消息 DTO
+ *
+ * @param message 私聊消息 DO
+ * @return 私聊 DTO
+ */
+ public static ImPrivateMessageDTO ofSend(ImPrivateMessageDO message) {
+ return BeanUtils.toBean(message, ImPrivateMessageDTO.class);
+ }
+
+ /**
+ * 构建已读同步 DTO(多端同步:通知自己的其他终端"我已经读了某个会话")
+ *
+ * @param senderId 当前用户编号
+ * @param receiverId 对方用户编号
+ * @param readId 已读位置(最大已读消息编号)
+ * @return 私聊 DTO
+ */
+ public static ImPrivateMessageDTO ofRead(Long senderId, Long receiverId, Long readId) {
+ return new ImPrivateMessageDTO()
+ .setId(readId).setType(ImMessageTypeEnum.READ.getType())
+ .setSenderId(senderId).setReceiverId(receiverId);
+ }
+
+ /**
+ * 构建已读回执 DTO(通知对方"我已读了你的消息")
+ *
+ * @param senderId 已读方的用户编号
+ * @param receiverId 对方用户编号
+ * @param readId 已读位置(最大已读消息编号)
+ * @return 私聊 DTO
+ */
+ public static ImPrivateMessageDTO ofReceipt(Long senderId, Long receiverId, Long readId) {
+ return new ImPrivateMessageDTO()
+ .setId(readId).setType(ImMessageTypeEnum.RECEIPT.getType())
+ .setSenderId(senderId).setReceiverId(receiverId);
+ }
+
}
diff --git a/yudao-module-im/yudao-module-im-biz/src/test/java/cn/iocoder/yudao/module/im/service/message/ImWebSocketEventTest.java b/yudao-module-im/yudao-module-im-biz/src/test/java/cn/iocoder/yudao/module/im/service/message/ImWebSocketEventTest.java
index cc4fd89..d8d7299 100644
--- a/yudao-module-im/yudao-module-im-biz/src/test/java/cn/iocoder/yudao/module/im/service/message/ImWebSocketEventTest.java
+++ b/yudao-module-im/yudao-module-im-biz/src/test/java/cn/iocoder/yudao/module/im/service/message/ImWebSocketEventTest.java
@@ -6,11 +6,10 @@ import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivate
import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO;
import cn.iocoder.yudao.module.im.dal.mysql.message.ImPrivateMessageMapper;
import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
+import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum;
import cn.iocoder.yudao.module.im.service.friend.ImFriendService;
import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService;
-import cn.iocoder.yudao.module.im.websocket.ImMessageReadMessage;
-import cn.iocoder.yudao.module.im.websocket.ImMessageReceiptMessage;
-import cn.iocoder.yudao.module.im.websocket.ImPrivateMessageSendMessage;
+import cn.iocoder.yudao.module.im.websocket.ImPrivateMessageDTO;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
@@ -73,12 +72,12 @@ public class ImWebSocketEventTest extends BaseMockitoUnitTest {
verify(webSocketMessageSender, times(2)).sendObject(anyInt(), anyLong(),
typeCaptor.capture(), contentCaptor.capture());
- // 验证消息事件类型
- assertEquals(ImPrivateMessageSendMessage.TYPE, typeCaptor.getAllValues().get(0));
- assertEquals(ImPrivateMessageSendMessage.TYPE, typeCaptor.getAllValues().get(1));
+ // 验证消息事件类型:统一为 im-private-message
+ assertEquals(ImPrivateMessageDTO.TYPE, typeCaptor.getAllValues().get(0));
+ assertEquals(ImPrivateMessageDTO.TYPE, typeCaptor.getAllValues().get(1));
// 验证 payload 是 DTO 类型
- ImPrivateMessageSendMessage payload = (ImPrivateMessageSendMessage) contentCaptor.getAllValues().get(0);
+ ImPrivateMessageDTO payload = (ImPrivateMessageDTO) contentCaptor.getAllValues().get(0);
assertNotNull(payload.getId(), "id 不应为空");
assertEquals(1L, payload.getSenderId());
assertEquals(2L, payload.getReceiverId());
@@ -91,9 +90,11 @@ public class ImWebSocketEventTest extends BaseMockitoUnitTest {
springUtilMockedStatic.when(() -> cn.hutool.extra.spring.SpringUtil.getBean(eq(ImPrivateMessageServiceImpl.class)))
.thenReturn(privateMessageService);
- // 准备:mock 未读消息
+ // 准备:mock 未读消息(id=1 和 id=5,maxReadId 应为 5)
List