Commit bb5ac45be8f517c2e780aebd920a7218a432afe5
1 parent
8996b947
✨ feat(im): 优化 JDK8 的兼容性
Showing
6 changed files
with
38 additions
and
27 deletions
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java
| ... | ... | @@ -91,5 +91,6 @@ public interface ErrorCodeConstants { |
| 91 | 91 | ErrorCode RTC_GROUP_REQUIRED = new ErrorCode(1_040_900_007, "群聊通话必须指定群编号"); |
| 92 | 92 | ErrorCode RTC_INVITE_BUSY = new ErrorCode(1_040_900_008, "通话发起繁忙,请稍后再试"); |
| 93 | 93 | ErrorCode RTC_GROUP_CALL_ACTIVE = new ErrorCode(1_040_900_009, "该群已有进行中通话,请通过胶囊条加入"); |
| 94 | + ErrorCode RTC_GROUP_INVITEE_OVER_LIMIT = new ErrorCode(1_040_900_010, "群通话邀请人数超过最大值"); | |
| 94 | 95 | |
| 95 | 96 | } | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/rtc/ImRtcCallStatusEnum.java
| 1 | 1 | package cn.iocoder.yudao.module.im.enums.rtc; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.collection.ListUtil; | |
| 3 | 4 | import cn.iocoder.yudao.framework.common.core.ArrayValuable; |
| 4 | 5 | import lombok.Getter; |
| 5 | 6 | import lombok.RequiredArgsConstructor; |
| ... | ... | @@ -28,7 +29,7 @@ public enum ImRtcCallStatusEnum implements ArrayValuable<Integer> { |
| 28 | 29 | /** |
| 29 | 30 | * 活跃状态集合;通话未结束(CREATED / RUNNING) |
| 30 | 31 | */ |
| 31 | - public static final List<Integer> ACTIVE_STATUSES = List.of(CREATED.getStatus(), RUNNING.getStatus()); | |
| 32 | + public static final List<Integer> ACTIVE_STATUSES = ListUtil.of(CREATED.getStatus(), RUNNING.getStatus()); | |
| 32 | 33 | |
| 33 | 34 | /** |
| 34 | 35 | * 状态值 | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/rtc/ImRtcParticipantStatusEnum.java
| 1 | 1 | package cn.iocoder.yudao.module.im.enums.rtc; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.collection.ListUtil; | |
| 3 | 4 | import cn.iocoder.yudao.framework.common.core.ArrayValuable; |
| 4 | 5 | import lombok.Getter; |
| 5 | 6 | import lombok.RequiredArgsConstructor; |
| ... | ... | @@ -35,7 +36,7 @@ public enum ImRtcParticipantStatusEnum implements ArrayValuable<Integer> { |
| 35 | 36 | /** |
| 36 | 37 | * 活跃状态集合;尚未离开通话(INVITING / JOINED) |
| 37 | 38 | */ |
| 38 | - public static final List<Integer> ACTIVE_STATUSES = List.of(INVITING.getStatus(), JOINED.getStatus()); | |
| 39 | + public static final List<Integer> ACTIVE_STATUSES = ListUtil.of(INVITING.getStatus(), JOINED.getStatus()); | |
| 39 | 40 | |
| 40 | 41 | /** |
| 41 | 42 | * 状态值 | ... | ... |
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; |
| 3 | 3 | import cn.hutool.core.codec.Base64; |
| 4 | 4 | import cn.hutool.core.collection.CollUtil; |
| 5 | 5 | import cn.hutool.core.lang.Assert; |
| 6 | +import cn.hutool.core.map.MapUtil; | |
| 6 | 7 | import cn.hutool.core.util.StrUtil; |
| 7 | 8 | import cn.hutool.crypto.digest.DigestUtil; |
| 8 | 9 | import cn.hutool.http.HttpRequest; |
| ... | ... | @@ -214,7 +215,7 @@ public class LiveKitClient { |
| 214 | 215 | .setIssuer(cfg.getApiKey()) |
| 215 | 216 | .setNotBefore(new Date(nowSec * 1000)) |
| 216 | 217 | .setExpiresAt(new Date((nowSec + ADMIN_TOKEN_TTL.getSeconds()) * 1000)) |
| 217 | - .setPayload("video", Map.of("roomAdmin", true)) | |
| 218 | + .setPayload("video", MapUtil.of("roomAdmin", true)) | |
| 218 | 219 | .setSigner(JWTSignerUtil.hs256(cfg.getApiSecret().getBytes())) |
| 219 | 220 | .sign(); |
| 220 | 221 | } |
| ... | ... | @@ -232,7 +233,7 @@ public class LiveKitClient { |
| 232 | 233 | return HttpRequest.post(HttpUtils.wsUrlToHttp(imProperties.getRtc().getLivekitUrl()) + path) |
| 233 | 234 | .header("Authorization", "Bearer " + token) |
| 234 | 235 | .header("Content-Type", "application/json") |
| 235 | - .body(JSONUtil.toJsonStr(Map.of("room", room))) | |
| 236 | + .body(JSONUtil.toJsonStr(MapUtil.of("room", room))) | |
| 236 | 237 | .timeout(SERVER_API_TIMEOUT_MS) |
| 237 | 238 | .execute(); |
| 238 | 239 | } | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallService.java
| ... | ... | @@ -43,7 +43,7 @@ public interface ImRtcCallService { |
| 43 | 43 | void inviteMoreCall(Long userId, ImRtcCallInviteMoreReqVO reqVO); |
| 44 | 44 | |
| 45 | 45 | /** |
| 46 | - * 接听通话:私聊状态切换 INVITING → RUNNING;群通话直接加入已有 RUNNING 房间 | |
| 46 | + * 接听通话:参与者 INVITING → JOINED;主表 CREATED → RUNNING(首次有非发起人接通时) | |
| 47 | 47 | * |
| 48 | 48 | * @return 通话主表 |
| 49 | 49 | */ | ... | ... |
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; |
| 41 | 41 | import java.time.Duration; |
| 42 | 42 | import java.time.LocalDateTime; |
| 43 | 43 | import java.util.*; |
| 44 | -import java.util.stream.Collectors; | |
| 45 | 44 | |
| 46 | 45 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 47 | 46 | import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; |
| ... | ... | @@ -181,10 +180,11 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 181 | 180 | } |
| 182 | 181 | rtcParticipantMapper.insertBatch(participants); |
| 183 | 182 | |
| 184 | - // 3.1 推送通知:RTC_CALL(INVITE) 给每个被邀请人 | |
| 183 | + // 3.1 推送通知:RTC_CALL(INVITE) 给每个被邀请人;批量取用户避免 N 次 RPC | |
| 185 | 184 | AdminUserRespDTO inviterUser = adminUserApi.getUser(inviterId); |
| 185 | + Map<Long, AdminUserRespDTO> inviteeMap = adminUserApi.getUserMap(invitees); | |
| 186 | 186 | for (Long inviteeId : invitees) { |
| 187 | - pushCallInviteNotification(call, inviterUser, inviteeId); | |
| 187 | + pushCallInviteNotification(call, inviterUser, inviteeId, inviteeMap.get(inviteeId)); | |
| 188 | 188 | } |
| 189 | 189 | // 3.2 群通话再向全群广播 RTC_CALL_START 入聊天流 |
| 190 | 190 | if (ImConversationTypeEnum.isGroup(reqVO.getScene())) { |
| ... | ... | @@ -268,10 +268,11 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 268 | 268 | } |
| 269 | 269 | rtcParticipantMapper.insertBatch(participants); |
| 270 | 270 | |
| 271 | - // 3. 推送通知:RTC_CALL(INVITE) 给每个新邀请人 | |
| 271 | + // 3. 推送通知:RTC_CALL(INVITE) 给每个新邀请人;批量取用户避免 N 次 RPC | |
| 272 | 272 | AdminUserRespDTO inviter = adminUserApi.getUser(userId); |
| 273 | + Map<Long, AdminUserRespDTO> inviteeMap = adminUserApi.getUserMap(incomingUserIds); | |
| 273 | 274 | for (Long inviteeId : incomingUserIds) { |
| 274 | - pushCallInviteNotification(call, inviter, inviteeId); | |
| 275 | + pushCallInviteNotification(call, inviter, inviteeId, inviteeMap.get(inviteeId)); | |
| 275 | 276 | } |
| 276 | 277 | } |
| 277 | 278 | |
| ... | ... | @@ -438,19 +439,23 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 438 | 439 | return signToken(userId, displayName, room); |
| 439 | 440 | } |
| 440 | 441 | |
| 441 | - // TODO DONE @AI:兼容 JDK8 —— 项目 Spring Boot 3 已要求 JDK17+,switch arrow case 可直接用 | |
| 442 | 442 | @Override |
| 443 | 443 | public void handleLiveKitEvent(LiveKitWebhookEventDTO event) { |
| 444 | 444 | if (event == null || event.getEvent() == null) { |
| 445 | 445 | return; |
| 446 | 446 | } |
| 447 | 447 | switch (event.getEvent()) { |
| 448 | - case LiveKitWebhookEventDTO.EVENT_PARTICIPANT_JOINED -> handleParticipantJoined(event); | |
| 449 | - case LiveKitWebhookEventDTO.EVENT_PARTICIPANT_LEFT -> handleParticipantLeft(event); | |
| 450 | - case LiveKitWebhookEventDTO.EVENT_ROOM_FINISHED -> handleRoomFinished(event); | |
| 451 | - default -> { | |
| 452 | - /* 其它事件忽略;track_published 等业务态由 LiveKit 自身分发驱动 */ | |
| 453 | - } | |
| 448 | + case LiveKitWebhookEventDTO.EVENT_PARTICIPANT_JOINED: | |
| 449 | + handleParticipantJoined(event); | |
| 450 | + break; | |
| 451 | + case LiveKitWebhookEventDTO.EVENT_PARTICIPANT_LEFT: | |
| 452 | + handleParticipantLeft(event); | |
| 453 | + break; | |
| 454 | + case LiveKitWebhookEventDTO.EVENT_ROOM_FINISHED: | |
| 455 | + handleRoomFinished(event); | |
| 456 | + break; | |
| 457 | + default: | |
| 458 | + // 其它事件忽略;track_published 等业务态由 LiveKit 自身分发驱动 | |
| 454 | 459 | } |
| 455 | 460 | } |
| 456 | 461 | |
| ... | ... | @@ -611,7 +616,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 611 | 616 | if (reqVO.getPeerUserId() == null) { |
| 612 | 617 | throw exception(RTC_PRIVATE_INVITEE_REQUIRED); |
| 613 | 618 | } |
| 614 | - if (Objects.equals(userId, reqVO.getPeerUserId())) { | |
| 619 | + if (ObjUtil.equal(userId, reqVO.getPeerUserId())) { | |
| 615 | 620 | throw exception(RTC_INVITE_SELF); |
| 616 | 621 | } |
| 617 | 622 | friendService.validateFriend(userId, reqVO.getPeerUserId()); |
| ... | ... | @@ -629,7 +634,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 629 | 634 | } |
| 630 | 635 | |
| 631 | 636 | /** |
| 632 | - * 解析被邀请池:私聊为 peerUserId 单元素;群聊优先用前端选中的子集,否则取群活跃成员;超量截断 | |
| 637 | + * 解析被邀请池:私聊为 peerUserId 单元素;群聊优先用前端选中的子集,否则取群活跃成员;超量直接抛错 | |
| 633 | 638 | * <p> |
| 634 | 639 | * 群聊场景:被邀请人必须是该群活跃成员;防止恶意客户端塞任意 userId 进群房间 |
| 635 | 640 | */ |
| ... | ... | @@ -653,12 +658,10 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 653 | 658 | } |
| 654 | 659 | // 不论来源,发起人本人不进被邀请池 |
| 655 | 660 | initial.remove(inviterId); |
| 656 | - // 超出最大同时在房成员数;截断为 max-1 个,避免邀请整个 500 人大群 | |
| 657 | - // TODO @AI:超过数量,直接抛出异常,不用裁剪,不然很奇怪; | |
| 661 | + // 超量直接抛错;前端按 max-1 限制选人 | |
| 658 | 662 | int max = imProperties.getRtc().getGroupMaxParticipants(); |
| 659 | 663 | if (initial.size() + 1 > max) { |
| 660 | - return initial.stream().limit(max - 1L) | |
| 661 | - .collect(Collectors.toCollection(LinkedHashSet::new)); | |
| 664 | + throw exception(RTC_GROUP_INVITEE_OVER_LIMIT); | |
| 662 | 665 | } |
| 663 | 666 | return initial; |
| 664 | 667 | } |
| ... | ... | @@ -671,7 +674,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 671 | 674 | if (!ImRtcCallStatusEnum.isCreated(call.getStatus())) { |
| 672 | 675 | return; |
| 673 | 676 | } |
| 674 | - if (Objects.equals(call.getInviterUserId(), acceptorId)) { | |
| 677 | + if (ObjUtil.equal(call.getInviterUserId(), acceptorId)) { | |
| 675 | 678 | return; |
| 676 | 679 | } |
| 677 | 680 | |
| ... | ... | @@ -745,8 +748,12 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 745 | 748 | /** |
| 746 | 749 | * RTC_CALL(INVITE):走 webSocketService 直推到被邀请人;persistent=false 不入消息流 |
| 747 | 750 | */ |
| 748 | - private void pushCallInviteNotification(ImRtcCallDO call, AdminUserRespDTO inviter, Long inviteeId) { | |
| 749 | - String token = signToken(inviteeId, inviter == null ? null : inviter.getNickname(), call.getRoom()); | |
| 751 | + private void pushCallInviteNotification(ImRtcCallDO call, AdminUserRespDTO inviter, | |
| 752 | + Long inviteeId, AdminUserRespDTO invitee) { | |
| 753 | + String displayName = invitee != null | |
| 754 | + ? StrUtil.blankToDefault(invitee.getNickname(), String.valueOf(inviteeId)) | |
| 755 | + : String.valueOf(inviteeId); | |
| 756 | + String token = signToken(inviteeId, displayName, call.getRoom()); | |
| 750 | 757 | ImRtcCallNotification payload = ImRtcCallNotification.ofInvite( |
| 751 | 758 | call, inviter, imProperties.getRtc().getLivekitUrl(), token); |
| 752 | 759 | webSocketService.sendPrivateMessageAsync(inviteeId, ImPrivateMessageDTO.ofRtcNotification( |
| ... | ... | @@ -831,7 +838,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 831 | 838 | // 私聊:发起人总是 sender;receiver = 通话另一方 |
| 832 | 839 | ImRtcParticipantDO peer = CollUtil.findOne( |
| 833 | 840 | rtcParticipantMapper.selectListByRoom(call.getRoom()), |
| 834 | - p -> !Objects.equals(p.getUserId(), senderId)); | |
| 841 | + p -> ObjUtil.notEqual(p.getUserId(), senderId)); | |
| 835 | 842 | Long receiverId = peer != null ? peer.getUserId() : senderId; |
| 836 | 843 | ImPrivateMessageSendDTO dto = new ImPrivateMessageSendDTO().setReceiverId(receiverId) |
| 837 | 844 | .setType(ImMessageTypeEnum.RTC_CALL_END.getType()).setContent(payload); | ... | ... |