Commit bb5ac45be8f517c2e780aebd920a7218a432afe5

Authored by YunaiV
1 parent 8996b947

✨ feat(im): 优化 JDK8 的兼容性

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