Commit 2f084bd5e6bd6dbe5789ab3459db13647e6c9cd2

Authored by YunaiV
1 parent 99121a25

✨ feat(message): 优化群聊的 pullGroupMessageList 方法,解决空分页的风险(pro 增强)

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 {
134 134 }
135 135  
136 136 /**
137   - * 拉取当前仍在群主路径的消息。
  137 + * 拉取当前仍在群的主路径消息。
138 138 *
139 139 * 仅当首批消息过滤后仍无可见消息时,才按消息最大 id 继续向后探测,
140 140 * 直到命中可见消息或确认该来源已耗尽。
... ... @@ -410,24 +410,18 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
410 410 if (CollUtil.isEmpty(visibleUserIds)) {
411 411 continue;
412 412 }
413   - int readCount = 0;
414   - for (Long uid : visibleUserIds) {
415   - Long readMaxMessageId = allPositions.get(uid);
416   - if (readMaxMessageId != null && readMaxMessageId >= message.getId()) {
417   - readCount++;
418   - }
419   - }
  413 + int readCount = getSumValue(visibleUserIds,
  414 + uid -> allPositions.getOrDefault(uid, -1L) >= message.getId() ? 1 : null,
  415 + Integer::sum, 0);
420 416 // 2.1.2 全部已读 → 标记回执完成
421   - boolean allRead = readCount >= visibleUserIds.size();
422   - Integer newReceiptStatus = allRead
423   - ? ImGroupMessageReceiptStatusEnum.DONE.getStatus()
424   - : ImGroupMessageReceiptStatusEnum.PENDING.getStatus();
425   - if (allRead) {
  417 + Integer newReceiptStatus = ImGroupMessageReceiptStatusEnum.PENDING.getStatus();
  418 + if (readCount >= visibleUserIds.size()) {
  419 + newReceiptStatus = ImGroupMessageReceiptStatusEnum.DONE.getStatus();
426 420 groupMessageMapper.updateById(new ImGroupMessageDO().setId(message.getId())
427 421 .setReceiptStatus(newReceiptStatus));
428 422 }
429 423  
430   - // 224 发送 RECEIPT 事件给消息发送方(只有 ta 关心已读进度)
  424 + // 2.2 发送 RECEIPT 事件给消息发送方(只有 ta 关心已读进度)
431 425 ImGroupMessageDTO receiptDTO = ImGroupMessageDTO.ofReceipt(
432 426 message.getId(), groupId, readCount, newReceiptStatus);
433 427 webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), message.getSenderId(),
... ... @@ -472,10 +466,13 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
472 466 && member.getQuitTime() != null && msg.getSendTime().isAfter(member.getQuitTime())) {
473 467 return false;
474 468 }
475   - // 3. 存在定向接收列表且当前用户既不在列表中也不是发送者 → 不可见
476   - return !CollUtil.isNotEmpty(msg.getReceiverUserIds())
477   - || msg.getReceiverUserIds().contains(userId)
478   - || !ObjUtil.notEqual(msg.getSenderId(), userId);
  469 + // 3.1 无定向接收列表 → 全员可见
  470 + if (CollUtil.isEmpty(msg.getReceiverUserIds())) {
  471 + return true;
  472 + }
  473 + // 3.2 当前用户在定向列表中,或本人即发送者 → 可见
  474 + return msg.getReceiverUserIds().contains(userId)
  475 + || ObjUtil.equal(msg.getSenderId(), userId);
479 476 }
480 477  
481 478 /**
... ...
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 {
186 186  
187 187 @Test
188 188 public void testPullMessages_activeQueryRetriesWhenFirstBatchInvisible() {
  189 + // 准备:首批 2 条(入群前 + 定向给别人)全部不可见,第二批命中可见消息
189 190 LocalDateTime now = LocalDateTime.now();
190 191 LocalDateTime joinTime = now.minusHours(1);
191 192 ImGroupMemberDO member = ImGroupMemberDO.builder()
... ... @@ -213,8 +214,10 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
213 214 when(groupMemberService.getQuitGroupMemberListByUserId(eq(1L), any(LocalDateTime.class)))
214 215 .thenReturn(List.of());
215 216  
  217 + // 调用
216 218 List<ImGroupMessageDO> result = groupMessageService.pullGroupMessageList(1L, 0L, 2);
217 219  
  220 + // 断言:仅返回第二批的可见消息;两批 selectListByMinId 各被调用一次
218 221 assertEquals(1, result.size());
219 222 assertEquals(3L, result.get(0).getId());
220 223 verify(groupMessageMapper).selectListByMinId(eq(List.of(10L)), eq(0L),
... ... @@ -289,6 +292,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
289 292  
290 293 @Test
291 294 public void testPullMessages_quitQueryKeepsOriginalMinIdWhenActiveQueryRetries() {
  295 + // 准备:主查询首批不可见触发重试(游标从 8→101),退群补齐仍使用原始 minId=8
292 296 LocalDateTime now = LocalDateTime.now();
293 297 LocalDateTime joinTime = now.minusHours(1);
294 298 ImGroupMemberDO activeMember = ImGroupMemberDO.builder()
... ... @@ -335,8 +339,10 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
335 339 any(LocalDateTime.class), eq(quitTime), eq(2)))
336 340 .thenReturn(List.of(quitGroupMsg));
337 341  
  342 + // 调用
338 343 List<ImGroupMessageDO> result = groupMessageService.pullGroupMessageList(1L, 8L, 2);
339 344  
  345 + // 断言:退群补齐使用原始 minId=8 而非主查询重试后的 101
340 346 assertEquals(2, result.size());
341 347 assertEquals(50L, result.get(0).getId());
342 348 assertEquals(102L, result.get(1).getId());
... ...