Commit bca66fee74f1999e482ac6f86b8d5833aaaba511
1 parent
4f9dc0e4
fix(im): 修复历史退群群未读和成员加载问题
- 已读上报使用会话末条消息编号兜底 - 历史退群群不再请求群成员列表 - 群聊 read 放开当前成员校验,保留可见性校验 - Vben 群详情补齐 joinStatus - 补充退群群已读边界测试
Showing
2 changed files
with
37 additions
and
23 deletions
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java
| ... | ... | @@ -262,9 +262,7 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { |
| 262 | 262 | throw exception(MESSAGE_GROUP_READ_DISABLED); |
| 263 | 263 | } |
| 264 | 264 | Assert.notNull(messageId, "已读消息编号不能为空"); |
| 265 | - // 1.1 校验用户在群中(权限校验) | |
| 266 | - groupMemberService.validateMemberInGroup(groupId, userId); | |
| 267 | - // 1.2 校验消息属于当前群,且对当前用户可见(按快照) | |
| 265 | + // 1. 校验消息属于当前群,且对当前用户可见(按快照) | |
| 268 | 266 | ImGroupMessageDO message = groupMessageMapper.selectById(messageId); |
| 269 | 267 | if (message == null |
| 270 | 268 | || ObjUtil.notEqual(message.getGroupId(), groupId) | ... | ... |
yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImplTest.java
| ... | ... | @@ -460,15 +460,17 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { |
| 460 | 460 | // ========== 群已读测试 ========== |
| 461 | 461 | |
| 462 | 462 | @Test |
| 463 | - public void testReadMessages_notInGroup() { | |
| 464 | - // 准备:当前用户不在群中 | |
| 465 | - when(groupMemberService.validateMemberInGroup(10L, 1L)) | |
| 466 | - .thenThrow(new ServiceException(GROUP_MEMBER_NOT_IN_GROUP.getCode(), GROUP_MEMBER_NOT_IN_GROUP.getMsg())); | |
| 463 | + public void testReadMessages_messageInvisible() { | |
| 464 | + // 准备:消息不在用户接收快照内 | |
| 465 | + when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder() | |
| 466 | + .id(100L).groupId(10L).senderId(2L).receiverUserIds(List.of(2L)) | |
| 467 | + .sendTime(LocalDateTime.now()).build()); | |
| 467 | 468 | |
| 468 | - // 调用并断言:越权校验 | |
| 469 | + // 调用并断言 | |
| 469 | 470 | ServiceException exception = assertThrows(ServiceException.class, |
| 470 | 471 | () -> groupMessageService.readGroupMessages(1L, 10L, 100L)); |
| 471 | - assertEquals(GROUP_MEMBER_NOT_IN_GROUP.getCode(), exception.getCode()); | |
| 472 | + assertEquals(MESSAGE_NOT_IN_GROUP.getCode(), exception.getCode()); | |
| 473 | + verify(conversationReadService, never()).updateConversationReadPosition(anyLong(), anyInt(), anyLong(), anyLong()); | |
| 472 | 474 | } |
| 473 | 475 | |
| 474 | 476 | @Test |
| ... | ... | @@ -636,11 +638,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { |
| 636 | 638 | springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupMessageServiceImpl.class))) |
| 637 | 639 | .thenReturn(groupMessageService); |
| 638 | 640 | |
| 639 | - // 准备:用户在群;已读位置从 5 前进到 100 | |
| 640 | - ImGroupMemberDO member = ImGroupMemberDO.builder() | |
| 641 | - .groupId(10L).userId(1L) | |
| 642 | - .status(CommonStatusEnum.ENABLE.getStatus()).build(); | |
| 643 | - when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn(member); | |
| 641 | + // 准备:消息对用户可见;已读位置从 5 前进到 100 | |
| 644 | 642 | when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder() |
| 645 | 643 | .id(100L).groupId(10L).senderId(2L).receiverUserIds(List.of(1L)) |
| 646 | 644 | .sendTime(LocalDateTime.now()).build()); |
| ... | ... | @@ -657,6 +655,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { |
| 657 | 655 | // 断言:已读位置更新 + READ 事件 |
| 658 | 656 | verify(conversationReadService).updateConversationReadPosition(1L, ImConversationTypeEnum.GROUP.getType(), 10L, 100L); |
| 659 | 657 | verify(imWebSocketService).sendNotificationAsync(eq(1L), anyInt(), anyInt(), any()); |
| 658 | + verify(groupMemberService, never()).validateMemberInGroup(10L, 1L); | |
| 660 | 659 | } |
| 661 | 660 | } |
| 662 | 661 | |
| ... | ... | @@ -715,10 +714,6 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { |
| 715 | 714 | @Test |
| 716 | 715 | public void testReadGroupMessages_cursorAlreadyAhead() { |
| 717 | 716 | // 准备:已读游标已 >= 目标,直接返回 |
| 718 | - ImGroupMemberDO member = ImGroupMemberDO.builder() | |
| 719 | - .groupId(10L).userId(1L) | |
| 720 | - .status(CommonStatusEnum.ENABLE.getStatus()).build(); | |
| 721 | - when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn(member); | |
| 722 | 717 | when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder() |
| 723 | 718 | .id(100L).groupId(10L).senderId(2L).receiverUserIds(List.of(1L)) |
| 724 | 719 | .sendTime(LocalDateTime.now()).build()); |
| ... | ... | @@ -738,11 +733,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { |
| 738 | 733 | |
| 739 | 734 | @Test |
| 740 | 735 | public void testReadGroupMessages_messageNotInGroup() { |
| 741 | - // 准备:用户在群,但 messageId 属于其它群 | |
| 742 | - ImGroupMemberDO member = ImGroupMemberDO.builder() | |
| 743 | - .groupId(10L).userId(1L) | |
| 744 | - .status(CommonStatusEnum.ENABLE.getStatus()).build(); | |
| 745 | - when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn(member); | |
| 736 | + // 准备:messageId 属于其它群 | |
| 746 | 737 | when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder() |
| 747 | 738 | .id(100L).groupId(20L).senderId(2L).sendTime(LocalDateTime.now()).build()); |
| 748 | 739 | |
| ... | ... | @@ -753,6 +744,31 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { |
| 753 | 744 | verify(conversationReadService, never()).updateConversationReadPosition(anyLong(), anyInt(), anyLong(), anyLong()); |
| 754 | 745 | } |
| 755 | 746 | |
| 747 | + @Test | |
| 748 | + public void testReadGroupMessages_quitMemberVisibleMessage_success() { | |
| 749 | + try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) { | |
| 750 | + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupMessageServiceImpl.class))) | |
| 751 | + .thenReturn(groupMessageService); | |
| 752 | + | |
| 753 | + // 准备:历史消息的接收快照包含用户 | |
| 754 | + when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder() | |
| 755 | + .id(100L).groupId(10L).senderId(2L).receiverUserIds(List.of(1L, 2L)) | |
| 756 | + .sendTime(LocalDateTime.now().minusMinutes(1)).build()); | |
| 757 | + when(conversationReadService.getConversationReadMessageId(1L, ImConversationTypeEnum.GROUP.getType(), 10L)) | |
| 758 | + .thenReturn(0L); | |
| 759 | + when(conversationReadService.updateConversationReadPosition(1L, ImConversationTypeEnum.GROUP.getType(), 10L, 100L)) | |
| 760 | + .thenReturn(true); | |
| 761 | + when(groupMessageMapper.selectListByGroupIdAndPendingReceipt(10L, 0L, 100L)).thenReturn(List.of()); | |
| 762 | + | |
| 763 | + // 调用 | |
| 764 | + groupMessageService.readGroupMessages(1L, 10L, 100L); | |
| 765 | + | |
| 766 | + // 断言:不校验当前在群,直接推进已读位置 | |
| 767 | + verify(groupMemberService, never()).validateMemberInGroup(10L, 1L); | |
| 768 | + verify(conversationReadService).updateConversationReadPosition(1L, ImConversationTypeEnum.GROUP.getType(), 10L, 100L); | |
| 769 | + } | |
| 770 | + } | |
| 771 | + | |
| 756 | 772 | // ========== 回执:DONE 状态迁移 ========== |
| 757 | 773 | |
| 758 | 774 | @Test | ... | ... |