Commit bca66fee74f1999e482ac6f86b8d5833aaaba511

Authored by YunaiV
1 parent 4f9dc0e4

fix(im): 修复历史退群群未读和成员加载问题

- 已读上报使用会话末条消息编号兜底
- 历史退群群不再请求群成员列表
- 群聊 read 放开当前成员校验,保留可见性校验
- Vben 群详情补齐 joinStatus
- 补充退群群已读边界测试
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