From 2f084bd5e6bd6dbe5789ab3459db13647e6c9cd2 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 21 Apr 2026 20:10:52 +0800 Subject: [PATCH] ✨ feat(message): 优化群聊的 pullGroupMessageList 方法,解决空分页的风险(pro 增强) --- yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java | 33 +++++++++++++++------------------ yudao-module-im/yudao-module-im-biz/src/test/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImplTest.java | 6 ++++++ 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java index e41b718..5a7cc89 100644 --- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java +++ b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java @@ -134,7 +134,7 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { } /** - * 拉取当前仍在群主路径的消息。 + * 拉取当前仍在群的主路径消息。 * * 仅当首批消息过滤后仍无可见消息时,才按消息最大 id 继续向后探测, * 直到命中可见消息或确认该来源已耗尽。 @@ -410,24 +410,18 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { if (CollUtil.isEmpty(visibleUserIds)) { continue; } - int readCount = 0; - for (Long uid : visibleUserIds) { - Long readMaxMessageId = allPositions.get(uid); - if (readMaxMessageId != null && readMaxMessageId >= message.getId()) { - readCount++; - } - } + int readCount = getSumValue(visibleUserIds, + uid -> allPositions.getOrDefault(uid, -1L) >= message.getId() ? 1 : null, + Integer::sum, 0); // 2.1.2 全部已读 → 标记回执完成 - boolean allRead = readCount >= visibleUserIds.size(); - Integer newReceiptStatus = allRead - ? ImGroupMessageReceiptStatusEnum.DONE.getStatus() - : ImGroupMessageReceiptStatusEnum.PENDING.getStatus(); - if (allRead) { + Integer newReceiptStatus = ImGroupMessageReceiptStatusEnum.PENDING.getStatus(); + if (readCount >= visibleUserIds.size()) { + newReceiptStatus = ImGroupMessageReceiptStatusEnum.DONE.getStatus(); groupMessageMapper.updateById(new ImGroupMessageDO().setId(message.getId()) .setReceiptStatus(newReceiptStatus)); } - // 224 发送 RECEIPT 事件给消息发送方(只有 ta 关心已读进度) + // 2.2 发送 RECEIPT 事件给消息发送方(只有 ta 关心已读进度) ImGroupMessageDTO receiptDTO = ImGroupMessageDTO.ofReceipt( message.getId(), groupId, readCount, newReceiptStatus); webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), message.getSenderId(), @@ -472,10 +466,13 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { && member.getQuitTime() != null && msg.getSendTime().isAfter(member.getQuitTime())) { return false; } - // 3. 存在定向接收列表且当前用户既不在列表中也不是发送者 → 不可见 - return !CollUtil.isNotEmpty(msg.getReceiverUserIds()) - || msg.getReceiverUserIds().contains(userId) - || !ObjUtil.notEqual(msg.getSenderId(), userId); + // 3.1 无定向接收列表 → 全员可见 + if (CollUtil.isEmpty(msg.getReceiverUserIds())) { + return true; + } + // 3.2 当前用户在定向列表中,或本人即发送者 → 可见 + return msg.getReceiverUserIds().contains(userId) + || ObjUtil.equal(msg.getSenderId(), userId); } /** diff --git a/yudao-module-im/yudao-module-im-biz/src/test/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImplTest.java b/yudao-module-im/yudao-module-im-biz/src/test/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImplTest.java index 12b94e1..3ed21e2 100644 --- a/yudao-module-im/yudao-module-im-biz/src/test/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImplTest.java +++ b/yudao-module-im/yudao-module-im-biz/src/test/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImplTest.java @@ -186,6 +186,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @Test public void testPullMessages_activeQueryRetriesWhenFirstBatchInvisible() { + // 准备:首批 2 条(入群前 + 定向给别人)全部不可见,第二批命中可见消息 LocalDateTime now = LocalDateTime.now(); LocalDateTime joinTime = now.minusHours(1); ImGroupMemberDO member = ImGroupMemberDO.builder() @@ -213,8 +214,10 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { when(groupMemberService.getQuitGroupMemberListByUserId(eq(1L), any(LocalDateTime.class))) .thenReturn(List.of()); + // 调用 List result = groupMessageService.pullGroupMessageList(1L, 0L, 2); + // 断言:仅返回第二批的可见消息;两批 selectListByMinId 各被调用一次 assertEquals(1, result.size()); assertEquals(3L, result.get(0).getId()); verify(groupMessageMapper).selectListByMinId(eq(List.of(10L)), eq(0L), @@ -289,6 +292,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @Test public void testPullMessages_quitQueryKeepsOriginalMinIdWhenActiveQueryRetries() { + // 准备:主查询首批不可见触发重试(游标从 8→101),退群补齐仍使用原始 minId=8 LocalDateTime now = LocalDateTime.now(); LocalDateTime joinTime = now.minusHours(1); ImGroupMemberDO activeMember = ImGroupMemberDO.builder() @@ -335,8 +339,10 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { any(LocalDateTime.class), eq(quitTime), eq(2))) .thenReturn(List.of(quitGroupMsg)); + // 调用 List result = groupMessageService.pullGroupMessageList(1L, 8L, 2); + // 断言:退群补齐使用原始 minId=8 而非主查询重试后的 101 assertEquals(2, result.size()); assertEquals(50L, result.get(0).getId()); assertEquals(102L, result.get(1).getId()); -- libgit2 0.21.4