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,9 +262,7 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { | ||
| 262 | throw exception(MESSAGE_GROUP_READ_DISABLED); | 262 | throw exception(MESSAGE_GROUP_READ_DISABLED); |
| 263 | } | 263 | } |
| 264 | Assert.notNull(messageId, "已读消息编号不能为空"); | 264 | Assert.notNull(messageId, "已读消息编号不能为空"); |
| 265 | - // 1.1 校验用户在群中(权限校验) | ||
| 266 | - groupMemberService.validateMemberInGroup(groupId, userId); | ||
| 267 | - // 1.2 校验消息属于当前群,且对当前用户可见(按快照) | 265 | + // 1. 校验消息属于当前群,且对当前用户可见(按快照) |
| 268 | ImGroupMessageDO message = groupMessageMapper.selectById(messageId); | 266 | ImGroupMessageDO message = groupMessageMapper.selectById(messageId); |
| 269 | if (message == null | 267 | if (message == null |
| 270 | || ObjUtil.notEqual(message.getGroupId(), groupId) | 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,15 +460,17 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { | ||
| 460 | // ========== 群已读测试 ========== | 460 | // ========== 群已读测试 ========== |
| 461 | 461 | ||
| 462 | @Test | 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 | ServiceException exception = assertThrows(ServiceException.class, | 470 | ServiceException exception = assertThrows(ServiceException.class, |
| 470 | () -> groupMessageService.readGroupMessages(1L, 10L, 100L)); | 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 | @Test | 476 | @Test |
| @@ -636,11 +638,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { | @@ -636,11 +638,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { | ||
| 636 | springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupMessageServiceImpl.class))) | 638 | springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupMessageServiceImpl.class))) |
| 637 | .thenReturn(groupMessageService); | 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 | when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder() | 642 | when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder() |
| 645 | .id(100L).groupId(10L).senderId(2L).receiverUserIds(List.of(1L)) | 643 | .id(100L).groupId(10L).senderId(2L).receiverUserIds(List.of(1L)) |
| 646 | .sendTime(LocalDateTime.now()).build()); | 644 | .sendTime(LocalDateTime.now()).build()); |
| @@ -657,6 +655,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { | @@ -657,6 +655,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { | ||
| 657 | // 断言:已读位置更新 + READ 事件 | 655 | // 断言:已读位置更新 + READ 事件 |
| 658 | verify(conversationReadService).updateConversationReadPosition(1L, ImConversationTypeEnum.GROUP.getType(), 10L, 100L); | 656 | verify(conversationReadService).updateConversationReadPosition(1L, ImConversationTypeEnum.GROUP.getType(), 10L, 100L); |
| 659 | verify(imWebSocketService).sendNotificationAsync(eq(1L), anyInt(), anyInt(), any()); | 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,10 +714,6 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { | ||
| 715 | @Test | 714 | @Test |
| 716 | public void testReadGroupMessages_cursorAlreadyAhead() { | 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 | when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder() | 717 | when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder() |
| 723 | .id(100L).groupId(10L).senderId(2L).receiverUserIds(List.of(1L)) | 718 | .id(100L).groupId(10L).senderId(2L).receiverUserIds(List.of(1L)) |
| 724 | .sendTime(LocalDateTime.now()).build()); | 719 | .sendTime(LocalDateTime.now()).build()); |
| @@ -738,11 +733,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { | @@ -738,11 +733,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { | ||
| 738 | 733 | ||
| 739 | @Test | 734 | @Test |
| 740 | public void testReadGroupMessages_messageNotInGroup() { | 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 | when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder() | 737 | when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder() |
| 747 | .id(100L).groupId(20L).senderId(2L).sendTime(LocalDateTime.now()).build()); | 738 | .id(100L).groupId(20L).senderId(2L).sendTime(LocalDateTime.now()).build()); |
| 748 | 739 | ||
| @@ -753,6 +744,31 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { | @@ -753,6 +744,31 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { | ||
| 753 | verify(conversationReadService, never()).updateConversationReadPosition(anyLong(), anyInt(), anyLong(), anyLong()); | 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 | // ========== 回执:DONE 状态迁移 ========== | 772 | // ========== 回执:DONE 状态迁移 ========== |
| 757 | 773 | ||
| 758 | @Test | 774 | @Test |