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,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()); |