From bb5ac45be8f517c2e780aebd920a7218a432afe5 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Wed, 13 May 2026 00:59:06 +0800 Subject: [PATCH] ✨ feat(im): 优化 JDK8 的兼容性 --- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java | 1 + yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/rtc/ImRtcCallStatusEnum.java | 3 ++- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/rtc/ImRtcParticipantStatusEnum.java | 3 ++- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/rtc/core/LiveKitClient.java | 5 +++-- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallService.java | 2 +- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallServiceImpl.java | 51 +++++++++++++++++++++++++++++---------------------- 6 files changed, 38 insertions(+), 27 deletions(-) diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java index 311ac1d..98920c0 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java @@ -91,5 +91,6 @@ public interface ErrorCodeConstants { ErrorCode RTC_GROUP_REQUIRED = new ErrorCode(1_040_900_007, "群聊通话必须指定群编号"); ErrorCode RTC_INVITE_BUSY = new ErrorCode(1_040_900_008, "通话发起繁忙,请稍后再试"); ErrorCode RTC_GROUP_CALL_ACTIVE = new ErrorCode(1_040_900_009, "该群已有进行中通话,请通过胶囊条加入"); + ErrorCode RTC_GROUP_INVITEE_OVER_LIMIT = new ErrorCode(1_040_900_010, "群通话邀请人数超过最大值"); } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/rtc/ImRtcCallStatusEnum.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/rtc/ImRtcCallStatusEnum.java index 7ae6387..42b3cda 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/rtc/ImRtcCallStatusEnum.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/rtc/ImRtcCallStatusEnum.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.im.enums.rtc; +import cn.hutool.core.collection.ListUtil; import cn.iocoder.yudao.framework.common.core.ArrayValuable; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -28,7 +29,7 @@ public enum ImRtcCallStatusEnum implements ArrayValuable { /** * 活跃状态集合;通话未结束(CREATED / RUNNING) */ - public static final List ACTIVE_STATUSES = List.of(CREATED.getStatus(), RUNNING.getStatus()); + public static final List ACTIVE_STATUSES = ListUtil.of(CREATED.getStatus(), RUNNING.getStatus()); /** * 状态值 diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/rtc/ImRtcParticipantStatusEnum.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/rtc/ImRtcParticipantStatusEnum.java index 770923e..3f3acc4 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/rtc/ImRtcParticipantStatusEnum.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/rtc/ImRtcParticipantStatusEnum.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.im.enums.rtc; +import cn.hutool.core.collection.ListUtil; import cn.iocoder.yudao.framework.common.core.ArrayValuable; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -35,7 +36,7 @@ public enum ImRtcParticipantStatusEnum implements ArrayValuable { /** * 活跃状态集合;尚未离开通话(INVITING / JOINED) */ - public static final List ACTIVE_STATUSES = List.of(INVITING.getStatus(), JOINED.getStatus()); + public static final List ACTIVE_STATUSES = ListUtil.of(INVITING.getStatus(), JOINED.getStatus()); /** * 状态值 diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/rtc/core/LiveKitClient.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/rtc/core/LiveKitClient.java index a9c1342..6f3df8d 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/rtc/core/LiveKitClient.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/rtc/core/LiveKitClient.java @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.im.framework.rtc.core; import cn.hutool.core.codec.Base64; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.digest.DigestUtil; import cn.hutool.http.HttpRequest; @@ -214,7 +215,7 @@ public class LiveKitClient { .setIssuer(cfg.getApiKey()) .setNotBefore(new Date(nowSec * 1000)) .setExpiresAt(new Date((nowSec + ADMIN_TOKEN_TTL.getSeconds()) * 1000)) - .setPayload("video", Map.of("roomAdmin", true)) + .setPayload("video", MapUtil.of("roomAdmin", true)) .setSigner(JWTSignerUtil.hs256(cfg.getApiSecret().getBytes())) .sign(); } @@ -232,7 +233,7 @@ public class LiveKitClient { return HttpRequest.post(HttpUtils.wsUrlToHttp(imProperties.getRtc().getLivekitUrl()) + path) .header("Authorization", "Bearer " + token) .header("Content-Type", "application/json") - .body(JSONUtil.toJsonStr(Map.of("room", room))) + .body(JSONUtil.toJsonStr(MapUtil.of("room", room))) .timeout(SERVER_API_TIMEOUT_MS) .execute(); } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallService.java index 6585f9d..545177a 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallService.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallService.java @@ -43,7 +43,7 @@ public interface ImRtcCallService { void inviteMoreCall(Long userId, ImRtcCallInviteMoreReqVO reqVO); /** - * 接听通话:私聊状态切换 INVITING → RUNNING;群通话直接加入已有 RUNNING 房间 + * 接听通话:参与者 INVITING → JOINED;主表 CREATED → RUNNING(首次有非发起人接通时) * * @return 通话主表 */ diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallServiceImpl.java index caf6576..708a1fe 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallServiceImpl.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallServiceImpl.java @@ -41,7 +41,6 @@ import org.springframework.validation.annotation.Validated; import java.time.Duration; import java.time.LocalDateTime; import java.util.*; -import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; @@ -181,10 +180,11 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { } rtcParticipantMapper.insertBatch(participants); - // 3.1 推送通知:RTC_CALL(INVITE) 给每个被邀请人 + // 3.1 推送通知:RTC_CALL(INVITE) 给每个被邀请人;批量取用户避免 N 次 RPC AdminUserRespDTO inviterUser = adminUserApi.getUser(inviterId); + Map inviteeMap = adminUserApi.getUserMap(invitees); for (Long inviteeId : invitees) { - pushCallInviteNotification(call, inviterUser, inviteeId); + pushCallInviteNotification(call, inviterUser, inviteeId, inviteeMap.get(inviteeId)); } // 3.2 群通话再向全群广播 RTC_CALL_START 入聊天流 if (ImConversationTypeEnum.isGroup(reqVO.getScene())) { @@ -268,10 +268,11 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { } rtcParticipantMapper.insertBatch(participants); - // 3. 推送通知:RTC_CALL(INVITE) 给每个新邀请人 + // 3. 推送通知:RTC_CALL(INVITE) 给每个新邀请人;批量取用户避免 N 次 RPC AdminUserRespDTO inviter = adminUserApi.getUser(userId); + Map inviteeMap = adminUserApi.getUserMap(incomingUserIds); for (Long inviteeId : incomingUserIds) { - pushCallInviteNotification(call, inviter, inviteeId); + pushCallInviteNotification(call, inviter, inviteeId, inviteeMap.get(inviteeId)); } } @@ -438,19 +439,23 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { return signToken(userId, displayName, room); } - // TODO DONE @AI:兼容 JDK8 —— 项目 Spring Boot 3 已要求 JDK17+,switch arrow case 可直接用 @Override public void handleLiveKitEvent(LiveKitWebhookEventDTO event) { if (event == null || event.getEvent() == null) { return; } switch (event.getEvent()) { - case LiveKitWebhookEventDTO.EVENT_PARTICIPANT_JOINED -> handleParticipantJoined(event); - case LiveKitWebhookEventDTO.EVENT_PARTICIPANT_LEFT -> handleParticipantLeft(event); - case LiveKitWebhookEventDTO.EVENT_ROOM_FINISHED -> handleRoomFinished(event); - default -> { - /* 其它事件忽略;track_published 等业务态由 LiveKit 自身分发驱动 */ - } + case LiveKitWebhookEventDTO.EVENT_PARTICIPANT_JOINED: + handleParticipantJoined(event); + break; + case LiveKitWebhookEventDTO.EVENT_PARTICIPANT_LEFT: + handleParticipantLeft(event); + break; + case LiveKitWebhookEventDTO.EVENT_ROOM_FINISHED: + handleRoomFinished(event); + break; + default: + // 其它事件忽略;track_published 等业务态由 LiveKit 自身分发驱动 } } @@ -611,7 +616,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { if (reqVO.getPeerUserId() == null) { throw exception(RTC_PRIVATE_INVITEE_REQUIRED); } - if (Objects.equals(userId, reqVO.getPeerUserId())) { + if (ObjUtil.equal(userId, reqVO.getPeerUserId())) { throw exception(RTC_INVITE_SELF); } friendService.validateFriend(userId, reqVO.getPeerUserId()); @@ -629,7 +634,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { } /** - * 解析被邀请池:私聊为 peerUserId 单元素;群聊优先用前端选中的子集,否则取群活跃成员;超量截断 + * 解析被邀请池:私聊为 peerUserId 单元素;群聊优先用前端选中的子集,否则取群活跃成员;超量直接抛错 *

* 群聊场景:被邀请人必须是该群活跃成员;防止恶意客户端塞任意 userId 进群房间 */ @@ -653,12 +658,10 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { } // 不论来源,发起人本人不进被邀请池 initial.remove(inviterId); - // 超出最大同时在房成员数;截断为 max-1 个,避免邀请整个 500 人大群 - // TODO @AI:超过数量,直接抛出异常,不用裁剪,不然很奇怪; + // 超量直接抛错;前端按 max-1 限制选人 int max = imProperties.getRtc().getGroupMaxParticipants(); if (initial.size() + 1 > max) { - return initial.stream().limit(max - 1L) - .collect(Collectors.toCollection(LinkedHashSet::new)); + throw exception(RTC_GROUP_INVITEE_OVER_LIMIT); } return initial; } @@ -671,7 +674,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { if (!ImRtcCallStatusEnum.isCreated(call.getStatus())) { return; } - if (Objects.equals(call.getInviterUserId(), acceptorId)) { + if (ObjUtil.equal(call.getInviterUserId(), acceptorId)) { return; } @@ -745,8 +748,12 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { /** * RTC_CALL(INVITE):走 webSocketService 直推到被邀请人;persistent=false 不入消息流 */ - private void pushCallInviteNotification(ImRtcCallDO call, AdminUserRespDTO inviter, Long inviteeId) { - String token = signToken(inviteeId, inviter == null ? null : inviter.getNickname(), call.getRoom()); + private void pushCallInviteNotification(ImRtcCallDO call, AdminUserRespDTO inviter, + Long inviteeId, AdminUserRespDTO invitee) { + String displayName = invitee != null + ? StrUtil.blankToDefault(invitee.getNickname(), String.valueOf(inviteeId)) + : String.valueOf(inviteeId); + String token = signToken(inviteeId, displayName, call.getRoom()); ImRtcCallNotification payload = ImRtcCallNotification.ofInvite( call, inviter, imProperties.getRtc().getLivekitUrl(), token); webSocketService.sendPrivateMessageAsync(inviteeId, ImPrivateMessageDTO.ofRtcNotification( @@ -831,7 +838,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { // 私聊:发起人总是 sender;receiver = 通话另一方 ImRtcParticipantDO peer = CollUtil.findOne( rtcParticipantMapper.selectListByRoom(call.getRoom()), - p -> !Objects.equals(p.getUserId(), senderId)); + p -> ObjUtil.notEqual(p.getUserId(), senderId)); Long receiverId = peer != null ? peer.getUserId() : senderId; ImPrivateMessageSendDTO dto = new ImPrivateMessageSendDTO().setReceiverId(receiverId) .setType(ImMessageTypeEnum.RTC_CALL_END.getType()).setContent(payload); -- libgit2 0.21.4