Commit f495f9c8054e78df95498b42f1eb7cba47b5434b
1 parent
2ce3a993
✨ feat(im): 添加私聊私聊历史消息查询功能
Showing
9 changed files
with
338 additions
and
26 deletions
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImPrivateMessageController.java
| ... | ... | @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.im.controller.admin.message; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 4 | 4 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 5 | +import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageListReqVO; | |
| 5 | 6 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageRespVO; |
| 6 | 7 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO; |
| 7 | 8 | import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; |
| ... | ... | @@ -60,9 +61,16 @@ public class ImPrivateMessageController { |
| 60 | 61 | @DeleteMapping("/recall") |
| 61 | 62 | @Operation(summary = "撤回私聊消息") |
| 62 | 63 | @Parameter(name = "id", description = "消息编号", required = true, example = "1") |
| 63 | - public CommonResult<Boolean> recallPrivateMessage(@RequestParam("id") Long id) { | |
| 64 | - privateMessageService.recallPrivateMessage(getLoginUserId(), id); | |
| 65 | - return success(true); | |
| 64 | + public CommonResult<ImPrivateMessageRespVO> recallPrivateMessage(@RequestParam("id") Long id) { | |
| 65 | + ImPrivateMessageDO message = privateMessageService.recallPrivateMessage(getLoginUserId(), id); | |
| 66 | + return success(BeanUtils.toBean(message, ImPrivateMessageRespVO.class)); | |
| 67 | + } | |
| 68 | + | |
| 69 | + @GetMapping("/list") | |
| 70 | + @Operation(summary = "查询私聊历史消息") | |
| 71 | + public CommonResult<List<ImPrivateMessageRespVO>> getPrivateMessageList(@Valid ImPrivateMessageListReqVO reqVO) { | |
| 72 | + List<ImPrivateMessageDO> messages = privateMessageService.getPrivateMessageList(getLoginUserId(), reqVO); | |
| 73 | + return success(BeanUtils.toBean(messages, ImPrivateMessageRespVO.class)); | |
| 66 | 74 | } |
| 67 | 75 | |
| 68 | 76 | } | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImPrivateMessageMapper.java
| ... | ... | @@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 4 | 4 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| 5 | 5 | import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX; |
| 6 | 6 | import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; |
| 7 | +import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; | |
| 7 | 8 | import org.apache.ibatis.annotations.Mapper; |
| 8 | 9 | |
| 9 | 10 | import java.util.List; |
| ... | ... | @@ -27,29 +28,52 @@ public interface ImPrivateMessageMapper extends BaseMapperX<ImPrivateMessageDO> |
| 27 | 28 | default List<ImPrivateMessageDO> selectListByMinId(Long userId, Long minId, Integer size) { |
| 28 | 29 | QueryWrapperX<ImPrivateMessageDO> wrapper = new QueryWrapperX<>(); |
| 29 | 30 | wrapper.and(w -> w.eq("sender_id", userId) |
| 30 | - .or().eq("receiver_id", userId)) | |
| 31 | + .or() | |
| 32 | + .eq("receiver_id", userId)) | |
| 31 | 33 | .gt("id", minId) |
| 32 | 34 | .orderByAsc("id"); |
| 33 | 35 | wrapper.limitN(size); |
| 34 | 36 | return selectList(wrapper); |
| 35 | 37 | } |
| 36 | 38 | |
| 39 | + /** | |
| 40 | + * 查询私聊历史消息(游标拉取) | |
| 41 | + * | |
| 42 | + * @param userId 当前用户编号 | |
| 43 | + * @param receiverId 对方用户编号 | |
| 44 | + * @param maxId 起始消息 id(不含),为空则从最新开始 | |
| 45 | + * @param limit 拉取数量 | |
| 46 | + * @return 消息列表(按 id 倒序) | |
| 47 | + */ | |
| 48 | + default List<ImPrivateMessageDO> selectHistoryList(Long userId, Long receiverId, Long maxId, Integer limit) { | |
| 49 | + QueryWrapperX<ImPrivateMessageDO> wrapper = new QueryWrapperX<>(); | |
| 50 | + wrapper.and(w -> w.eq("sender_id", userId).eq("receiver_id", receiverId) | |
| 51 | + .or() | |
| 52 | + .eq("sender_id", receiverId).eq("receiver_id", userId)) | |
| 53 | + .ne("status", ImMessageStatusEnum.RECALL.getStatus()) | |
| 54 | + .lt(maxId != null, "id", maxId) | |
| 55 | + .orderByDesc("id"); | |
| 56 | + wrapper.limitN(limit); | |
| 57 | + return selectList(wrapper); | |
| 58 | + } | |
| 59 | + | |
| 37 | 60 | default ImPrivateMessageDO selectBySenderIdAndClientMessageId(Long senderId, String clientMessageId) { |
| 38 | 61 | return selectOne(new LambdaQueryWrapperX<ImPrivateMessageDO>() |
| 39 | 62 | .eq(ImPrivateMessageDO::getSenderId, senderId) |
| 40 | 63 | .eq(ImPrivateMessageDO::getClientMessageId, clientMessageId)); |
| 41 | 64 | } |
| 42 | 65 | |
| 43 | - /** | |
| 44 | - * 批量更新私聊消息已读状态 | |
| 45 | - * 将发送方 = friendId、接收方 = userId 且状态为 UNREAD 的消息更新为 READ | |
| 46 | - */ | |
| 47 | - default int updateStatusToRead(Long userId, Long friendId) { | |
| 48 | - return update(new ImPrivateMessageDO().setStatus(1), // READ | |
| 49 | - new LambdaQueryWrapperX<ImPrivateMessageDO>() | |
| 50 | - .eq(ImPrivateMessageDO::getSenderId, friendId) | |
| 51 | - .eq(ImPrivateMessageDO::getReceiverId, userId) | |
| 52 | - .eq(ImPrivateMessageDO::getStatus, 0)); // UNREAD | |
| 66 | + default List<ImPrivateMessageDO> selectListBySenderIdAndReceiverIdAndStatus(Long senderId, Long receiverId, Integer status) { | |
| 67 | + return selectList(new LambdaQueryWrapperX<ImPrivateMessageDO>() | |
| 68 | + .eq(ImPrivateMessageDO::getSenderId, senderId) | |
| 69 | + .eq(ImPrivateMessageDO::getReceiverId, receiverId) | |
| 70 | + .eq(ImPrivateMessageDO::getStatus, status)); | |
| 71 | + } | |
| 72 | + | |
| 73 | + default void updateByIdsAndStatus(List<Long> ids, Integer whereStatus, ImPrivateMessageDO updateObj) { | |
| 74 | + update(updateObj, new LambdaQueryWrapperX<ImPrivateMessageDO>() | |
| 75 | + .in(ImPrivateMessageDO::getId, ids) | |
| 76 | + .eq(ImPrivateMessageDO::getStatus, whereStatus)); | |
| 53 | 77 | } |
| 54 | 78 | |
| 55 | 79 | } | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageService.java
| 1 | 1 | package cn.iocoder.yudao.module.im.service.message; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageListReqVO; | |
| 3 | 4 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO; |
| 4 | 5 | import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; |
| 5 | 6 | |
| ... | ... | @@ -48,4 +49,13 @@ public interface ImPrivateMessageService { |
| 48 | 49 | */ |
| 49 | 50 | ImPrivateMessageDO recallPrivateMessage(Long userId, Long messageId); |
| 50 | 51 | |
| 52 | + /** | |
| 53 | + * 查询私聊历史消息(游标拉取) | |
| 54 | + * | |
| 55 | + * @param userId 当前用户编号 | |
| 56 | + * @param reqVO 拉取请求 | |
| 57 | + * @return 消息列表(按 id 倒序) | |
| 58 | + */ | |
| 59 | + List<ImPrivateMessageDO> getPrivateMessageList(Long userId, ImPrivateMessageListReqVO reqVO); | |
| 60 | + | |
| 51 | 61 | } | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java
| ... | ... | @@ -8,6 +8,7 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; |
| 8 | 8 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 9 | 9 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 10 | 10 | import cn.iocoder.yudao.framework.websocket.core.sender.WebSocketMessageSender; |
| 11 | +import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageListReqVO; | |
| 11 | 12 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO; |
| 12 | 13 | import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; |
| 13 | 14 | import cn.iocoder.yudao.module.im.dal.dataobject.message.content.RecallMessage; |
| ... | ... | @@ -189,4 +190,9 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { |
| 189 | 190 | return SpringUtil.getBean(getClass()); |
| 190 | 191 | } |
| 191 | 192 | |
| 193 | + @Override | |
| 194 | + public List<ImPrivateMessageDO> getPrivateMessageList(Long userId, ImPrivateMessageListReqVO reqVO) { | |
| 195 | + return privateMessageMapper.selectHistoryList(userId, reqVO.getReceiverId(), reqVO.getMaxId(), reqVO.getLimit()); | |
| 196 | + } | |
| 197 | + | |
| 192 | 198 | } | ... | ... |
yudao-module-im/yudao-module-im-biz/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImPrivateMessageMapperTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.im.dal.mysql.message; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; | |
| 4 | +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; | |
| 5 | +import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; | |
| 6 | +import jakarta.annotation.Resource; | |
| 7 | +import org.junit.jupiter.api.Test; | |
| 8 | +import org.springframework.context.annotation.Import; | |
| 9 | + | |
| 10 | +import java.time.LocalDateTime; | |
| 11 | +import java.util.List; | |
| 12 | + | |
| 13 | +import static org.junit.jupiter.api.Assertions.*; | |
| 14 | + | |
| 15 | +/** | |
| 16 | + * IM 私聊消息 Mapper 单元测试 | |
| 17 | + * | |
| 18 | + * @author 芋道源码 | |
| 19 | + */ | |
| 20 | +@Import(ImPrivateMessageMapper.class) | |
| 21 | +public class ImPrivateMessageMapperTest extends BaseDbUnitTest { | |
| 22 | + | |
| 23 | + @Resource | |
| 24 | + private ImPrivateMessageMapper mapper; | |
| 25 | + | |
| 26 | + // ========== selectListByMinId ========== | |
| 27 | + | |
| 28 | + @Test | |
| 29 | + public void testSelectListByMinId() { | |
| 30 | + // 准备:用户 1 发给用户 2 | |
| 31 | + ImPrivateMessageDO msg1 = buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD); | |
| 32 | + mapper.insert(msg1); | |
| 33 | + // 用户 2 发给用户 1 | |
| 34 | + ImPrivateMessageDO msg2 = buildMessage(2L, 1L, ImMessageStatusEnum.UNREAD); | |
| 35 | + mapper.insert(msg2); | |
| 36 | + // 用户 3 发给用户 4(不相关) | |
| 37 | + ImPrivateMessageDO msg3 = buildMessage(3L, 4L, ImMessageStatusEnum.UNREAD); | |
| 38 | + mapper.insert(msg3); | |
| 39 | + | |
| 40 | + // 调用:用户 1 从 id=0 拉取 | |
| 41 | + List<ImPrivateMessageDO> result = mapper.selectListByMinId(1L, 0L, 100); | |
| 42 | + | |
| 43 | + // 断言:只包含与用户 1 相关的 2 条 | |
| 44 | + assertEquals(2, result.size()); | |
| 45 | + assertTrue(result.stream().allMatch(m -> | |
| 46 | + m.getSenderId().equals(1L) || m.getReceiverId().equals(1L))); | |
| 47 | + } | |
| 48 | + | |
| 49 | + @Test | |
| 50 | + public void testSelectListByMinId_withMinId() { | |
| 51 | + // 准备 | |
| 52 | + ImPrivateMessageDO msg1 = buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD); | |
| 53 | + mapper.insert(msg1); | |
| 54 | + ImPrivateMessageDO msg2 = buildMessage(2L, 1L, ImMessageStatusEnum.UNREAD); | |
| 55 | + mapper.insert(msg2); | |
| 56 | + | |
| 57 | + // 调用:从 msg1.id 之后拉取 | |
| 58 | + List<ImPrivateMessageDO> result = mapper.selectListByMinId(1L, msg1.getId(), 100); | |
| 59 | + | |
| 60 | + // 断言:只有 msg2 | |
| 61 | + assertEquals(1, result.size()); | |
| 62 | + assertEquals(msg2.getId(), result.get(0).getId()); | |
| 63 | + } | |
| 64 | + | |
| 65 | + @Test | |
| 66 | + public void testSelectListByMinId_limitSize() { | |
| 67 | + // 准备:插入 3 条 | |
| 68 | + mapper.insert(buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD)); | |
| 69 | + mapper.insert(buildMessage(2L, 1L, ImMessageStatusEnum.UNREAD)); | |
| 70 | + mapper.insert(buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD)); | |
| 71 | + | |
| 72 | + // 调用:limit 2 | |
| 73 | + List<ImPrivateMessageDO> result = mapper.selectListByMinId(1L, 0L, 2); | |
| 74 | + | |
| 75 | + // 断言 | |
| 76 | + assertEquals(2, result.size()); | |
| 77 | + } | |
| 78 | + | |
| 79 | + // ========== selectHistoryList ========== | |
| 80 | + | |
| 81 | + @Test | |
| 82 | + public void testSelectHistoryList_basic() { | |
| 83 | + // 准备:用户 1 <-> 用户 2 的消息 | |
| 84 | + ImPrivateMessageDO msg1 = buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD); | |
| 85 | + mapper.insert(msg1); | |
| 86 | + ImPrivateMessageDO msg2 = buildMessage(2L, 1L, ImMessageStatusEnum.UNREAD); | |
| 87 | + mapper.insert(msg2); | |
| 88 | + // 用户 1 <-> 用户 3 的消息(不相关) | |
| 89 | + ImPrivateMessageDO msg3 = buildMessage(1L, 3L, ImMessageStatusEnum.UNREAD); | |
| 90 | + mapper.insert(msg3); | |
| 91 | + | |
| 92 | + // 调用 | |
| 93 | + List<ImPrivateMessageDO> result = mapper.selectHistoryList(1L, 2L, null, 100); | |
| 94 | + | |
| 95 | + // 断言:只有与用户 2 对话的 2 条,按 id 倒序 | |
| 96 | + assertEquals(2, result.size()); | |
| 97 | + assertTrue(result.get(0).getId() > result.get(1).getId()); | |
| 98 | + } | |
| 99 | + | |
| 100 | + @Test | |
| 101 | + public void testSelectHistoryList_excludeRecall() { | |
| 102 | + // 准备 | |
| 103 | + ImPrivateMessageDO msg1 = buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD); | |
| 104 | + mapper.insert(msg1); | |
| 105 | + ImPrivateMessageDO msg2 = buildMessage(1L, 2L, ImMessageStatusEnum.RECALL); | |
| 106 | + mapper.insert(msg2); | |
| 107 | + | |
| 108 | + // 调用 | |
| 109 | + List<ImPrivateMessageDO> result = mapper.selectHistoryList(1L, 2L, null, 100); | |
| 110 | + | |
| 111 | + // 断言:撤回的不返回 | |
| 112 | + assertEquals(1, result.size()); | |
| 113 | + assertEquals(msg1.getId(), result.get(0).getId()); | |
| 114 | + } | |
| 115 | + | |
| 116 | + @Test | |
| 117 | + public void testSelectHistoryList_withMaxId() { | |
| 118 | + // 准备 | |
| 119 | + ImPrivateMessageDO msg1 = buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD); | |
| 120 | + mapper.insert(msg1); | |
| 121 | + ImPrivateMessageDO msg2 = buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD); | |
| 122 | + mapper.insert(msg2); | |
| 123 | + ImPrivateMessageDO msg3 = buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD); | |
| 124 | + mapper.insert(msg3); | |
| 125 | + | |
| 126 | + // 调用:从 msg3 往前拉 | |
| 127 | + List<ImPrivateMessageDO> result = mapper.selectHistoryList(1L, 2L, msg3.getId(), 100); | |
| 128 | + | |
| 129 | + // 断言:只有 msg1 和 msg2 | |
| 130 | + assertEquals(2, result.size()); | |
| 131 | + assertTrue(result.stream().allMatch(m -> m.getId() < msg3.getId())); | |
| 132 | + } | |
| 133 | + | |
| 134 | + @Test | |
| 135 | + public void testSelectHistoryList_limit() { | |
| 136 | + // 准备 | |
| 137 | + mapper.insert(buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD)); | |
| 138 | + mapper.insert(buildMessage(2L, 1L, ImMessageStatusEnum.UNREAD)); | |
| 139 | + mapper.insert(buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD)); | |
| 140 | + | |
| 141 | + // 调用:limit 2 | |
| 142 | + List<ImPrivateMessageDO> result = mapper.selectHistoryList(1L, 2L, null, 2); | |
| 143 | + | |
| 144 | + // 断言 | |
| 145 | + assertEquals(2, result.size()); | |
| 146 | + } | |
| 147 | + | |
| 148 | + @Test | |
| 149 | + public void testSelectHistoryList_bidirectional() { | |
| 150 | + // 准备:验证双向查询 | |
| 151 | + ImPrivateMessageDO msg1 = buildMessage(2L, 1L, ImMessageStatusEnum.UNREAD); // 对方发的 | |
| 152 | + mapper.insert(msg1); | |
| 153 | + | |
| 154 | + // 调用:以用户 1 的视角查 | |
| 155 | + List<ImPrivateMessageDO> result = mapper.selectHistoryList(1L, 2L, null, 100); | |
| 156 | + | |
| 157 | + // 断言:能查到对方发来的消息 | |
| 158 | + assertEquals(1, result.size()); | |
| 159 | + assertEquals(2L, result.get(0).getSenderId()); | |
| 160 | + } | |
| 161 | + | |
| 162 | + // ========== selectBySenderIdAndClientMessageId ========== | |
| 163 | + | |
| 164 | + @Test | |
| 165 | + public void testSelectBySenderIdAndClientMessageId() { | |
| 166 | + // 准备 | |
| 167 | + ImPrivateMessageDO msg = buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD); | |
| 168 | + msg.setClientMessageId("uuid-001"); | |
| 169 | + mapper.insert(msg); | |
| 170 | + | |
| 171 | + // 调用 | |
| 172 | + ImPrivateMessageDO result = mapper.selectBySenderIdAndClientMessageId(1L, "uuid-001"); | |
| 173 | + | |
| 174 | + // 断言 | |
| 175 | + assertNotNull(result); | |
| 176 | + assertEquals(msg.getId(), result.getId()); | |
| 177 | + } | |
| 178 | + | |
| 179 | + @Test | |
| 180 | + public void testSelectBySenderIdAndClientMessageId_notFound() { | |
| 181 | + ImPrivateMessageDO result = mapper.selectBySenderIdAndClientMessageId(1L, "not-exist"); | |
| 182 | + assertNull(result); | |
| 183 | + } | |
| 184 | + | |
| 185 | + // ========== 工具方法 ========== | |
| 186 | + | |
| 187 | + private ImPrivateMessageDO buildMessage(Long senderId, Long receiverId, ImMessageStatusEnum status) { | |
| 188 | + return ImPrivateMessageDO.builder() | |
| 189 | + .clientMessageId("uuid-" + System.nanoTime()) | |
| 190 | + .senderId(senderId) | |
| 191 | + .receiverId(receiverId) | |
| 192 | + .type(0) | |
| 193 | + .content("{\"content\":\"test\"}") | |
| 194 | + .status(status.getStatus()) | |
| 195 | + .sendTime(LocalDateTime.now()) | |
| 196 | + .build(); | |
| 197 | + } | |
| 198 | + | |
| 199 | +} | ... | ... |
yudao-module-im/yudao-module-im-biz/src/test/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImplTest.java
| ... | ... | @@ -177,20 +177,30 @@ public class ImPrivateMessageServiceImplTest { |
| 177 | 177 | |
| 178 | 178 | @Test |
| 179 | 179 | public void testRecallMessage_success() { |
| 180 | - // 准备 | |
| 181 | - ImPrivateMessageDO message = ImPrivateMessageDO.builder() | |
| 182 | - .id(10L).senderId(1L).receiverId(2L) | |
| 183 | - .status(ImMessageStatusEnum.UNREAD.getStatus()).build(); | |
| 184 | - when(imPrivateMessageMapper.selectById(10L)).thenReturn(message); | |
| 185 | - when(imPrivateMessageMapper.updateById(any(ImPrivateMessageDO.class))).thenReturn(1); | |
| 180 | + try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) { | |
| 181 | + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImPrivateMessageServiceImpl.class))) | |
| 182 | + .thenReturn(privateMessageService); | |
| 186 | 183 | |
| 187 | - // 调用 | |
| 188 | - privateMessageService.recallPrivateMessage(1L, 10L); | |
| 184 | + // 准备 | |
| 185 | + ImPrivateMessageDO message = ImPrivateMessageDO.builder() | |
| 186 | + .id(10L).senderId(1L).receiverId(2L) | |
| 187 | + .status(ImMessageStatusEnum.UNREAD.getStatus()) | |
| 188 | + .sendTime(LocalDateTime.now()).build(); // 刚发送,5 分钟内 | |
| 189 | + when(imPrivateMessageMapper.selectById(10L)).thenReturn(message); | |
| 190 | + when(imPrivateMessageMapper.updateById(any(ImPrivateMessageDO.class))).thenReturn(1); | |
| 191 | + when(imPrivateMessageMapper.insert(any(ImPrivateMessageDO.class))).thenReturn(1); | |
| 189 | 192 | |
| 190 | - // 断言 | |
| 191 | - verify(imPrivateMessageMapper).updateById(any(ImPrivateMessageDO.class)); | |
| 192 | - // 验证推送了 RECALL 事件(给接收方和发送方) | |
| 193 | - verify(webSocketMessageSender, times(2)).sendObject(anyInt(), anyLong(), anyString(), any()); | |
| 193 | + // 调用 | |
| 194 | + ImPrivateMessageDO result = privateMessageService.recallPrivateMessage(1L, 10L); | |
| 195 | + | |
| 196 | + // 断言:返回 TIP_TEXT 消息 | |
| 197 | + assertNotNull(result); | |
| 198 | + // 验证:更新原消息状态 + 插入 tipMessage | |
| 199 | + verify(imPrivateMessageMapper).updateById(any(ImPrivateMessageDO.class)); | |
| 200 | + verify(imPrivateMessageMapper).insert(any(ImPrivateMessageDO.class)); | |
| 201 | + // 验证推送了消息(给接收方和发送方) | |
| 202 | + verify(webSocketMessageSender, times(2)).sendObject(anyInt(), anyLong(), anyString(), any()); | |
| 203 | + } | |
| 194 | 204 | } |
| 195 | 205 | |
| 196 | 206 | @Test | ... | ... |
yudao-module-im/yudao-module-im-biz/src/test/resources/application-unit-test.yaml
0 → 100644
| 1 | +spring: | |
| 2 | + main: | |
| 3 | + lazy-initialization: true | |
| 4 | + banner-mode: off | |
| 5 | + | |
| 6 | +--- #################### 数据库相关配置 #################### | |
| 7 | + | |
| 8 | +spring: | |
| 9 | + datasource: | |
| 10 | + name: ruoyi-vue-pro | |
| 11 | + url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value; | |
| 12 | + driver-class-name: org.h2.Driver | |
| 13 | + username: sa | |
| 14 | + password: | |
| 15 | + druid: | |
| 16 | + async-init: true | |
| 17 | + initial-size: 1 | |
| 18 | + sql: | |
| 19 | + init: | |
| 20 | + schema-locations: classpath:/sql/create_tables.sql | |
| 21 | + encoding: UTF-8 | |
| 22 | + | |
| 23 | + data: | |
| 24 | + redis: | |
| 25 | + host: 127.0.0.1 | |
| 26 | + port: 16379 | |
| 27 | + database: 0 | |
| 28 | + | |
| 29 | +mybatis-plus: | |
| 30 | + lazy-initialization: true | |
| 31 | + type-aliases-package: ${yudao.info.base-package}.module.*.dal.dataobject | |
| 32 | + | |
| 33 | +--- #################### 芋道相关配置 #################### | |
| 34 | + | |
| 35 | +yudao: | |
| 36 | + info: | |
| 37 | + base-package: cn.iocoder.yudao | ... | ... |
yudao-module-im/yudao-module-im-biz/src/test/resources/sql/clean.sql
0 → 100644
| 1 | +DELETE FROM "im_private_message"; | ... | ... |
yudao-module-im/yudao-module-im-biz/src/test/resources/sql/create_tables.sql
0 → 100644
| 1 | +CREATE TABLE IF NOT EXISTS "im_private_message" ( | |
| 2 | + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY COMMENT '编号', | |
| 3 | + "client_message_id" varchar(64) DEFAULT NULL COMMENT '客户端消息编号', | |
| 4 | + "sender_id" bigint NOT NULL COMMENT '发送人编号', | |
| 5 | + "receiver_id" bigint NOT NULL COMMENT '接收人编号', | |
| 6 | + "type" tinyint NOT NULL COMMENT '消息类型', | |
| 7 | + "content" varchar(4096) DEFAULT NULL COMMENT '消息内容', | |
| 8 | + "status" tinyint NOT NULL COMMENT '消息状态', | |
| 9 | + "send_time" timestamp NOT NULL COMMENT '发送时间', | |
| 10 | + "creator" varchar(64) DEFAULT '', | |
| 11 | + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, | |
| 12 | + "updater" varchar(64) DEFAULT '', | |
| 13 | + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, | |
| 14 | + "deleted" bit NOT NULL DEFAULT FALSE, | |
| 15 | + "tenant_id" bigint NOT NULL DEFAULT 0, | |
| 16 | + PRIMARY KEY ("id") | |
| 17 | +) COMMENT 'IM 私聊消息表'; | ... | ... |