Commit 04ea1c9db57a8c39b90ee88e3a01729649f016c5
1 parent
dc7be585
✨ feat(im): 优化私聊消息已读的处理逻辑
Showing
10 changed files
with
134 additions
and
64 deletions
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); |