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