From bca66fee74f1999e482ac6f86b8d5833aaaba511 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Wed, 17 Jun 2026 01:38:53 +0800 Subject: [PATCH] fix(im): 修复历史退群群未读和成员加载问题 --- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java | 4 +--- yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImplTest.java | 56 ++++++++++++++++++++++++++++++++++++-------------------- 2 files changed, 37 insertions(+), 23 deletions(-) diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java index b43ff9b..12914fb 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java @@ -262,9 +262,7 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { throw exception(MESSAGE_GROUP_READ_DISABLED); } Assert.notNull(messageId, "已读消息编号不能为空"); - // 1.1 校验用户在群中(权限校验) - groupMemberService.validateMemberInGroup(groupId, userId); - // 1.2 校验消息属于当前群,且对当前用户可见(按快照) + // 1. 校验消息属于当前群,且对当前用户可见(按快照) ImGroupMessageDO message = groupMessageMapper.selectById(messageId); if (message == null || ObjUtil.notEqual(message.getGroupId(), groupId) diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImplTest.java index 4301ca9..f29ade0 100644 --- a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImplTest.java +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImplTest.java @@ -460,15 +460,17 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { // ========== 群已读测试 ========== @Test - public void testReadMessages_notInGroup() { - // 准备:当前用户不在群中 - when(groupMemberService.validateMemberInGroup(10L, 1L)) - .thenThrow(new ServiceException(GROUP_MEMBER_NOT_IN_GROUP.getCode(), GROUP_MEMBER_NOT_IN_GROUP.getMsg())); + public void testReadMessages_messageInvisible() { + // 准备:消息不在用户接收快照内 + when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder() + .id(100L).groupId(10L).senderId(2L).receiverUserIds(List.of(2L)) + .sendTime(LocalDateTime.now()).build()); - // 调用并断言:越权校验 + // 调用并断言 ServiceException exception = assertThrows(ServiceException.class, () -> groupMessageService.readGroupMessages(1L, 10L, 100L)); - assertEquals(GROUP_MEMBER_NOT_IN_GROUP.getCode(), exception.getCode()); + assertEquals(MESSAGE_NOT_IN_GROUP.getCode(), exception.getCode()); + verify(conversationReadService, never()).updateConversationReadPosition(anyLong(), anyInt(), anyLong(), anyLong()); } @Test @@ -636,11 +638,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupMessageServiceImpl.class))) .thenReturn(groupMessageService); - // 准备:用户在群;已读位置从 5 前进到 100 - ImGroupMemberDO member = ImGroupMemberDO.builder() - .groupId(10L).userId(1L) - .status(CommonStatusEnum.ENABLE.getStatus()).build(); - when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn(member); + // 准备:消息对用户可见;已读位置从 5 前进到 100 when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder() .id(100L).groupId(10L).senderId(2L).receiverUserIds(List.of(1L)) .sendTime(LocalDateTime.now()).build()); @@ -657,6 +655,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { // 断言:已读位置更新 + READ 事件 verify(conversationReadService).updateConversationReadPosition(1L, ImConversationTypeEnum.GROUP.getType(), 10L, 100L); verify(imWebSocketService).sendNotificationAsync(eq(1L), anyInt(), anyInt(), any()); + verify(groupMemberService, never()).validateMemberInGroup(10L, 1L); } } @@ -715,10 +714,6 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @Test public void testReadGroupMessages_cursorAlreadyAhead() { // 准备:已读游标已 >= 目标,直接返回 - ImGroupMemberDO member = ImGroupMemberDO.builder() - .groupId(10L).userId(1L) - .status(CommonStatusEnum.ENABLE.getStatus()).build(); - when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn(member); when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder() .id(100L).groupId(10L).senderId(2L).receiverUserIds(List.of(1L)) .sendTime(LocalDateTime.now()).build()); @@ -738,11 +733,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @Test public void testReadGroupMessages_messageNotInGroup() { - // 准备:用户在群,但 messageId 属于其它群 - ImGroupMemberDO member = ImGroupMemberDO.builder() - .groupId(10L).userId(1L) - .status(CommonStatusEnum.ENABLE.getStatus()).build(); - when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn(member); + // 准备:messageId 属于其它群 when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder() .id(100L).groupId(20L).senderId(2L).sendTime(LocalDateTime.now()).build()); @@ -753,6 +744,31 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { verify(conversationReadService, never()).updateConversationReadPosition(anyLong(), anyInt(), anyLong(), anyLong()); } + @Test + public void testReadGroupMessages_quitMemberVisibleMessage_success() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupMessageServiceImpl.class))) + .thenReturn(groupMessageService); + + // 准备:历史消息的接收快照包含用户 + when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder() + .id(100L).groupId(10L).senderId(2L).receiverUserIds(List.of(1L, 2L)) + .sendTime(LocalDateTime.now().minusMinutes(1)).build()); + when(conversationReadService.getConversationReadMessageId(1L, ImConversationTypeEnum.GROUP.getType(), 10L)) + .thenReturn(0L); + when(conversationReadService.updateConversationReadPosition(1L, ImConversationTypeEnum.GROUP.getType(), 10L, 100L)) + .thenReturn(true); + when(groupMessageMapper.selectListByGroupIdAndPendingReceipt(10L, 0L, 100L)).thenReturn(List.of()); + + // 调用 + groupMessageService.readGroupMessages(1L, 10L, 100L); + + // 断言:不校验当前在群,直接推进已读位置 + verify(groupMemberService, never()).validateMemberInGroup(10L, 1L); + verify(conversationReadService).updateConversationReadPosition(1L, ImConversationTypeEnum.GROUP.getType(), 10L, 100L); + } + } + // ========== 回执:DONE 状态迁移 ========== @Test -- libgit2 0.21.4