Commit 04ea1c9db57a8c39b90ee88e3a01729649f016c5

Authored by YunaiV
1 parent dc7be585

✨ feat(im): 优化私聊消息已读的处理逻辑

yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageSceneEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.enums.message;
  2 +
  3 +import cn.iocoder.yudao.framework.common.core.ArrayValuable;
  4 +import lombok.Getter;
  5 +import lombok.RequiredArgsConstructor;
  6 +
  7 +import java.util.Arrays;
  8 +
  9 +/**
  10 + * IM 消息场景枚举
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +@RequiredArgsConstructor
  15 +@Getter
  16 +public enum ImMessageSceneEnum implements ArrayValuable<Integer> {
  17 +
  18 + PRIVATE(1, "私聊"),
  19 + GROUP(2, "群聊");
  20 +
  21 + public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImMessageSceneEnum::getScene).toArray(Integer[]::new);
  22 +
  23 + /**
  24 + * 场景
  25 + */
  26 + private final Integer scene;
  27 + /**
  28 + * 名字
  29 + */
  30 + private final String name;
  31 +
  32 + @Override
  33 + public Integer[] array() {
  34 + return ARRAYS;
  35 + }
  36 +
  37 +}
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImPrivateMessageController.java
@@ -51,9 +51,9 @@ public class ImPrivateMessageController { @@ -51,9 +51,9 @@ public class ImPrivateMessageController {
51 51
52 @PutMapping("/read") 52 @PutMapping("/read")
53 @Operation(summary = "标记私聊消息已读") 53 @Operation(summary = "标记私聊消息已读")
54 - @Parameter(name = "friendId", description = "好友用户编号", required = true, example = "2")  
55 - public CommonResult<Boolean> readPrivateMessages(@RequestParam("friendId") Long friendId) {  
56 - privateMessageService.readPrivateMessages(getLoginUserId(), friendId); 54 + @Parameter(name = "receiverId", description = "接收方用户编号(对方)", required = true, example = "2")
  55 + public CommonResult<Boolean> readPrivateMessages(@RequestParam("receiverId") Long receiverId) {
  56 + privateMessageService.readPrivateMessages(getLoginUserId(), receiverId);
57 return success(true); 57 return success(true);
58 } 58 }
59 59
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageService.java
@@ -34,10 +34,10 @@ public interface ImPrivateMessageService { @@ -34,10 +34,10 @@ public interface ImPrivateMessageService {
34 /** 34 /**
35 * 标记私聊消息已读 35 * 标记私聊消息已读
36 * 36 *
37 - * @param userId 当前用户编号  
38 - * @param friendId 好友用户编号 37 + * @param userId 当前用户编号
  38 + * @param receiverId 接收方用户编号(对方)
39 */ 39 */
40 - void readPrivateMessages(Long userId, Long friendId); 40 + void readPrivateMessages(Long userId, Long receiverId);
41 41
42 /** 42 /**
43 * 撤回私聊消息 43 * 撤回私聊消息
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java
1 package cn.iocoder.yudao.module.im.service.message; 1 package cn.iocoder.yudao.module.im.service.message;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
3 import cn.hutool.extra.spring.SpringUtil; 4 import cn.hutool.extra.spring.SpringUtil;
4 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; 5 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  7 +import cn.iocoder.yudao.framework.websocket.core.sender.WebSocketMessageSender;
6 import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO; 8 import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO;
7 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; 9 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO;
8 import cn.iocoder.yudao.module.im.dal.mysql.message.ImPrivateMessageMapper; 10 import cn.iocoder.yudao.module.im.dal.mysql.message.ImPrivateMessageMapper;
9 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; 11 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
  12 +import cn.iocoder.yudao.module.im.enums.message.ImMessageSceneEnum;
10 import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; 13 import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum;
11 import cn.iocoder.yudao.module.im.service.friend.ImFriendService; 14 import cn.iocoder.yudao.module.im.service.friend.ImFriendService;
12 import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; 15 import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService;
13 import cn.iocoder.yudao.module.im.websocket.ImMessageReadMessage; 16 import cn.iocoder.yudao.module.im.websocket.ImMessageReadMessage;
14 -import cn.iocoder.yudao.module.im.websocket.ImMessageReceiptMessage;  
15 import cn.iocoder.yudao.module.im.websocket.ImMessageRecallMessage; 17 import cn.iocoder.yudao.module.im.websocket.ImMessageRecallMessage;
  18 +import cn.iocoder.yudao.module.im.websocket.ImMessageReceiptMessage;
16 import cn.iocoder.yudao.module.im.websocket.ImPrivateMessageSendMessage; 19 import cn.iocoder.yudao.module.im.websocket.ImPrivateMessageSendMessage;
17 -import cn.iocoder.yudao.framework.websocket.core.sender.WebSocketMessageSender;  
18 import jakarta.annotation.Resource; 20 import jakarta.annotation.Resource;
19 import lombok.extern.slf4j.Slf4j; 21 import lombok.extern.slf4j.Slf4j;
20 import org.springframework.scheduling.annotation.Async; 22 import org.springframework.scheduling.annotation.Async;
@@ -25,6 +27,7 @@ import java.time.LocalDateTime; @@ -25,6 +27,7 @@ import java.time.LocalDateTime;
25 import java.util.List; 27 import java.util.List;
26 28
27 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 29 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  30 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
28 import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; 31 import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*;
29 32
30 /** 33 /**
@@ -91,31 +94,21 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { @@ -91,31 +94,21 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService {
91 return messages; 94 return messages;
92 } 95 }
93 96
94 - // TODO @AI:应该是将 friendId 改成 receiverId,毕竟消息是发给对方的  
95 @Override 97 @Override
96 - public void readPrivateMessages(Long userId, Long friendId) {  
97 - // 1. 批量更新消息状态为已读  
98 - // TODO @AI:先查询;然后在 update;避免锁的问题;  
99 - // TODO @AI:注意,update 的时候,也要 id in、status =,最后 updateObj 传入;避免并发问题;  
100 - int updated = privateMessageMapper.updateStatusToRead(userId, friendId);  
101 - if (updated == 0) {  
102 - return; // 没有需要更新的消息 98 + public void readPrivateMessages(Long userId, Long receiverId) {
  99 + // 1.1 查询未读消息列表
  100 + List<ImPrivateMessageDO> unreadMessages = privateMessageMapper.selectListBySenderIdAndReceiverIdAndStatus(
  101 + receiverId, userId, ImMessageStatusEnum.UNREAD.getStatus());
  102 + if (CollUtil.isEmpty(unreadMessages)) {
  103 + return;
103 } 104 }
  105 + // 1.2 根据 id in + status 条件更新,避免并发问题
  106 + List<Long> messageIds = convertList(unreadMessages, ImPrivateMessageDO::getId);
  107 + privateMessageMapper.updateByIdsAndStatus(messageIds, ImMessageStatusEnum.UNREAD.getStatus(),
  108 + new ImPrivateMessageDO().setStatus(ImMessageStatusEnum.READ.getStatus()));
104 109
105 - // TODO @AI:继续 async !  
106 - // 2.1 发送 READ 事件给自己的其他终端(多端同步)  
107 - ImMessageReadMessage readMessage = new ImMessageReadMessage()  
108 - .setFriendId(friendId.toString())  
109 - .setMessageScene("private");  
110 - webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), userId,  
111 - ImMessageReadMessage.TYPE, readMessage);  
112 -  
113 - // 2.2 发送 RECEIPT 事件给对方(已读回执)  
114 - ImMessageReceiptMessage receiptMessage = new ImMessageReceiptMessage()  
115 - .setUserId(userId.toString())  
116 - .setMessageScene("private");  
117 - webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), friendId,  
118 - ImMessageReceiptMessage.TYPE, receiptMessage); 110 + // 2. 异步发送 READ + RECEIPT 事件
  111 + getSelf().readPrivateMessageEvent(userId, receiverId);
119 } 112 }
120 113
121 // DONE @芋艿:已 review,逻辑正确 114 // DONE @芋艿:已 review,逻辑正确
@@ -142,9 +135,7 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { @@ -142,9 +135,7 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService {
142 135
143 // 3. 发送 RECALL 事件 136 // 3. 发送 RECALL 事件
144 ImMessageRecallMessage recallMessage = new ImMessageRecallMessage() 137 ImMessageRecallMessage recallMessage = new ImMessageRecallMessage()
145 - .setMessageId(messageId.toString())  
146 - .setSenderId(userId.toString())  
147 - .setMessageScene("private"); 138 + .setMessageId(messageId).setSenderId(userId).setScene(ImMessageSceneEnum.PRIVATE.getScene());
148 // 3.1 通知接收方 139 // 3.1 通知接收方
149 webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), message.getReceiverId(), 140 webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), message.getReceiverId(),
150 ImMessageRecallMessage.TYPE, recallMessage); 141 ImMessageRecallMessage.TYPE, recallMessage);
@@ -162,11 +153,33 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { @@ -162,11 +153,33 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService {
162 // 推送给接收方 153 // 推送给接收方
163 webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), message.getReceiverId(), 154 webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), message.getReceiverId(),
164 ImPrivateMessageSendMessage.TYPE, websocketMessage); 155 ImPrivateMessageSendMessage.TYPE, websocketMessage);
  156 +
165 // 推送给发送方自己的其他终端(多端同步) 157 // 推送给发送方自己的其他终端(多端同步)
166 webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), message.getSenderId(), 158 webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), message.getSenderId(),
167 ImPrivateMessageSendMessage.TYPE, websocketMessage); 159 ImPrivateMessageSendMessage.TYPE, websocketMessage);
168 } 160 }
169 161
  162 + /**
  163 + * 发送已读 + 已读回执 WebSocket 事件
  164 + *
  165 + * @param userId 当前用户编号
  166 + * @param receiverId 对方用户编号
  167 + */
  168 + @Async
  169 + public void readPrivateMessageEvent(Long userId, Long receiverId) {
  170 + // 1. 发送 READ 事件给自己的其他终端(多端同步)
  171 + ImMessageReadMessage readMessage = new ImMessageReadMessage()
  172 + .setReceiverId(receiverId).setScene(ImMessageSceneEnum.PRIVATE.getScene());
  173 + webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), userId,
  174 + ImMessageReadMessage.TYPE, readMessage);
  175 +
  176 + // 2. 发送 RECEIPT 事件给对方(已读回执)
  177 + ImMessageReceiptMessage receiptMessage = new ImMessageReceiptMessage()
  178 + .setReceiverId(userId).setScene(ImMessageSceneEnum.PRIVATE.getScene());
  179 + webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), receiverId,
  180 + ImMessageReceiptMessage.TYPE, receiptMessage);
  181 + }
  182 +
170 private ImPrivateMessageServiceImpl getSelf() { 183 private ImPrivateMessageServiceImpl getSelf() {
171 return SpringUtil.getBean(getClass()); 184 return SpringUtil.getBean(getClass());
172 } 185 }
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/websocket/ImMessageReadMessage.java
@@ -17,16 +17,18 @@ public class ImMessageReadMessage { @@ -17,16 +17,18 @@ public class ImMessageReadMessage {
17 public static final String TYPE = "im-message-read"; 17 public static final String TYPE = "im-message-read";
18 18
19 /** 19 /**
20 - * 私聊场景:好友编号 20 + * 私聊场景:接收方编号
21 */ 21 */
22 - private String friendId; 22 + private Long receiverId;
23 /** 23 /**
24 * 群聊场景:群编号 24 * 群聊场景:群编号
25 */ 25 */
26 - private String groupId; 26 + private Long groupId;
27 /** 27 /**
28 - * 消息场景:private / group 28 + * 消息场景
  29 + *
  30 + * 关联 {@link cn.iocoder.yudao.module.im.enums.message.ImMessageSceneEnum}
29 */ 31 */
30 - private String messageScene; 32 + private Integer scene;
31 33
32 } 34 }
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/websocket/ImMessageRecallMessage.java
@@ -17,18 +17,20 @@ public class ImMessageRecallMessage { @@ -17,18 +17,20 @@ public class ImMessageRecallMessage {
17 /** 17 /**
18 * 消息编号 18 * 消息编号
19 */ 19 */
20 - private String messageId; 20 + private Long messageId;
21 /** 21 /**
22 * 发送人编号 22 * 发送人编号
23 */ 23 */
24 - private String senderId; 24 + private Long senderId;
25 /** 25 /**
26 * 群聊场景:群编号 26 * 群聊场景:群编号
27 */ 27 */
28 - private String groupId; 28 + private Long groupId;
29 /** 29 /**
30 - * 消息场景:private / group 30 + * 消息场景
  31 + *
  32 + * 关联 {@link cn.iocoder.yudao.module.im.enums.message.ImMessageSceneEnum}
31 */ 33 */
32 - private String messageScene; 34 + private Integer scene;
33 35
34 } 36 }
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/websocket/ImMessageReceiptMessage.java
@@ -17,23 +17,30 @@ public class ImMessageReceiptMessage { @@ -17,23 +17,30 @@ public class ImMessageReceiptMessage {
17 17
18 public static final String TYPE = "im-message-receipt"; 18 public static final String TYPE = "im-message-receipt";
19 19
  20 + /**
  21 + * 消息场景
  22 + *
  23 + * 关联 {@link cn.iocoder.yudao.module.im.enums.message.ImMessageSceneEnum}
  24 + */
  25 + private Integer scene;
  26 +
20 // ========== 私聊场景 ========== 27 // ========== 私聊场景 ==========
21 28
22 /** 29 /**
23 * 私聊场景:已读方的用户编号 30 * 私聊场景:已读方的用户编号
24 */ 31 */
25 - private String userId; 32 + private Long receiverId;
26 33
27 // ========== 群聊场景 ========== 34 // ========== 群聊场景 ==========
28 35
29 /** 36 /**
30 * 群聊场景:消息编号 37 * 群聊场景:消息编号
31 */ 38 */
32 - private String messageId; 39 + private Long messageId;
33 /** 40 /**
34 * 群聊场景:群编号 41 * 群聊场景:群编号
35 */ 42 */
36 - private String groupId; 43 + private Long groupId;
37 /** 44 /**
38 * 群聊场景:已读人数 45 * 群聊场景:已读人数
39 */ 46 */
@@ -43,11 +50,4 @@ public class ImMessageReceiptMessage { @@ -43,11 +50,4 @@ public class ImMessageReceiptMessage {
43 */ 50 */
44 private Integer receiptStatus; 51 private Integer receiptStatus;
45 52
46 - // ========== 通用 ==========  
47 -  
48 - /**  
49 - * 消息场景:private / group  
50 - */  
51 - private String messageScene;  
52 -  
53 } 53 }
yudao-module-im/yudao-module-im-biz/src/test/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImplTest.java
@@ -168,7 +168,7 @@ public class ImGroupMessageServiceImplTest { @@ -168,7 +168,7 @@ public class ImGroupMessageServiceImplTest {
168 .thenReturn(List.of(beforeJoin, afterJoin)); 168 .thenReturn(List.of(beforeJoin, afterJoin));
169 169
170 // 调用 170 // 调用
171 - List<ImGroupMessageDO> result = groupMessageService.pullGroupMessages(1L, 0L, 100); 171 + List<ImGroupMessageDO> result = groupMessageService.pullGroupMessageList(1L, 0L, 100);
172 172
173 // 断言:入群前消息不可见 173 // 断言:入群前消息不可见
174 assertEquals(1, result.size()); 174 assertEquals(1, result.size());
@@ -197,7 +197,7 @@ public class ImGroupMessageServiceImplTest { @@ -197,7 +197,7 @@ public class ImGroupMessageServiceImplTest {
197 .thenReturn(List.of(beforeQuit, afterQuit)); 197 .thenReturn(List.of(beforeQuit, afterQuit));
198 198
199 // 调用 199 // 调用
200 - List<ImGroupMessageDO> result = groupMessageService.pullGroupMessages(1L, 0L, 100); 200 + List<ImGroupMessageDO> result = groupMessageService.pullGroupMessageList(1L, 0L, 100);
201 201
202 // 断言:退群后消息不可见 202 // 断言:退群后消息不可见
203 assertEquals(1, result.size()); 203 assertEquals(1, result.size());
@@ -226,7 +226,7 @@ public class ImGroupMessageServiceImplTest { @@ -226,7 +226,7 @@ public class ImGroupMessageServiceImplTest {
226 .thenReturn(List.of(directedMsg, allMsg)); 226 .thenReturn(List.of(directedMsg, allMsg));
227 227
228 // 调用 228 // 调用
229 - List<ImGroupMessageDO> result = groupMessageService.pullGroupMessages(1L, 0L, 100); 229 + List<ImGroupMessageDO> result = groupMessageService.pullGroupMessageList(1L, 0L, 100);
230 230
231 // 断言:定向接收的消息用户 1 看不到,只能看到全员消息 231 // 断言:定向接收的消息用户 1 看不到,只能看到全员消息
232 assertEquals(1, result.size()); 232 assertEquals(1, result.size());
@@ -250,7 +250,7 @@ public class ImGroupMessageServiceImplTest { @@ -250,7 +250,7 @@ public class ImGroupMessageServiceImplTest {
250 .thenReturn(List.of(atMsg)); 250 .thenReturn(List.of(atMsg));
251 251
252 // 调用 252 // 调用
253 - List<ImGroupMessageDO> result = groupMessageService.pullGroupMessages(1L, 0L, 100); 253 + List<ImGroupMessageDO> result = groupMessageService.pullGroupMessageList(1L, 0L, 100);
254 254
255 // 断言:@ 字段正确返回 255 // 断言:@ 字段正确返回
256 assertEquals(1, result.size()); 256 assertEquals(1, result.size());
yudao-module-im/yudao-module-im-biz/src/test/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImplTest.java
@@ -137,7 +137,7 @@ public class ImPrivateMessageServiceImplTest { @@ -137,7 +137,7 @@ public class ImPrivateMessageServiceImplTest {
137 when(imPrivateMessageMapper.selectListByMinId(1L, 0L, 100)).thenReturn(mockMessages); 137 when(imPrivateMessageMapper.selectListByMinId(1L, 0L, 100)).thenReturn(mockMessages);
138 138
139 // 调用 139 // 调用
140 - List<ImPrivateMessageDO> result = privateMessageService.pullPrivateMessages(1L, 0L, 100); 140 + List<ImPrivateMessageDO> result = privateMessageService.pullPrivateMessageList(1L, 0L, 100);
141 141
142 // 断言 142 // 断言
143 assertEquals(2, result.size()); 143 assertEquals(2, result.size());
@@ -147,7 +147,7 @@ public class ImPrivateMessageServiceImplTest { @@ -147,7 +147,7 @@ public class ImPrivateMessageServiceImplTest {
147 public void testPullMessages_sizeExceeded() { 147 public void testPullMessages_sizeExceeded() {
148 // 调用并断言 148 // 调用并断言
149 ServiceException exception = assertThrows(ServiceException.class, 149 ServiceException exception = assertThrows(ServiceException.class,
150 - () -> privateMessageService.pullPrivateMessages(1L, 0L, 1001)); 150 + () -> privateMessageService.pullPrivateMessageList(1L, 0L, 1001));
151 assertEquals(MESSAGE_PULL_SIZE_EXCEEDED.getCode(), exception.getCode()); 151 assertEquals(MESSAGE_PULL_SIZE_EXCEEDED.getCode(), exception.getCode());
152 } 152 }
153 153
@@ -155,13 +155,21 @@ public class ImPrivateMessageServiceImplTest { @@ -155,13 +155,21 @@ public class ImPrivateMessageServiceImplTest {
155 155
156 @Test 156 @Test
157 public void testReadMessages_success() { 157 public void testReadMessages_success() {
158 - // 准备  
159 - when(imPrivateMessageMapper.updateStatusToRead(1L, 2L)).thenReturn(3); 158 + // 准备:mock 未读消息列表
  159 + List<ImPrivateMessageDO> unreadMessages = List.of(
  160 + ImPrivateMessageDO.builder().id(1L).senderId(2L).receiverId(1L)
  161 + .status(ImMessageStatusEnum.UNREAD.getStatus()).build(),
  162 + ImPrivateMessageDO.builder().id(2L).senderId(2L).receiverId(1L)
  163 + .status(ImMessageStatusEnum.UNREAD.getStatus()).build()
  164 + );
  165 + when(imPrivateMessageMapper.selectListBySenderIdAndReceiverIdAndStatus(2L, 1L,
  166 + ImMessageStatusEnum.UNREAD.getStatus())).thenReturn(unreadMessages);
160 167
161 // 调用 168 // 调用
162 privateMessageService.readPrivateMessages(1L, 2L); 169 privateMessageService.readPrivateMessages(1L, 2L);
163 170
164 - // 断言:发送了 READ 和 RECEIPT 事件 171 + // 断言:更新了消息状态 + 发送了 READ 和 RECEIPT 事件
  172 + verify(imPrivateMessageMapper).update(any(ImPrivateMessageDO.class), any());
165 verify(webSocketMessageSender, times(2)).sendObject(anyInt(), anyLong(), anyString(), any()); 173 verify(webSocketMessageSender, times(2)).sendObject(anyInt(), anyLong(), anyString(), any());
166 } 174 }
167 175
yudao-module-im/yudao-module-im-biz/src/test/java/cn/iocoder/yudao/module/im/service/message/ImWebSocketEventTest.java
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.im.service.message; @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.im.service.message;
3 import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO; 3 import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO;
4 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; 4 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO;
5 import cn.iocoder.yudao.module.im.dal.mysql.message.ImPrivateMessageMapper; 5 import cn.iocoder.yudao.module.im.dal.mysql.message.ImPrivateMessageMapper;
  6 +import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
6 import cn.iocoder.yudao.module.im.websocket.ImMessageReadMessage; 7 import cn.iocoder.yudao.module.im.websocket.ImMessageReadMessage;
7 import cn.iocoder.yudao.module.im.websocket.ImMessageReceiptMessage; 8 import cn.iocoder.yudao.module.im.websocket.ImMessageReceiptMessage;
8 import cn.iocoder.yudao.module.im.websocket.ImMessageRecallMessage; 9 import cn.iocoder.yudao.module.im.websocket.ImMessageRecallMessage;
@@ -17,6 +18,8 @@ import org.mockito.InjectMocks; @@ -17,6 +18,8 @@ import org.mockito.InjectMocks;
17 import org.mockito.Mock; 18 import org.mockito.Mock;
18 import org.mockito.junit.jupiter.MockitoExtension; 19 import org.mockito.junit.jupiter.MockitoExtension;
19 20
  21 +import java.util.List;
  22 +
20 import static org.junit.jupiter.api.Assertions.*; 23 import static org.junit.jupiter.api.Assertions.*;
21 import static org.mockito.ArgumentMatchers.*; 24 import static org.mockito.ArgumentMatchers.*;
22 import static org.mockito.Mockito.*; 25 import static org.mockito.Mockito.*;
@@ -82,8 +85,13 @@ public class ImWebSocketEventTest { @@ -82,8 +85,13 @@ public class ImWebSocketEventTest {
82 85
83 @Test 86 @Test
84 public void testReadMessage_readAndReceiptEvents() { 87 public void testReadMessage_readAndReceiptEvents() {
85 - // 准备  
86 - when(imPrivateMessageMapper.updateStatusToRead(1L, 2L)).thenReturn(1); 88 + // 准备:mock 未读消息
  89 + List<ImPrivateMessageDO> unreadMessages = List.of(
  90 + ImPrivateMessageDO.builder().id(1L).senderId(2L).receiverId(1L)
  91 + .status(ImMessageStatusEnum.UNREAD.getStatus()).build()
  92 + );
  93 + when(imPrivateMessageMapper.selectListBySenderIdAndReceiverIdAndStatus(2L, 1L,
  94 + ImMessageStatusEnum.UNREAD.getStatus())).thenReturn(unreadMessages);
87 95
88 // 调用 96 // 调用
89 privateMessageService.readPrivateMessages(1L, 2L); 97 privateMessageService.readPrivateMessages(1L, 2L);