Commit 9ed7c050b03c696bf92cba5e2a5332ff29b82411

Authored by YunaiV
1 parent 79de9233

feat(im):优化已读上报、群详情缓存与 RTC 通话状态

- 已读上报增加本地读位置覆盖判断,避免切换会话和当前会话自动已读时重复调用 read 接口
- 标记会话已读时同步推进本地 read 游标并写入 IndexedDB,接口失败仅记录日志
- 缓存私聊对方 maxReadMessageId,并在状态补拉、回执更新和退出 IM 时维护缓存
- 增加群详情 infoLoaded 内存标记,减少切群时重复拉取群详情,手动刷新和关键通知仍强制刷新
- 同步 GROUP_INFO_UPDATE 的 joinApproval,避免群审批配置在前端缓存中陈旧
- 优化群通话胶囊条状态,记录 participantsLoaded,按需补齐参与者并在通话无人时移除胶囊
- RTC_CALL_START 生成群通话最小胶囊条,后续由参与者事件和 getActiveCall 补齐
- 退出 IM 时清理 RTC 状态和群通话缓存
- Vben antd/antd-next 调整媒体元素为函数 ref,修复 MediaStream 与元素挂载时序问题
- 修复 Vben 消息历史弹窗回调类型标注
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java
... ... @@ -149,7 +149,9 @@ public class ImGroupServiceImpl implements ImGroupService {
149 149 boolean nameChanged = StrUtil.isNotEmpty(updateReqVO.getName());
150 150 boolean noticeChanged = updateReqVO.getNotice() != null;
151 151 boolean avatarChanged = StrUtil.isNotEmpty(updateReqVO.getAvatar());
152   - if (nameChanged || noticeChanged || avatarChanged) {
  152 + boolean joinApprovalChanged = updateReqVO.getJoinApproval() != null
  153 + && ObjUtil.notEqual(group.getJoinApproval(), updateReqVO.getJoinApproval());
  154 + if (nameChanged || noticeChanged || avatarChanged || joinApprovalChanged) {
153 155 List<Long> memberUserIds = groupMemberService.getActiveGroupMemberUserIdsByGroupId(groupId);
154 156 if (nameChanged) {
155 157 groupMessageService.sendGroupMessage(userId, memberUserIds, ImGroupMessageSendDTO.ofGroupNameUpdate(
... ... @@ -161,7 +163,11 @@ public class ImGroupServiceImpl implements ImGroupService {
161 163 }
162 164 if (avatarChanged) {
163 165 groupMessageService.sendGroupMessage(userId, memberUserIds, ImGroupMessageSendDTO.ofGroupInfoUpdate(
164   - groupId, userId, group.getAvatar(), updateReqVO.getAvatar()));
  166 + groupId, userId, group.getAvatar(), updateReqVO.getAvatar(), null, null));
  167 + }
  168 + if (joinApprovalChanged) {
  169 + groupMessageService.sendGroupMessage(userId, memberUserIds, ImGroupMessageSendDTO.ofGroupInfoUpdate(
  170 + groupId, userId, null, null, group.getJoinApproval(), updateReqVO.getJoinApproval()));
165 171 }
166 172 }
167 173  
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/dto/ImGroupMessageSendDTO.java
... ... @@ -63,11 +63,15 @@ public class ImGroupMessageSendDTO {
63 63 .setType(ImContentTypeEnum.GROUP_CREATE.getType()).setContent(notification);
64 64 }
65 65  
66   - public static ImGroupMessageSendDTO ofGroupInfoUpdate(Long groupId, Long operatorUserId, String oldAvatar, String newAvatar) {
  66 + public static ImGroupMessageSendDTO ofGroupInfoUpdate(Long groupId, Long operatorUserId,
  67 + String oldAvatar, String newAvatar,
  68 + Boolean oldJoinApproval, Boolean newJoinApproval) {
67 69 GroupInfoUpdateNotification notification = new GroupInfoUpdateNotification();
68 70 notification.setOperatorUserId(operatorUserId);
69 71 notification.setOldAvatar(oldAvatar);
70 72 notification.setNewAvatar(newAvatar);
  73 + notification.setOldJoinApproval(oldJoinApproval);
  74 + notification.setNewJoinApproval(newJoinApproval);
71 75 return new ImGroupMessageSendDTO().setGroupId(groupId)
72 76 .setType(ImContentTypeEnum.GROUP_INFO_UPDATE.getType()).setContent(notification);
73 77 }
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupInfoUpdateNotification.java
... ... @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.im.service.websocket.notification.group;
3 3 import lombok.Data;
4 4  
5 5 /**
6   - * 群信息变更事件通知(当前承载头像变更,NAME / NOTICE 走独立事件)
  6 + * 群信息变更事件通知(NAME / NOTICE 走独立事件)
7 7 */
8 8 @Data
9 9 public class GroupInfoUpdateNotification extends BaseGroupNotification {
... ... @@ -16,5 +16,13 @@ public class GroupInfoUpdateNotification extends BaseGroupNotification {
16 16 * 新群头像
17 17 */
18 18 private String newAvatar;
  19 + /**
  20 + * 旧进群审批开关
  21 + */
  22 + private Boolean oldJoinApproval;
  23 + /**
  24 + * 新进群审批开关
  25 + */
  26 + private Boolean newJoinApproval;
19 27  
20 28 }
... ...
yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImplTest.java
... ... @@ -209,6 +209,58 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
209 209 }
210 210 }
211 211  
  212 + @Test
  213 + public void testUpdateGroup_joinApprovalChanged() {
  214 + try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
  215 + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class)))
  216 + .thenReturn(groupService);
  217 +
  218 + // 准备
  219 + ImGroupDO group = ImGroupDO.builder().id(10L).name("群").ownerUserId(1L)
  220 + .joinApproval(false).status(CommonStatusEnum.ENABLE.getStatus()).build();
  221 + when(groupMapper.selectById(10L)).thenReturn(group);
  222 +
  223 + ImGroupUpdateReqVO reqVO = new ImGroupUpdateReqVO();
  224 + reqVO.setId(10L);
  225 + reqVO.setJoinApproval(true);
  226 +
  227 + // 调用
  228 + ImGroupDO result = groupService.updateGroup(reqVO, 1L);
  229 +
  230 + // 断言
  231 + verify(groupMapper).updateById(any(ImGroupDO.class));
  232 + assertTrue(result.getJoinApproval());
  233 + ArgumentCaptor<ImGroupMessageSendDTO> dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class);
  234 + verify(groupMessageService).sendGroupMessage(eq(1L), anyCollection(), dtoCaptor.capture());
  235 + assertEquals(ImContentTypeEnum.GROUP_INFO_UPDATE.getType(), dtoCaptor.getValue().getType());
  236 + }
  237 + }
  238 +
  239 + @Test
  240 + public void testUpdateGroup_joinApprovalSame() {
  241 + try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
  242 + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class)))
  243 + .thenReturn(groupService);
  244 +
  245 + // 准备
  246 + ImGroupDO group = ImGroupDO.builder().id(10L).name("群").ownerUserId(1L)
  247 + .joinApproval(true).status(CommonStatusEnum.ENABLE.getStatus()).build();
  248 + when(groupMapper.selectById(10L)).thenReturn(group);
  249 +
  250 + ImGroupUpdateReqVO reqVO = new ImGroupUpdateReqVO();
  251 + reqVO.setId(10L);
  252 + reqVO.setJoinApproval(true);
  253 +
  254 + // 调用
  255 + ImGroupDO result = groupService.updateGroup(reqVO, 1L);
  256 +
  257 + // 断言
  258 + verify(groupMapper).updateById(any(ImGroupDO.class));
  259 + assertTrue(result.getJoinApproval());
  260 + verify(groupMessageService, never()).sendGroupMessage(anyLong(), anyCollection(), any());
  261 + }
  262 + }
  263 +
212 264 // ========== dissolveGroup ==========
213 265  
214 266 @Test
... ...