From ae36d1ad68c5ff7f5e84cb0e8e20bc30fb844e42 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Fri, 1 May 2026 18:03:05 +0800 Subject: [PATCH] ✨ feat(im): 增加【消息引用】的功能 --- yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java | 18 ++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImGroupMessageController.java | 7 ++++--- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java | 1 + yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java | 2 +- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java | 47 +++++++++++++++++++++++++++++++++++++++++++---- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java | 43 ++++++++++++++++++++++++++++++++++++++++++- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/AudioMessage.java | 5 +++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/FileMessage.java | 5 +++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/ImageMessage.java | 5 +++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/QuoteMessage.java | 45 +++++++++++++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/TextMessage.java | 5 +++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/VideoMessage.java | 5 +++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/util/ImMessageUtils.java | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImplTest.java | 2 +- yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImplTest.java | 12 ++++++------ 15 files changed, 268 insertions(+), 16 deletions(-) create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/QuoteMessage.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/util/ImMessageUtils.java diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java index 7711ae0..111cf03 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java @@ -22,6 +22,7 @@ import java.lang.reflect.Type; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import java.util.Map; /** * JSON 工具类 @@ -173,6 +174,23 @@ public class JsonUtils { } } + /** + * 解析 JSON 字符串成 Map,空字符串或解析失败返回 null + * + * @param text JSON 字符串 + * @return Map 对象 + */ + public static Map parseMap(String text) { + if (StrUtil.isEmpty(text)) { + return null; + } + try { + return objectMapper.readValue(text, new TypeReference>() {}); + } catch (IOException e) { + return null; + } + } + public static List parseArray(String text, Class clazz) { if (StrUtil.isEmpty(text)) { return new ArrayList<>(); diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImGroupMessageController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImGroupMessageController.java index 26bffe7..d3e5608 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImGroupMessageController.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImGroupMessageController.java @@ -64,13 +64,14 @@ public class ImGroupMessageController { return success(BeanUtils.toBean(message, ImGroupMessageRespVO.class)); } + // TODO @AI:改成 get-read-user-ids @GetMapping("/read-users") @Operation(summary = "获取群消息已读用户列表") @Parameter(name = "groupId", description = "群编号", required = true, example = "1") @Parameter(name = "messageId", description = "消息编号", required = true, example = "1") - public CommonResult> getGroupReadUsers(@RequestParam("groupId") Long groupId, - @RequestParam("messageId") Long messageId) { - return success(groupMessageService.getGroupReadUsers(getLoginUserId(), groupId, messageId)); + public CommonResult> getGroupReadUserIds(@RequestParam("groupId") Long groupId, + @RequestParam("messageId") Long messageId) { + return success(groupMessageService.getGroupReadUserIds(getLoginUserId(), groupId, messageId)); } @GetMapping("/list") diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java index 56c4289..32cfbf4 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java @@ -16,6 +16,7 @@ public interface ErrorCodeConstants { ErrorCode MESSAGE_SENSITIVE_WORD_BLOCKED = new ErrorCode(1_040_300_004, "消息包含敏感词,无法发送"); ErrorCode MESSAGE_PULL_SIZE_EXCEEDED = new ErrorCode(1_040_300_005, "单次拉取消息数量不能超过 {} 条"); ErrorCode MESSAGE_RECALL_TIMEOUT = new ErrorCode(1_040_300_007, "超过 {} 分钟的消息无法撤回"); + ErrorCode MESSAGE_QUOTE_INVALID = new ErrorCode(1_040_300_008, "引用的消息不可用"); // ========== 群 (1-040-400-000) ========== ErrorCode GROUP_NOT_EXISTS = new ErrorCode(1_040_400_000, "群不存在"); diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java index 3551018..b8db3ac 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java @@ -62,7 +62,7 @@ public interface ImGroupMessageService { * @param messageId 消息编号 * @return 已读用户编号列表 */ - List getGroupReadUsers(Long userId, Long groupId, Long messageId); + List getGroupReadUserIds(Long userId, Long groupId, Long messageId); /** * 查询群聊历史消息(游标拉取) diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java index 0590ce4..f0adfa3 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java @@ -24,7 +24,9 @@ import cn.iocoder.yudao.module.im.service.group.ImGroupService; import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; import cn.iocoder.yudao.module.im.service.websocket.dto.ImGroupMessageDTO; +import cn.iocoder.yudao.module.im.service.websocket.dto.message.QuoteMessage; import cn.iocoder.yudao.module.im.service.websocket.dto.message.RecallMessage; +import cn.iocoder.yudao.module.im.util.ImMessageUtils; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Async; @@ -84,13 +86,15 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { } // 1.2 校验群存在、发送人仍在群中 groupService.validateGroupExists(reqVO.getGroupId()); - groupMemberService.validateMemberInGroup(reqVO.getGroupId(), senderId); + ImGroupMemberDO senderMember = groupMemberService.validateMemberInGroup(reqVO.getGroupId(), senderId); // 1.3 文本消息敏感词过滤 if (ImMessageTypeEnum.TEXT.getType().equals(reqVO.getType())) { sensitiveWordService.validateText(reqVO.getContent()); } - // 2. 构建并保存消息 + // 2.1 引用 quote 消息规范化 + reqVO.setContent(normalizeQuoteContent(reqVO, senderMember)); + // 2.2 构建并保存消息 ImGroupMessageDO message = BeanUtils.toBean(reqVO, ImGroupMessageDO.class, m -> m .setSenderId(senderId).setStatus(ImMessageStatusEnum.UNREAD.getStatus()).setSendTime(LocalDateTime.now()) .setReceiptStatus(Boolean.TRUE.equals(reqVO.getReceipt()) @@ -319,9 +323,8 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { return recallMessage; } - // TODO @芋艿:这个方法在优化下; @Override - public List getGroupReadUsers(Long userId, Long groupId, Long messageId) { + public List getGroupReadUserIds(Long userId, Long groupId, Long messageId) { // 1.1 校验用户在群中(权限校验) groupMemberService.validateMemberInGroup(groupId, userId); // 1.2 获取消息 @@ -433,6 +436,42 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { // ========== 私有工具方法 ========== /** + * 群聊引用消息规范化 + * + * @param reqVO 发送请求 + * @param senderMember 发送人成员 + * @return 规范化后的 content + */ + private String normalizeQuoteContent(ImGroupMessageSendReqVO reqVO, ImGroupMemberDO senderMember) { + // 解析客户端 content 里的 quote.messageId + Long quoteMessageId = ImMessageUtils.parseQuoteMessageId(reqVO.getContent()); + + // 情况一:没有 quoteMessageId,直接 remove 掉 content 里可能伪造的 quote 字段 + if (quoteMessageId == null) { + return ImMessageUtils.removeQuote(reqVO.getContent()); + } + + // 情况二:有 quoteMessageId,加载原消息并校验 + ImGroupMessageDO original = groupMessageMapper.selectById(quoteMessageId); + if (original == null + || ImMessageStatusEnum.RECALL.getStatus().equals(original.getStatus())) { + throw exception(MESSAGE_QUOTE_INVALID); + } + // 校验是同群 + if (ObjUtil.notEqual(original.getGroupId(), reqVO.getGroupId())) { + throw exception(MESSAGE_QUOTE_INVALID); + } + // 校验对发送人可见(入群时间 / 退群时间 / 定向接收) + if (!isMessageVisible(original, senderMember, senderMember.getUserId())) { + throw exception(MESSAGE_QUOTE_INVALID); + } + // 构建 quote 对象并注入 content + QuoteMessage quote = ImMessageUtils.buildQuote(original.getId(), + original.getSenderId(), original.getType(), original.getContent()); + return ImMessageUtils.appendQuote(reqVO.getContent(), quote); + } + + /** * 校验群聊消息存在 * * @param messageId 消息编号 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 a9b8fd6..ea9ccb8 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 @@ -17,7 +17,9 @@ import cn.iocoder.yudao.module.im.service.friend.ImFriendService; import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; 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.message.QuoteMessage; import cn.iocoder.yudao.module.im.service.websocket.dto.message.RecallMessage; +import cn.iocoder.yudao.module.im.util.ImMessageUtils; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -71,7 +73,10 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { sensitiveWordService.validateText(reqVO.getContent()); } - // 2. 构建并保存消息 + + // 2.1 引用 quote 消息规范化 + reqVO.setContent(normalizeQuoteContent(reqVO, senderId)); + // 2.2 构建并保存消息 ImPrivateMessageDO message = BeanUtils.toBean(reqVO, ImPrivateMessageDO.class, m -> m .setSenderId(senderId).setStatus(ImMessageStatusEnum.UNREAD.getStatus()).setSendTime(LocalDateTime.now())); privateMessageMapper.insert(message); @@ -83,6 +88,42 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { return message; } + /** + * 私聊引用消息规范化 + * + * @param reqVO 发送请求 + * @param senderId 发送人编号 + * @return 规范化后的 content + */ + private String normalizeQuoteContent(ImPrivateMessageSendReqVO reqVO, Long senderId) { + // 解析客户端 content 里的 quote.messageId + Long quoteMessageId = ImMessageUtils.parseQuoteMessageId(reqVO.getContent()); + + // 情况一:没有 quoteMessageId,直接 remove 掉 content 里可能伪造的 quote 字段 + if (quoteMessageId == null) { + return ImMessageUtils.removeQuote(reqVO.getContent()); + } + + // 情况二:有 quoteMessageId,加载原消息并校验 + ImPrivateMessageDO original = privateMessageMapper.selectById(quoteMessageId); + if (original == null + || ImMessageStatusEnum.RECALL.getStatus().equals(original.getStatus())) { + throw exception(MESSAGE_QUOTE_INVALID); + } + // 校验是同对话 + boolean sameConversation = (ObjUtil.equal(original.getSenderId(), senderId) // 发送人是当前用户,接收人是对方 + && ObjUtil.equal(original.getReceiverId(), reqVO.getReceiverId())) + || (ObjUtil.equal(original.getSenderId(), reqVO.getReceiverId()) // 发送人是对方,接收人是当前用户 + && ObjUtil.equal(original.getReceiverId(), senderId)); + if (!sameConversation) { + throw exception(MESSAGE_QUOTE_INVALID); + } + // 构建 quote 对象并注入 content + QuoteMessage quote = ImMessageUtils.buildQuote(original.getId(), + original.getSenderId(), original.getType(), original.getContent()); + return ImMessageUtils.appendQuote(reqVO.getContent(), quote); + } + @Override public List pullPrivateMessageList(Long userId, Long minId, Integer size) { if (size > MESSAGE_MAX_PULL_SIZE) { diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/AudioMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/AudioMessage.java index 884e1b0..dd37cc0 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/AudioMessage.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/AudioMessage.java @@ -21,4 +21,9 @@ public class AudioMessage { */ private Long size; + /** + * 引用消息(可选) + */ + private QuoteMessage quote; + } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/FileMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/FileMessage.java index 7965179..7feb9ff 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/FileMessage.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/FileMessage.java @@ -25,4 +25,9 @@ public class FileMessage { */ private String type; + /** + * 引用消息(可选) + */ + private QuoteMessage quote; + } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/ImageMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/ImageMessage.java index c3d272d..bc8e41e 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/ImageMessage.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/ImageMessage.java @@ -29,4 +29,9 @@ public class ImageMessage { */ private Long size; + /** + * 引用消息(可选) + */ + private QuoteMessage quote; + } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/QuoteMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/QuoteMessage.java new file mode 100644 index 0000000..8c6c186 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/QuoteMessage.java @@ -0,0 +1,45 @@ +package cn.iocoder.yudao.module.im.service.websocket.dto.message; + +import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 引用消息内容 + *

+ * 客户端在 content.quote 里写入 messageId 表达"引用了哪条消息",服务端反查原消息后重算 QuoteMessage 覆盖客户端写入, + * 避免伪造发送人 / 类型 / 摘要 + * + * @see TextMessage 等等消息内容里 quote 字段 + */ +@Data +@Accessors(chain = true) +public class QuoteMessage { + + /** + * content JSON 里 quote 字段名 + */ + public static final String FIELD_NAME = "quote"; + + /** + * 被引用消息编号 + */ + private Long messageId; + /** + * 被引用消息发送人编号 + *

+ * 关联 AdminUserDO 的 id 字段 + */ + private Long senderId; + /** + * 被引用消息类型 + *

+ * 枚举 {@link ImMessageTypeEnum} + */ + private Integer type; + /** + * 原消息完整 content(JSON);服务端复制时会 removeQuote 防嵌套 + */ + private String content; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/TextMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/TextMessage.java index 747fabd..9d54b7d 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/TextMessage.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/TextMessage.java @@ -13,4 +13,9 @@ public class TextMessage { */ private String content; + /** + * 引用消息(可选) + */ + private QuoteMessage quote; + } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/VideoMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/VideoMessage.java index 3aac314..a5ba53a 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/VideoMessage.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/VideoMessage.java @@ -33,4 +33,9 @@ public class VideoMessage { */ private Long size; + /** + * 引用消息(可选) + */ + private QuoteMessage quote; + } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/util/ImMessageUtils.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/util/ImMessageUtils.java new file mode 100644 index 0000000..f304030 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/util/ImMessageUtils.java @@ -0,0 +1,82 @@ +package cn.iocoder.yudao.module.im.util; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.module.im.service.websocket.dto.message.QuoteMessage; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * IM 消息内容相关工具类 + */ +public class ImMessageUtils { + + /** + * 从 content 解析客户端写入的 quote.messageId + * + * @param content 原 content(JSON) + * @return messageId,不存在 / 非法返回 null + */ + public static Long parseQuoteMessageId(String content) { + Map map = JsonUtils.parseMap(content); + if (map == null) { + return null; + } + Object quoteRaw = map.get(QuoteMessage.FIELD_NAME); + if (!(quoteRaw instanceof Map)) { + return null; + } + return Convert.toLong(((Map) quoteRaw).get("messageId")); + } + + /** + * 把 quote 写入 content 的 quote 字段 + * + * @param content 原 content(JSON) + * @param quote QuoteMessage 对象 + * @return 注入 quote 后的 content(JSON) + */ + public static String appendQuote(String content, QuoteMessage quote) { + Map map = JsonUtils.parseMap(content); + if (map == null) { + map = new LinkedHashMap<>(); + } + map.put(QuoteMessage.FIELD_NAME, quote); + return JsonUtils.toJsonString(map); + } + + /** + * 移除 content 里的 quote 字段 + * + * @param content 原 content(JSON) + * @return remove quote 后的 content(JSON) + */ + public static String removeQuote(String content) { + if (StrUtil.isBlank(content) || !StrUtil.contains(content, "\"" + QuoteMessage.FIELD_NAME + "\"")) { + return content; + } + Map map = JsonUtils.parseMap(content); + if (map == null || !map.containsKey(QuoteMessage.FIELD_NAME)) { + return content; + } + map.remove(QuoteMessage.FIELD_NAME); + return JsonUtils.toJsonString(map); + } + + /** + * 构建 QuoteMessage 对象 + * + * @param messageId 被引用消息编号 + * @param senderId 被引用消息发送人编号 + * @param type 被引用消息类型 + * @param originalContent 被引用消息原 content(JSON),服务端会 removeQuote 防止嵌套 + * @return QuoteMessage 对象 + */ + public static QuoteMessage buildQuote(Long messageId, Long senderId, Integer type, String originalContent) { + return new QuoteMessage().setMessageId(messageId).setSenderId(senderId).setType(type) + .setContent(removeQuote(originalContent)); + } + +} 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 da0a93e..ee95561 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 @@ -137,7 +137,7 @@ public class ImFriendServiceImplTest extends BaseMockitoUnitTest { @Test public void testUpdateFriend_emptyRequest() { - // 准备:muted 和 displayName 都不传 —— 短路返回,不打 SQL 也不发推送 + // 准备:muted 和 displayName 都不传 —— 直接返回,不打 SQL 也不发推送 ImFriendUpdateReqVO reqVO = new ImFriendUpdateReqVO(); reqVO.setFriendUserId(2L); ImFriendDO friend = ImFriendDO.builder().id(100L).userId(1L).friendUserId(2L) 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 9fa3d9d..5be560c 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 @@ -636,7 +636,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { } @Test - public void testGetReadUsers_withVisibleScope() { + public void testGetReadUserIds_withVisibleScope() { // 准备:用户 1 是群成员 ImGroupMemberDO currentMember = ImGroupMemberDO.builder() .groupId(10L).userId(1L) @@ -677,7 +677,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { when(groupMessageReadRedisDAO.getReadMaxMessageIdMap(10L)).thenReturn(positions); // 调用:查询 messageId=80 的已读用户 - List readUsers = groupMessageService.getGroupReadUsers(1L, 10L, 80L); + List readUsers = groupMessageService.getGroupReadUserIds(1L, 10L, 80L); // 断言: // 用户 1: 可见 + readMaxId=100>=80 → 已读 ✓ @@ -692,14 +692,14 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { } @Test - public void testGetReadUsers_notInGroup() { + public void testGetReadUserIds_notInGroup() { // 准备:当前用户不在群中 when(groupMemberService.validateMemberInGroup(10L, 99L)) .thenThrow(new ServiceException(GROUP_MEMBER_NOT_IN_GROUP.getCode(), GROUP_MEMBER_NOT_IN_GROUP.getMsg())); // 调用并断言:越权校验 ServiceException exception = assertThrows(ServiceException.class, - () -> groupMessageService.getGroupReadUsers(99L, 10L, 80L)); + () -> groupMessageService.getGroupReadUserIds(99L, 10L, 80L)); assertEquals(GROUP_MEMBER_NOT_IN_GROUP.getCode(), exception.getCode()); } @@ -787,7 +787,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { verify(groupMessageMapper, never()).insert(any(ImGroupMessageDO.class)); } - // ========== 已读:happy path + 短路 ========== + // ========== 已读:happy path + 跳过 ========== @Test public void testReadGroupMessages_success() { @@ -815,7 +815,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @Test public void testReadGroupMessages_cursorAlreadyAhead() { - // 准备:已读游标已 >= 目标,直接短路返回 + // 准备:已读游标已 >= 目标,直接返回 ImGroupMemberDO member = ImGroupMemberDO.builder() .groupId(10L).userId(1L) .status(CommonStatusEnum.ENABLE.getStatus()).build(); -- libgit2 0.21.4