Commit 2f084bd5e6bd6dbe5789ab3459db13647e6c9cd2
1 parent
99121a25
✨ feat(message): 优化群聊的 pullGroupMessageList 方法,解决空分页的风险(pro 增强)
Showing
2 changed files
with
21 additions
and
18 deletions
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()); | ... | ... |