Commit 07f3bab72acd11b3469ff708ac5b2a1e71430616
1 parent
d0aac382
✨ feat(im): 将后端的 roomName 和 callId 融合,简化字段和逻辑(一致性更好、概念更简洁)
Showing
17 changed files
with
150 additions
and
192 deletions
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/rtc/ImRtcCallController.java
| ... | ... | @@ -26,7 +26,6 @@ import java.util.Set; |
| 26 | 26 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 27 | 27 | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; |
| 28 | 28 | |
| 29 | -// TODO @AI:需要讨论,到底使用 roomName 还是 callId 更好?前端传参;然后,DO 那种,是不是只存储 callId,roomName 二选 1,是不是就行了。或者统一成 room?uuid 完事;callid 还给 call.id 更好理解; | |
| 30 | 29 | @Tag(name = "管理后台 - IM 实时通话") |
| 31 | 30 | @RestController |
| 32 | 31 | @RequestMapping("/im/rtc") |
| ... | ... | @@ -47,9 +46,9 @@ public class ImRtcCallController { |
| 47 | 46 | |
| 48 | 47 | @PostMapping("/join") |
| 49 | 48 | @Operation(summary = "加入已有群通话;用于胶囊条「加入」按钮") |
| 50 | - @Parameter(name = "roomName", description = "LiveKit 房间名", required = true, example = "im_rtc_call_xxxx") | |
| 51 | - public CommonResult<ImRtcCallRespVO> joinCall(@RequestParam("roomName") String roomName) { | |
| 52 | - ImRtcCallDO call = rtcCallService.joinCall(getLoginUserId(), roomName); | |
| 49 | + @Parameter(name = "room", description = "业务通话编号", required = true, example = "f47ac10b58cc4372a567") | |
| 50 | + public CommonResult<ImRtcCallRespVO> joinCall(@RequestParam("room") String room) { | |
| 51 | + ImRtcCallDO call = rtcCallService.joinCall(getLoginUserId(), room); | |
| 53 | 52 | return success(buildCallResp(call, getLoginUserId())); |
| 54 | 53 | } |
| 55 | 54 | |
| ... | ... | @@ -62,41 +61,41 @@ public class ImRtcCallController { |
| 62 | 61 | |
| 63 | 62 | @PostMapping("/accept") |
| 64 | 63 | @Operation(summary = "接听通话") |
| 65 | - @Parameter(name = "roomName", description = "LiveKit 房间名", required = true, example = "call_friend_1_2") | |
| 66 | - public CommonResult<ImRtcCallRespVO> accept(@RequestParam("roomName") String roomName) { | |
| 67 | - ImRtcCallDO call = rtcCallService.acceptCall(getLoginUserId(), roomName); | |
| 64 | + @Parameter(name = "room", description = "业务通话编号", required = true, example = "f47ac10b58cc4372a567") | |
| 65 | + public CommonResult<ImRtcCallRespVO> accept(@RequestParam("room") String room) { | |
| 66 | + ImRtcCallDO call = rtcCallService.acceptCall(getLoginUserId(), room); | |
| 68 | 67 | return success(buildCallResp(call, getLoginUserId())); |
| 69 | 68 | } |
| 70 | 69 | |
| 71 | 70 | @PostMapping("/reject") |
| 72 | 71 | @Operation(summary = "拒绝通话") |
| 73 | - @Parameter(name = "roomName", description = "LiveKit 房间名", required = true, example = "call_friend_1_2") | |
| 74 | - public CommonResult<Boolean> reject(@RequestParam("roomName") String roomName) { | |
| 75 | - rtcCallService.rejectCall(getLoginUserId(), roomName); | |
| 72 | + @Parameter(name = "room", description = "业务通话编号", required = true, example = "f47ac10b58cc4372a567") | |
| 73 | + public CommonResult<Boolean> reject(@RequestParam("room") String room) { | |
| 74 | + rtcCallService.rejectCall(getLoginUserId(), room); | |
| 76 | 75 | return success(true); |
| 77 | 76 | } |
| 78 | 77 | |
| 79 | 78 | @PostMapping("/cancel") |
| 80 | 79 | @Operation(summary = "取消邀请;主叫接通前调用") |
| 81 | - @Parameter(name = "roomName", description = "LiveKit 房间名", required = true, example = "call_friend_1_2") | |
| 82 | - public CommonResult<Boolean> cancel(@RequestParam("roomName") String roomName) { | |
| 83 | - rtcCallService.cancelCall(getLoginUserId(), roomName); | |
| 80 | + @Parameter(name = "room", description = "业务通话编号", required = true, example = "f47ac10b58cc4372a567") | |
| 81 | + public CommonResult<Boolean> cancel(@RequestParam("room") String room) { | |
| 82 | + rtcCallService.cancelCall(getLoginUserId(), room); | |
| 84 | 83 | return success(true); |
| 85 | 84 | } |
| 86 | 85 | |
| 87 | 86 | @PostMapping("/leave") |
| 88 | 87 | @Operation(summary = "离开通话;接通后调用") |
| 89 | - @Parameter(name = "roomName", description = "LiveKit 房间名", required = true, example = "call_friend_1_2") | |
| 90 | - public CommonResult<Boolean> leave(@RequestParam("roomName") String roomName) { | |
| 91 | - rtcCallService.leaveCall(getLoginUserId(), roomName); | |
| 88 | + @Parameter(name = "room", description = "业务通话编号", required = true, example = "f47ac10b58cc4372a567") | |
| 89 | + public CommonResult<Boolean> leave(@RequestParam("room") String room) { | |
| 90 | + rtcCallService.leaveCall(getLoginUserId(), room); | |
| 92 | 91 | return success(true); |
| 93 | 92 | } |
| 94 | 93 | |
| 95 | 94 | @GetMapping("/refresh-token") |
| 96 | 95 | @Operation(summary = "重新签发 Token;客户端重连或 Token 过期续期") |
| 97 | - @Parameter(name = "roomName", description = "LiveKit 房间名", required = true, example = "call_friend_1_2") | |
| 98 | - public CommonResult<ImRtcCallRespVO> refreshToken(@RequestParam("roomName") String roomName) { | |
| 99 | - ImRtcCallDO call = rtcCallService.refreshCallToken(getLoginUserId(), roomName); | |
| 96 | + @Parameter(name = "room", description = "业务通话编号", required = true, example = "f47ac10b58cc4372a567") | |
| 97 | + public CommonResult<ImRtcCallRespVO> refreshToken(@RequestParam("room") String room) { | |
| 98 | + ImRtcCallDO call = rtcCallService.refreshCallToken(getLoginUserId(), room); | |
| 100 | 99 | return success(buildCallResp(call, getLoginUserId())); |
| 101 | 100 | } |
| 102 | 101 | |
| ... | ... | @@ -114,11 +113,11 @@ public class ImRtcCallController { |
| 114 | 113 | if (call == null) { |
| 115 | 114 | return null; |
| 116 | 115 | } |
| 117 | - List<ImRtcParticipantDO> participants = rtcCallService.getCallParticipants(call.getCallId()); | |
| 116 | + List<ImRtcParticipantDO> participants = rtcCallService.getCallParticipants(call.getRoom()); | |
| 118 | 117 | return new ImRtcCallRespVO() |
| 119 | - .setCallId(call.getCallId()).setRoomName(call.getRoomName()) | |
| 118 | + .setRoom(call.getRoom()) | |
| 120 | 119 | .setLivekitUrl(imProperties.getRtc().getLivekitUrl()) |
| 121 | - .setToken(rtcCallService.signCallToken(userId, call.getRoomName())) | |
| 120 | + .setToken(rtcCallService.signCallToken(userId, call.getRoom())) | |
| 122 | 121 | .setScene(call.getConversationType()).setMediaType(call.getMediaType()) |
| 123 | 122 | .setStatus(call.getStatus()).setInviterId(call.getInviterUserId()) |
| 124 | 123 | .setGroupId(call.getGroupId()) |
| ... | ... | @@ -130,9 +129,9 @@ public class ImRtcCallController { |
| 130 | 129 | if (call == null) { |
| 131 | 130 | return null; |
| 132 | 131 | } |
| 133 | - List<ImRtcParticipantDO> participants = rtcCallService.getCallParticipants(call.getCallId()); | |
| 132 | + List<ImRtcParticipantDO> participants = rtcCallService.getCallParticipants(call.getRoom()); | |
| 134 | 133 | return new ImRtcGroupCallRespVO() |
| 135 | - .setCallId(call.getCallId()).setRoomName(call.getRoomName()) | |
| 134 | + .setRoom(call.getRoom()) | |
| 136 | 135 | .setGroupId(call.getGroupId()).setMediaType(call.getMediaType()) |
| 137 | 136 | .setInviterId(call.getInviterUserId()) |
| 138 | 137 | .setJoinedUserIds(filterUserIds(participants, ImRtcParticipantStatusEnum.JOINED)) | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/rtc/ImRtcLiveKitController.java
| ... | ... | @@ -57,7 +57,7 @@ public class ImRtcLiveKitController { |
| 57 | 57 | } |
| 58 | 58 | |
| 59 | 59 | // 2. 交给 service 处理;幂等由 service 自己保证 |
| 60 | - log.info("[webhook][事件处理 event={} roomName={}]", event.getEvent(), | |
| 60 | + log.info("[webhook][事件处理 event={} room={}]", event.getEvent(), | |
| 61 | 61 | event.getRoom() == null ? null : event.getRoom().getName()); |
| 62 | 62 | try { |
| 63 | 63 | rtcCallService.handleLiveKitEvent(event); | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/rtc/vo/ImRtcCallInviteMoreReqVO.java
| ... | ... | @@ -11,11 +11,10 @@ import java.util.Set; |
| 11 | 11 | @Data |
| 12 | 12 | public class ImRtcCallInviteMoreReqVO { |
| 13 | 13 | |
| 14 | - @Schema(description = "LiveKit 房间名", requiredMode = Schema.RequiredMode.REQUIRED, example = "call_group_2048") | |
| 15 | - @NotBlank(message = "房间名不能为空") | |
| 16 | - private String roomName; | |
| 14 | + @Schema(description = "业务通话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "f47ac10b58cc4372a567") | |
| 15 | + @NotBlank(message = "通话编号不能为空") | |
| 16 | + private String room; | |
| 17 | 17 | |
| 18 | - // TODO @AI:targetIds? | |
| 19 | 18 | @Schema(description = "新邀请的用户编号集合", requiredMode = Schema.RequiredMode.REQUIRED) |
| 20 | 19 | @NotEmpty(message = "请至少选择一位成员") |
| 21 | 20 | private Set<Long> inviteeIds; | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/rtc/vo/ImRtcCallRespVO.java
| ... | ... | @@ -10,10 +10,7 @@ import java.util.Set; |
| 10 | 10 | public class ImRtcCallRespVO { |
| 11 | 11 | |
| 12 | 12 | @Schema(description = "业务通话编号") |
| 13 | - private String callId; | |
| 14 | - | |
| 15 | - @Schema(description = "LiveKit 房间名") | |
| 16 | - private String roomName; | |
| 13 | + private String room; | |
| 17 | 14 | |
| 18 | 15 | @Schema(description = "LiveKit Server WebSocket 地址;前端 connect 用") |
| 19 | 16 | private String livekitUrl; | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/rtc/vo/ImRtcGroupCallRespVO.java
| ... | ... | @@ -10,10 +10,7 @@ import java.util.Set; |
| 10 | 10 | public class ImRtcGroupCallRespVO { |
| 11 | 11 | |
| 12 | 12 | @Schema(description = "业务通话编号") |
| 13 | - private String callId; | |
| 14 | - | |
| 15 | - @Schema(description = "LiveKit 房间名") | |
| 16 | - private String roomName; | |
| 13 | + private String room; | |
| 17 | 14 | |
| 18 | 15 | @Schema(description = "群编号") |
| 19 | 16 | private Long groupId; | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/rtc/ImRtcCallDO.java
| ... | ... | @@ -16,7 +16,7 @@ import java.time.LocalDateTime; |
| 16 | 16 | /** |
| 17 | 17 | * IM 通话记录 DO(房间级 / 主表) |
| 18 | 18 | * <p> |
| 19 | - * 一通通话一行;状态机 CREATED → RUNNING → ENDED;和明细表 {@link ImRtcParticipantDO} 通过 callId 关联 | |
| 19 | + * 一通通话一行;状态机 CREATED → RUNNING → ENDED;和明细表 {@link ImRtcParticipantDO} 通过 {@link #room} 关联 | |
| 20 | 20 | * |
| 21 | 21 | * @author 芋道源码 |
| 22 | 22 | */ |
| ... | ... | @@ -31,25 +31,14 @@ import java.time.LocalDateTime; |
| 31 | 31 | public class ImRtcCallDO extends BaseDO { |
| 32 | 32 | |
| 33 | 33 | /** |
| 34 | - * LiveKit 房间名前缀;roomName 格式固定为 {@code ROOM_NAME_PREFIX + callId} | |
| 35 | - */ | |
| 36 | - public static final String ROOM_NAME_PREFIX = "im_rtc_call_"; | |
| 37 | - | |
| 38 | - /** | |
| 39 | 34 | * 编号 |
| 40 | 35 | */ |
| 41 | 36 | @TableId |
| 42 | 37 | private Long id; |
| 43 | 38 | /** |
| 44 | - * 业务通话编号;唯一;UUID 生成 | |
| 45 | - */ | |
| 46 | - private String callId; | |
| 47 | - /** | |
| 48 | - * LiveKit 房间名 | |
| 49 | - * | |
| 50 | - * 格式 {@code im_rtc_call_{callId}} | |
| 39 | + * 业务通话编号(UUID,同时作为 LiveKit 房间名);唯一 | |
| 51 | 40 | */ |
| 52 | - private String roomName; | |
| 41 | + private String room; | |
| 53 | 42 | /** |
| 54 | 43 | * 会话类型 |
| 55 | 44 | * | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/rtc/ImRtcParticipantDO.java
| ... | ... | @@ -13,7 +13,7 @@ import java.time.LocalDateTime; |
| 13 | 13 | /** |
| 14 | 14 | * IM 通话参与者 DO(用户级 / 明细表) |
| 15 | 15 | * <p> |
| 16 | - * 一通通话每个参与者一行;通过 callId 关联主表 {@link ImRtcCallDO} | |
| 16 | + * 一通通话每个参与者一行;通过 {@link #room} 关联主表 {@link ImRtcCallDO} | |
| 17 | 17 | * <p> |
| 18 | 18 | * 终态闭合:通话 ENDED 时所有明细 status 必属 {LEFT / REJECTED / NO_ANSWER} 之一 |
| 19 | 19 | * |
| ... | ... | @@ -37,9 +37,9 @@ public class ImRtcParticipantDO extends BaseDO { |
| 37 | 37 | /** |
| 38 | 38 | * 业务通话编号 |
| 39 | 39 | * <p> |
| 40 | - * 关联 {@link ImRtcCallDO#getCallId()} | |
| 40 | + * 关联 {@link ImRtcCallDO#getRoom()} | |
| 41 | 41 | */ |
| 42 | - private String callId; | |
| 42 | + private String room; | |
| 43 | 43 | /** |
| 44 | 44 | * 参与者用户编号 |
| 45 | 45 | * <p> | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/rtc/ImRtcCallMapper.java
| ... | ... | @@ -18,12 +18,8 @@ import java.util.List; |
| 18 | 18 | @Mapper |
| 19 | 19 | public interface ImRtcCallMapper extends BaseMapperX<ImRtcCallDO> { |
| 20 | 20 | |
| 21 | - default ImRtcCallDO selectByCallId(String callId) { | |
| 22 | - return selectOne(ImRtcCallDO::getCallId, callId); | |
| 23 | - } | |
| 24 | - | |
| 25 | - default ImRtcCallDO selectByRoomName(String roomName) { | |
| 26 | - return selectOne(ImRtcCallDO::getRoomName, roomName); | |
| 21 | + default ImRtcCallDO selectByRoom(String room) { | |
| 22 | + return selectOne(ImRtcCallDO::getRoom, room); | |
| 27 | 23 | } |
| 28 | 24 | |
| 29 | 25 | default ImRtcCallDO selectLastOneByGroupIdAndStatusIn(Long groupId, Collection<Integer> statuses) { | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/rtc/ImRtcParticipantMapper.java
| ... | ... | @@ -17,19 +17,19 @@ import java.util.List; |
| 17 | 17 | @Mapper |
| 18 | 18 | public interface ImRtcParticipantMapper extends BaseMapperX<ImRtcParticipantDO> { |
| 19 | 19 | |
| 20 | - default ImRtcParticipantDO selectByCallIdAndUserId(String callId, Long userId) { | |
| 20 | + default ImRtcParticipantDO selectByRoomAndUserId(String room, Long userId) { | |
| 21 | 21 | return selectOne(new LambdaQueryWrapperX<ImRtcParticipantDO>() |
| 22 | - .eq(ImRtcParticipantDO::getCallId, callId) | |
| 22 | + .eq(ImRtcParticipantDO::getRoom, room) | |
| 23 | 23 | .eq(ImRtcParticipantDO::getUserId, userId)); |
| 24 | 24 | } |
| 25 | 25 | |
| 26 | - default List<ImRtcParticipantDO> selectListByCallId(String callId) { | |
| 27 | - return selectList(ImRtcParticipantDO::getCallId, callId); | |
| 26 | + default List<ImRtcParticipantDO> selectListByRoom(String room) { | |
| 27 | + return selectList(ImRtcParticipantDO::getRoom, room); | |
| 28 | 28 | } |
| 29 | 29 | |
| 30 | - default List<ImRtcParticipantDO> selectListByCallIdAndStatus(String callId, Integer status) { | |
| 30 | + default List<ImRtcParticipantDO> selectListByRoomAndStatus(String room, Integer status) { | |
| 31 | 31 | return selectList(new LambdaQueryWrapperX<ImRtcParticipantDO>() |
| 32 | - .eq(ImRtcParticipantDO::getCallId, callId) | |
| 32 | + .eq(ImRtcParticipantDO::getRoom, room) | |
| 33 | 33 | .eq(ImRtcParticipantDO::getStatus, status)); |
| 34 | 34 | } |
| 35 | 35 | |
| ... | ... | @@ -47,9 +47,9 @@ public interface ImRtcParticipantMapper extends BaseMapperX<ImRtcParticipantDO> |
| 47 | 47 | } |
| 48 | 48 | |
| 49 | 49 | @SuppressWarnings("UnusedReturnValue") |
| 50 | - default int updateByCallIdAndStatus(String callId, Integer oldStatus, ImRtcParticipantDO updateObj) { | |
| 50 | + default int updateByRoomAndStatus(String room, Integer oldStatus, ImRtcParticipantDO updateObj) { | |
| 51 | 51 | return update(updateObj, Wrappers.<ImRtcParticipantDO>lambdaUpdate() |
| 52 | - .eq(ImRtcParticipantDO::getCallId, callId) | |
| 52 | + .eq(ImRtcParticipantDO::getRoom, room) | |
| 53 | 53 | .eq(ImRtcParticipantDO::getStatus, oldStatus)); |
| 54 | 54 | } |
| 55 | 55 | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/rtc/core/LiveKitClient.java
| ... | ... | @@ -62,18 +62,18 @@ public class LiveKitClient { |
| 62 | 62 | * |
| 63 | 63 | * @param identity 用户唯一标识;写入 sub claim;同 identity 重连会踢前一个连接 |
| 64 | 64 | * @param displayName 客户端展示名;可空 |
| 65 | - * @param roomName 房间名 | |
| 65 | + * @param room 房间名 | |
| 66 | 66 | * @return JWT 字符串 |
| 67 | 67 | */ |
| 68 | - public String signJoinToken(String identity, String displayName, String roomName) { | |
| 68 | + public String signJoinToken(String identity, String displayName, String room) { | |
| 69 | 69 | Assert.notBlank(identity, "identity 不可为空"); |
| 70 | - Assert.notBlank(roomName, "roomName 不可为空"); | |
| 70 | + Assert.notBlank(room, "room 不可为空"); | |
| 71 | 71 | ImProperties.Rtc cfg = imProperties.getRtc(); |
| 72 | 72 | |
| 73 | 73 | // video claim:限定客户端能在该房间内做什么 |
| 74 | 74 | Map<String, Object> video = new HashMap<>(); |
| 75 | 75 | video.put("roomJoin", true); // 允许加入房间 |
| 76 | - video.put("room", roomName); // 限定只能加入这个房间 | |
| 76 | + video.put("room", room); // 限定只能加入这个房间 | |
| 77 | 77 | video.put("canPublish", true); // 允许发布媒体(推流) |
| 78 | 78 | video.put("canSubscribe", true); // 允许订阅媒体(拉流) |
| 79 | 79 | video.put("canPublishData", true); // 允许发送 data channel 消息 |
| ... | ... | @@ -96,11 +96,11 @@ public class LiveKitClient { |
| 96 | 96 | /** |
| 97 | 97 | * 调用 LiveKit Server API 删除房间:用于通话结束时,强制断开异常残留客户端 |
| 98 | 98 | * |
| 99 | - * @param roomName 房间名 | |
| 99 | + * @param room 房间名 | |
| 100 | 100 | */ |
| 101 | 101 | @SuppressWarnings("EmptyTryBlock") |
| 102 | - public void deleteRoom(String roomName) { | |
| 103 | - try (HttpResponse ignored = postTwirp(TWIRP_DELETE_ROOM, roomName)) { | |
| 102 | + public void deleteRoom(String room) { | |
| 103 | + try (HttpResponse ignored = postTwirp(TWIRP_DELETE_ROOM, room)) { | |
| 104 | 104 | // 状态码不区分;调用方失败即兜底 |
| 105 | 105 | } |
| 106 | 106 | } |
| ... | ... | @@ -110,17 +110,17 @@ public class LiveKitClient { |
| 110 | 110 | * <p> |
| 111 | 111 | * 房间不存在 LiveKit 返回 404,视同 0 人 |
| 112 | 112 | * |
| 113 | - * @param roomName 房间名 | |
| 113 | + * @param room 房间名 | |
| 114 | 114 | * @return 参与者数量;HTTP 失败返回 -1 |
| 115 | 115 | */ |
| 116 | - public int listParticipants(String roomName) { | |
| 117 | - try (HttpResponse response = postTwirp(TWIRP_LIST_PARTICIPANTS, roomName)) { | |
| 116 | + public int listParticipants(String room) { | |
| 117 | + try (HttpResponse response = postTwirp(TWIRP_LIST_PARTICIPANTS, room)) { | |
| 118 | 118 | if (response.getStatus() == 404) { |
| 119 | 119 | return 0; |
| 120 | 120 | } |
| 121 | 121 | if (!response.isOk()) { |
| 122 | - log.warn("[listParticipants][LiveKit 返回非 2xx status={} roomName={} body={}]", | |
| 123 | - response.getStatus(), roomName, response.body()); | |
| 122 | + log.warn("[listParticipants][LiveKit 返回非 2xx status={} room={} body={}]", | |
| 123 | + response.getStatus(), room, response.body()); | |
| 124 | 124 | return -1; |
| 125 | 125 | } |
| 126 | 126 | JSONArray participants = JSONUtil.parseObj(response.body()).getJSONArray("participants"); |
| ... | ... | @@ -223,16 +223,16 @@ public class LiveKitClient { |
| 223 | 223 | * Twirp 协议 POST 调用;统一处理 ws→http 协议切换、签 admin token、Bearer 头、JSON body、超时 |
| 224 | 224 | * |
| 225 | 225 | * @param path Twirp 端点路径,例如 {@code /twirp/livekit.RoomService/DeleteRoom} |
| 226 | - * @param roomName 房间名;写入 JSON body 的 room 字段 | |
| 226 | + * @param room 房间名;写入 JSON body 的 room 字段 | |
| 227 | 227 | * @return HTTP 响应;调用方负责 close 与状态码判断 |
| 228 | 228 | */ |
| 229 | - private HttpResponse postTwirp(String path, String roomName) { | |
| 230 | - Assert.notBlank(roomName, "roomName 不可为空"); | |
| 229 | + private HttpResponse postTwirp(String path, String room) { | |
| 230 | + Assert.notBlank(room, "room 不可为空"); | |
| 231 | 231 | String token = signAdminToken(); |
| 232 | 232 | return HttpRequest.post(HttpUtils.wsUrlToHttp(imProperties.getRtc().getLivekitUrl()) + path) |
| 233 | 233 | .header("Authorization", "Bearer " + token) |
| 234 | 234 | .header("Content-Type", "application/json") |
| 235 | - .body(JSONUtil.toJsonStr(Map.of("room", roomName))) | |
| 235 | + .body(JSONUtil.toJsonStr(Map.of("room", room))) | |
| 236 | 236 | .timeout(SERVER_API_TIMEOUT_MS) |
| 237 | 237 | .execute(); |
| 238 | 238 | } | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallService.java
| ... | ... | @@ -28,17 +28,17 @@ public interface ImRtcCallService { |
| 28 | 28 | /** |
| 29 | 29 | * 加入已有群通话:用于群胶囊条「加入」按钮;旁观者作为 ACTIVE_JOIN 加入,邀请池内成员转 JOINED |
| 30 | 30 | * |
| 31 | - * @param userId 加入者用户编号 | |
| 32 | - * @param roomName LiveKit 房间名;从胶囊条 activeCall 拿 | |
| 31 | + * @param userId 加入者用户编号 | |
| 32 | + * @param room 业务通话编号;从胶囊条 activeCall 拿 | |
| 33 | 33 | * @return 通话主表 |
| 34 | 34 | */ |
| 35 | - ImRtcCallDO joinCall(Long userId, String roomName); | |
| 35 | + ImRtcCallDO joinCall(Long userId, String room); | |
| 36 | 36 | |
| 37 | 37 | /** |
| 38 | 38 | * 通话中追加成员:仅群通话场景可用;给新邀请人推 RTC_CALL(INVITE) |
| 39 | 39 | * |
| 40 | 40 | * @param userId 操作人编号;必须是当前会话参与者 |
| 41 | - * @param reqVO 房间名 + 新邀请的用户编号集合 | |
| 41 | + * @param reqVO room + 新邀请的用户编号集合 | |
| 42 | 42 | */ |
| 43 | 43 | void inviteMoreCall(Long userId, ImRtcCallInviteMoreReqVO reqVO); |
| 44 | 44 | |
| ... | ... | @@ -47,22 +47,22 @@ public interface ImRtcCallService { |
| 47 | 47 | * |
| 48 | 48 | * @return 通话主表 |
| 49 | 49 | */ |
| 50 | - ImRtcCallDO acceptCall(Long userId, String roomName); | |
| 50 | + ImRtcCallDO acceptCall(Long userId, String room); | |
| 51 | 51 | |
| 52 | 52 | /** |
| 53 | 53 | * 拒绝通话;仅 INVITING 状态可拒;群通话拒绝等同于不参与,房间仍存在 |
| 54 | 54 | */ |
| 55 | - void rejectCall(Long userId, String roomName); | |
| 55 | + void rejectCall(Long userId, String room); | |
| 56 | 56 | |
| 57 | 57 | /** |
| 58 | 58 | * 取消邀请;主叫在 INVITING 状态主动取消 |
| 59 | 59 | */ |
| 60 | - void cancelCall(Long userId, String roomName); | |
| 60 | + void cancelCall(Long userId, String room); | |
| 61 | 61 | |
| 62 | 62 | /** |
| 63 | 63 | * 离开通话;RUNNING 状态下离开;私聊任一方离开 = 通话结束;群通话最后一人离开才结束 |
| 64 | 64 | */ |
| 65 | - void leaveCall(Long userId, String roomName); | |
| 65 | + void leaveCall(Long userId, String room); | |
| 66 | 66 | |
| 67 | 67 | /** |
| 68 | 68 | * 校验用户仍是该通话的参与者,用于客户端重连或 Token 过期续期前的合法性检查; |
| ... | ... | @@ -70,7 +70,7 @@ public interface ImRtcCallService { |
| 70 | 70 | * |
| 71 | 71 | * @return 通话主表 |
| 72 | 72 | */ |
| 73 | - ImRtcCallDO refreshCallToken(Long userId, String roomName); | |
| 73 | + ImRtcCallDO refreshCallToken(Long userId, String room); | |
| 74 | 74 | |
| 75 | 75 | /** |
| 76 | 76 | * 查询当前正在进行的通话;目前仅群聊场景(胶囊条),私聊未来扩展再补 peerUserId 参数 |
| ... | ... | @@ -86,19 +86,19 @@ public interface ImRtcCallService { |
| 86 | 86 | /** |
| 87 | 87 | * 查询某通话的全部参与者明细;交给 Controller 拼装 inviteeIds / joinedUserIds |
| 88 | 88 | * |
| 89 | - * @param callId 业务通话编号 | |
| 89 | + * @param room 业务通话编号 | |
| 90 | 90 | * @return 参与者明细列表 |
| 91 | 91 | */ |
| 92 | - List<ImRtcParticipantDO> getCallParticipants(String callId); | |
| 92 | + List<ImRtcParticipantDO> getCallParticipants(String room); | |
| 93 | 93 | |
| 94 | 94 | /** |
| 95 | 95 | * 签发指定用户进入该通话的 LiveKit Token;供 Controller 拼接到响应 VO |
| 96 | 96 | * |
| 97 | - * @param userId 进房用户编号;token 内 displayName 取该用户昵称 | |
| 98 | - * @param roomName LiveKit 房间名 | |
| 97 | + * @param userId 进房用户编号;token 内 displayName 取该用户昵称 | |
| 98 | + * @param room 业务通话编号 | |
| 99 | 99 | * @return JWT 字符串 |
| 100 | 100 | */ |
| 101 | - String signCallToken(Long userId, String roomName); | |
| 101 | + String signCallToken(Long userId, String room); | |
| 102 | 102 | |
| 103 | 103 | /** |
| 104 | 104 | * 处理 LiveKit Webhook 事件;用于关 tab / 强杀 / 网络断开等异常退出场景的兜底清理 | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallServiceImpl.java
| ... | ... | @@ -155,28 +155,26 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 155 | 155 | * @return 通话主表 |
| 156 | 156 | */ |
| 157 | 157 | private ImRtcCallDO createCall(Long inviterId, ImRtcCallInviteReqVO reqVO) { |
| 158 | - // 1. 构造参数:派生 callId / roomName,解析被邀请池(私聊单元素;群聊 invitee 子集或全员) | |
| 159 | - // TODO @AI:callId、roomName 都抽成小方法。 | |
| 160 | - String callId = IdUtil.fastSimpleUUID(); | |
| 161 | - String roomName = ImRtcCallDO.ROOM_NAME_PREFIX + callId; | |
| 158 | + // 1. 构造参数:room 用 UUID;解析被邀请池 | |
| 159 | + String room = IdUtil.fastSimpleUUID(); | |
| 162 | 160 | LocalDateTime now = LocalDateTime.now(); |
| 163 | 161 | Set<Long> invitees = resolveInvitees(reqVO, inviterId); |
| 164 | 162 | |
| 165 | 163 | // 2.1 INSERT 主表;群聊发起人即时 JOINED 但通话仍处 CREATED,等首个非发起人接通才切 RUNNING |
| 166 | - ImRtcCallDO call = new ImRtcCallDO().setCallId(callId).setRoomName(roomName) | |
| 164 | + ImRtcCallDO call = new ImRtcCallDO().setRoom(room) | |
| 167 | 165 | .setConversationType(reqVO.getScene()).setMediaType(reqVO.getMediaType()) |
| 168 | 166 | .setInviterUserId(inviterId).setGroupId(reqVO.getGroupId()) |
| 169 | 167 | .setStatus(ImRtcCallStatusEnum.CREATED.getStatus()).setStartTime(now); |
| 170 | 168 | rtcCallMapper.insert(call); |
| 171 | 169 | // 2.2 批量 INSERT 参与表:发起人即时 JOINED,被邀请人 INVITING 等接通 |
| 172 | 170 | List<ImRtcParticipantDO> participants = new ArrayList<>(invitees.size() + 1); |
| 173 | - participants.add(new ImRtcParticipantDO().setCallId(callId).setUserId(inviterId) | |
| 171 | + participants.add(new ImRtcParticipantDO().setRoom(room).setUserId(inviterId) | |
| 174 | 172 | .setRole(ImRtcParticipantRoleEnum.INVITER.getRole()) |
| 175 | 173 | .setStatus(ImRtcParticipantStatusEnum.JOINED.getStatus()) |
| 176 | 174 | .setInviteTime(now).setAcceptTime(now)); |
| 177 | 175 | for (Long inviteeId : invitees) { |
| 178 | 176 | participants.add(new ImRtcParticipantDO() |
| 179 | - .setCallId(callId).setUserId(inviteeId) | |
| 177 | + .setRoom(room).setUserId(inviteeId) | |
| 180 | 178 | .setRole(ImRtcParticipantRoleEnum.INVITEE.getRole()) |
| 181 | 179 | .setStatus(ImRtcParticipantStatusEnum.INVITING.getStatus()) |
| 182 | 180 | .setInviteTime(now)); |
| ... | ... | @@ -197,10 +195,10 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 197 | 195 | } |
| 198 | 196 | |
| 199 | 197 | @Override |
| 200 | - public ImRtcCallDO joinCall(Long userId, String roomName) { | |
| 198 | + public ImRtcCallDO joinCall(Long userId, String room) { | |
| 201 | 199 | ensureEnabled(); |
| 202 | 200 | // 1.1 校验通话存在且活跃 |
| 203 | - ImRtcCallDO call = validateCallActive(roomName); | |
| 201 | + ImRtcCallDO call = validateCallActive(room); | |
| 204 | 202 | // 1.2 仅群通话支持「旁观者加入」 |
| 205 | 203 | if (!ImConversationTypeEnum.isGroup(call.getConversationType())) { |
| 206 | 204 | throw exception(RTC_GROUP_REQUIRED); |
| ... | ... | @@ -208,7 +206,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 208 | 206 | |
| 209 | 207 | // 2. 入参与表:已有记录切回 JOINED;不在记录则以 ACTIVE_JOIN 角色 INSERT |
| 210 | 208 | LocalDateTime now = LocalDateTime.now(); |
| 211 | - ImRtcParticipantDO existing = rtcParticipantMapper.selectByCallIdAndUserId(call.getCallId(), userId); | |
| 209 | + ImRtcParticipantDO existing = rtcParticipantMapper.selectByRoomAndUserId(call.getRoom(), userId); | |
| 212 | 210 | if (existing != null) { |
| 213 | 211 | // 非 JOINED 状态切回 JOINED + accept_time;INVITER / INVITEE 重连 / 旁观点胶囊条都走这条 |
| 214 | 212 | if (!ImRtcParticipantStatusEnum.isJoined(existing.getStatus())) { |
| ... | ... | @@ -217,7 +215,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 217 | 215 | } |
| 218 | 216 | } else { |
| 219 | 217 | // 旁观者主动加入:INSERT role=ACTIVE_JOIN |
| 220 | - rtcParticipantMapper.insert(new ImRtcParticipantDO().setCallId(call.getCallId()) | |
| 218 | + rtcParticipantMapper.insert(new ImRtcParticipantDO().setRoom(call.getRoom()) | |
| 221 | 219 | .setUserId(userId).setRole(ImRtcParticipantRoleEnum.ACTIVE_JOIN.getRole()) |
| 222 | 220 | .setStatus(ImRtcParticipantStatusEnum.JOINED.getStatus()).setInviteTime(now).setAcceptTime(now)); |
| 223 | 221 | } |
| ... | ... | @@ -231,13 +229,13 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 231 | 229 | public void inviteMoreCall(Long userId, ImRtcCallInviteMoreReqVO reqVO) { |
| 232 | 230 | ensureEnabled(); |
| 233 | 231 | // TODO @AI:校验的,按照 1.1 1.2 1.3 这样; |
| 234 | - ImRtcCallDO call = validateCallActive(reqVO.getRoomName()); | |
| 232 | + ImRtcCallDO call = validateCallActive(reqVO.getRoom()); | |
| 235 | 233 | // 仅群通话支持追加邀请 |
| 236 | 234 | if (!ImConversationTypeEnum.isGroup(call.getConversationType())) { |
| 237 | 235 | throw exception(RTC_GROUP_REQUIRED); |
| 238 | 236 | } |
| 239 | 237 | // 操作者必须是已在房间的参与者 |
| 240 | - ImRtcParticipantDO operator = rtcParticipantMapper.selectByCallIdAndUserId(call.getCallId(), userId); | |
| 238 | + ImRtcParticipantDO operator = rtcParticipantMapper.selectByRoomAndUserId(call.getRoom(), userId); | |
| 241 | 239 | if (operator == null || !ImRtcParticipantStatusEnum.isJoined(operator.getStatus())) { |
| 242 | 240 | throw exception(RTC_NOT_PARTICIPANT); |
| 243 | 241 | } |
| ... | ... | @@ -246,7 +244,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 246 | 244 | Set<Long> validMemberIds = new LinkedHashSet<>( |
| 247 | 245 | groupMemberService.getActiveGroupMemberUserIdsByGroupId(call.getGroupId())); |
| 248 | 246 | // TODO @AI:convertSet; |
| 249 | - Set<Long> existingUserIds = rtcParticipantMapper.selectListByCallId(call.getCallId()).stream() | |
| 247 | + Set<Long> existingUserIds = rtcParticipantMapper.selectListByRoom(call.getRoom()).stream() | |
| 250 | 248 | .map(ImRtcParticipantDO::getUserId) |
| 251 | 249 | .collect(Collectors.toSet()); |
| 252 | 250 | // TODO @AI:incomingUserIds;直接 remove 原始的元素;没必要;单独的; |
| ... | ... | @@ -262,7 +260,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 262 | 260 | LocalDateTime now = LocalDateTime.now(); |
| 263 | 261 | for (Long inviteeId : incoming) { |
| 264 | 262 | rtcParticipantMapper.insert(new ImRtcParticipantDO() |
| 265 | - .setCallId(call.getCallId()).setUserId(inviteeId) | |
| 263 | + .setRoom(call.getRoom()).setUserId(inviteeId) | |
| 266 | 264 | .setRole(ImRtcParticipantRoleEnum.INVITEE.getRole()) |
| 267 | 265 | .setStatus(ImRtcParticipantStatusEnum.INVITING.getStatus()) |
| 268 | 266 | .setInviteTime(now)); |
| ... | ... | @@ -276,11 +274,11 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 276 | 274 | } |
| 277 | 275 | |
| 278 | 276 | @Override |
| 279 | - public ImRtcCallDO acceptCall(Long userId, String roomName) { | |
| 277 | + public ImRtcCallDO acceptCall(Long userId, String room) { | |
| 280 | 278 | ensureEnabled(); |
| 281 | 279 | // TODO @AI:按照 inviteMoreCall 风格,优化下注释; |
| 282 | - ImRtcCallDO call = validateCallActive(roomName); | |
| 283 | - ImRtcParticipantDO participant = rtcParticipantMapper.selectByCallIdAndUserId(call.getCallId(), userId); | |
| 280 | + ImRtcCallDO call = validateCallActive(room); | |
| 281 | + ImRtcParticipantDO participant = rtcParticipantMapper.selectByRoomAndUserId(call.getRoom(), userId); | |
| 284 | 282 | if (participant == null) { |
| 285 | 283 | throw exception(RTC_NOT_PARTICIPANT); |
| 286 | 284 | } |
| ... | ... | @@ -305,11 +303,11 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 305 | 303 | } |
| 306 | 304 | |
| 307 | 305 | @Override |
| 308 | - public void rejectCall(Long userId, String roomName) { | |
| 306 | + public void rejectCall(Long userId, String room) { | |
| 309 | 307 | ensureEnabled(); |
| 310 | 308 | // TODO @AI:按照 inviteMoreCall 风格,优化下注释; |
| 311 | - ImRtcCallDO call = validateCallActive(roomName); | |
| 312 | - ImRtcParticipantDO participant = rtcParticipantMapper.selectByCallIdAndUserId(call.getCallId(), userId); | |
| 309 | + ImRtcCallDO call = validateCallActive(room); | |
| 310 | + ImRtcParticipantDO participant = rtcParticipantMapper.selectByRoomAndUserId(call.getRoom(), userId); | |
| 313 | 311 | if (participant == null) { |
| 314 | 312 | throw exception(RTC_NOT_PARTICIPANT); |
| 315 | 313 | } |
| ... | ... | @@ -337,10 +335,10 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 337 | 335 | } |
| 338 | 336 | |
| 339 | 337 | @Override |
| 340 | - public void cancelCall(Long userId, String roomName) { | |
| 338 | + public void cancelCall(Long userId, String room) { | |
| 341 | 339 | ensureEnabled(); |
| 342 | 340 | // TODO @AI:按照 inviteMoreCall 风格,优化下注释; |
| 343 | - ImRtcCallDO call = validateCallActive(roomName); | |
| 341 | + ImRtcCallDO call = validateCallActive(room); | |
| 344 | 342 | // TODO @AI:notEquals; |
| 345 | 343 | if (!Objects.equals(call.getInviterUserId(), userId)) { |
| 346 | 344 | throw exception(RTC_NOT_PARTICIPANT); |
| ... | ... | @@ -355,11 +353,11 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 355 | 353 | } |
| 356 | 354 | |
| 357 | 355 | @Override |
| 358 | - public void leaveCall(Long userId, String roomName) { | |
| 356 | + public void leaveCall(Long userId, String room) { | |
| 359 | 357 | ensureEnabled(); |
| 360 | 358 | // TODO @AI:按照 inviteMoreCall 风格,优化下注释; |
| 361 | - ImRtcCallDO call = validateCallActive(roomName); | |
| 362 | - ImRtcParticipantDO participant = rtcParticipantMapper.selectByCallIdAndUserId(call.getCallId(), userId); | |
| 359 | + ImRtcCallDO call = validateCallActive(room); | |
| 360 | + ImRtcParticipantDO participant = rtcParticipantMapper.selectByRoomAndUserId(call.getRoom(), userId); | |
| 363 | 361 | if (participant == null) { |
| 364 | 362 | throw exception(RTC_NOT_PARTICIPANT); |
| 365 | 363 | } |
| ... | ... | @@ -373,7 +371,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 373 | 371 | // TODO @AI:群聊的关房,本质和单聊,是不是一样的?是否有必要统一掉? |
| 374 | 372 | // 关房条件:私聊任一方离开 = 关;群通话仅在「无人在房 + 无人响铃」时关 |
| 375 | 373 | if (ImConversationTypeEnum.isPrivate(call.getConversationType()) |
| 376 | - || shouldCloseGroupRoom(call.getCallId())) { | |
| 374 | + || shouldCloseGroupRoom(call.getRoom())) { | |
| 377 | 375 | endSession(call, userId, ImRtcCallEndReasonEnum.HANGUP); |
| 378 | 376 | } |
| 379 | 377 | // 群通话单人离开:LiveKit `ParticipantDisconnected` 自动通知房内成员;业务后端不另外推 |
| ... | ... | @@ -386,9 +384,9 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 386 | 384 | * <p> |
| 387 | 385 | * 关房后 endSession 会把残留 INVITING 批量改 NO_ANSWER 并推 RTC_CALL_END,响铃端 UI 自动收敛 |
| 388 | 386 | */ |
| 389 | - private boolean shouldCloseGroupRoom(String callId) { | |
| 387 | + private boolean shouldCloseGroupRoom(String room) { | |
| 390 | 388 | // TODO @AI:通过查询,计算情况?;感觉所有群聊的关闭条件:都是只有 1 个 join,剩余都处于结束,说白了没人可以和他电话了。。。。 |
| 391 | - List<ImRtcParticipantDO> joined = rtcParticipantMapper.selectListByCallIdAndStatus(callId, | |
| 389 | + List<ImRtcParticipantDO> joined = rtcParticipantMapper.selectListByRoomAndStatus(room, | |
| 392 | 390 | ImRtcParticipantStatusEnum.JOINED.getStatus()); |
| 393 | 391 | return CollUtil.isEmpty(joined); |
| 394 | 392 | } |
| ... | ... | @@ -406,13 +404,13 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 406 | 404 | if (aPart == null) { |
| 407 | 405 | return null; |
| 408 | 406 | } |
| 409 | - ImRtcCallDO call = rtcCallMapper.selectByCallId(aPart.getCallId()); | |
| 407 | + ImRtcCallDO call = rtcCallMapper.selectByRoom(aPart.getRoom()); | |
| 410 | 408 | if (call == null |
| 411 | 409 | || !ImConversationTypeEnum.isPrivate(call.getConversationType()) |
| 412 | 410 | || ImRtcCallStatusEnum.isEnded(call.getStatus())) { |
| 413 | 411 | return null; |
| 414 | 412 | } |
| 415 | - ImRtcParticipantDO bPart = rtcParticipantMapper.selectByCallIdAndUserId(call.getCallId(), userIdB); | |
| 413 | + ImRtcParticipantDO bPart = rtcParticipantMapper.selectByRoomAndUserId(call.getRoom(), userIdB); | |
| 416 | 414 | if (bPart == null |
| 417 | 415 | || !ImRtcParticipantStatusEnum.ACTIVE_STATUSES.contains(bPart.getStatus())) { |
| 418 | 416 | return null; |
| ... | ... | @@ -422,11 +420,11 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 422 | 420 | |
| 423 | 421 | // TODO @AI:怎么感觉这个要改下?是不是根据 userid + callid 更合理? |
| 424 | 422 | @Override |
| 425 | - public ImRtcCallDO refreshCallToken(Long userId, String roomName) { | |
| 423 | + public ImRtcCallDO refreshCallToken(Long userId, String room) { | |
| 426 | 424 | ensureEnabled(); |
| 427 | 425 | // TODO @AI:按照 inviteMoreCall 风格,优化下注释; |
| 428 | - ImRtcCallDO call = validateCallActive(roomName); | |
| 429 | - ImRtcParticipantDO participant = rtcParticipantMapper.selectByCallIdAndUserId(call.getCallId(), userId); | |
| 426 | + ImRtcCallDO call = validateCallActive(room); | |
| 427 | + ImRtcParticipantDO participant = rtcParticipantMapper.selectByRoomAndUserId(call.getRoom(), userId); | |
| 430 | 428 | if (participant == null) { |
| 431 | 429 | throw exception(RTC_NOT_PARTICIPANT); |
| 432 | 430 | } |
| ... | ... | @@ -444,16 +442,16 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 444 | 442 | |
| 445 | 443 | // TODO @AI:方法名,复数应该使用 List,不使用 s; |
| 446 | 444 | @Override |
| 447 | - public List<ImRtcParticipantDO> getCallParticipants(String callId) { | |
| 448 | - return rtcParticipantMapper.selectListByCallId(callId); | |
| 445 | + public List<ImRtcParticipantDO> getCallParticipants(String room) { | |
| 446 | + return rtcParticipantMapper.selectListByRoom(room); | |
| 449 | 447 | } |
| 450 | 448 | |
| 451 | 449 | @Override |
| 452 | - public String signCallToken(Long userId, String roomName) { | |
| 450 | + public String signCallToken(Long userId, String room) { | |
| 453 | 451 | AdminUserRespDTO user = adminUserApi.getUser(userId); |
| 454 | 452 | // TODO @AI:不能为空。。。nickname 为空,则使用 userid; |
| 455 | 453 | String displayName = user == null ? null : user.getNickname(); |
| 456 | - return signToken(userId, displayName, roomName); | |
| 454 | + return signToken(userId, displayName, room); | |
| 457 | 455 | } |
| 458 | 456 | |
| 459 | 457 | // TODO DONE @AI:兼容 JDK8 —— 项目 Spring Boot 3 已要求 JDK17+,switch arrow case 可直接用 |
| ... | ... | @@ -483,8 +481,8 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 483 | 481 | return; |
| 484 | 482 | } |
| 485 | 483 | // 前置检查 |
| 486 | - String roomName = event.getRoom().getName(); | |
| 487 | - ImRtcCallDO call = rtcCallMapper.selectByRoomName(roomName); | |
| 484 | + String room = event.getRoom().getName(); | |
| 485 | + ImRtcCallDO call = rtcCallMapper.selectByRoom(room); | |
| 488 | 486 | if (call == null || ImRtcCallStatusEnum.isEnded(call.getStatus())) { |
| 489 | 487 | return; |
| 490 | 488 | } |
| ... | ... | @@ -506,8 +504,8 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 506 | 504 | return; |
| 507 | 505 | } |
| 508 | 506 | // 前置检查 |
| 509 | - String roomName = event.getRoom().getName(); | |
| 510 | - ImRtcCallDO call = rtcCallMapper.selectByRoomName(roomName); | |
| 507 | + String room = event.getRoom().getName(); | |
| 508 | + ImRtcCallDO call = rtcCallMapper.selectByRoom(room); | |
| 511 | 509 | if (call == null || ImRtcCallStatusEnum.isEnded(call.getStatus())) { |
| 512 | 510 | return; |
| 513 | 511 | } |
| ... | ... | @@ -515,7 +513,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 515 | 513 | if (userId == null) { |
| 516 | 514 | return; |
| 517 | 515 | } |
| 518 | - ImRtcParticipantDO participant = rtcParticipantMapper.selectByCallIdAndUserId(call.getCallId(), userId); | |
| 516 | + ImRtcParticipantDO participant = rtcParticipantMapper.selectByRoomAndUserId(call.getRoom(), userId); | |
| 519 | 517 | if (participant == null || !ImRtcParticipantStatusEnum.isJoined(participant.getStatus())) { |
| 520 | 518 | return; |
| 521 | 519 | } |
| ... | ... | @@ -526,14 +524,14 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 526 | 524 | if (updated == 0) { |
| 527 | 525 | return; |
| 528 | 526 | } |
| 529 | - log.info("[handleParticipantLeft][roomName={} userId={} 由 LiveKit Webhook 兜底]", roomName, userId); | |
| 527 | + log.info("[handleParticipantLeft][room={} userId={} 由 LiveKit Webhook 兜底]", room, userId); | |
| 530 | 528 | |
| 531 | 529 | // 推 1603 通知参与方 / 全群参与方 |
| 532 | 530 | pushParticipantDisconnectedNotification(call, userId); |
| 533 | 531 | // 关房条件与 leave 一致;群通话仅在「无人在房 + 无人响铃」时关 |
| 534 | 532 | // TODO @AI:注释风格;感觉可以抽个 endSessionIfXXX;收口更统一; |
| 535 | 533 | if (ImConversationTypeEnum.isPrivate(call.getConversationType()) |
| 536 | - || shouldCloseGroupRoom(call.getCallId())) { | |
| 534 | + || shouldCloseGroupRoom(call.getRoom())) { | |
| 537 | 535 | endSession(call, userId, ImRtcCallEndReasonEnum.HANGUP); |
| 538 | 536 | } |
| 539 | 537 | } |
| ... | ... | @@ -546,14 +544,14 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 546 | 544 | return; |
| 547 | 545 | } |
| 548 | 546 | // 前置检查 |
| 549 | - String roomName = event.getRoom().getName(); | |
| 550 | - ImRtcCallDO call = rtcCallMapper.selectByRoomName(roomName); | |
| 547 | + String room = event.getRoom().getName(); | |
| 548 | + ImRtcCallDO call = rtcCallMapper.selectByRoom(room); | |
| 551 | 549 | if (call == null || ImRtcCallStatusEnum.isEnded(call.getStatus())) { |
| 552 | 550 | return; |
| 553 | 551 | } |
| 554 | 552 | |
| 555 | 553 | // TODO @AI:注释。带序号的; |
| 556 | - log.info("[handleRoomFinished][roomName={} 由 LiveKit Webhook 兜底]", roomName); | |
| 554 | + log.info("[handleRoomFinished][room={} 由 LiveKit Webhook 兜底]", room); | |
| 557 | 555 | endSession(call, null, ImRtcCallEndReasonEnum.HANGUP); |
| 558 | 556 | } |
| 559 | 557 | |
| ... | ... | @@ -572,17 +570,15 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 572 | 570 | for (ImRtcCallDO call : candidates) { |
| 573 | 571 | int count; |
| 574 | 572 | try { |
| 575 | - count = liveKitClient.listParticipants(call.getRoomName()); | |
| 573 | + count = liveKitClient.listParticipants(call.getRoom()); | |
| 576 | 574 | } catch (Exception e) { |
| 577 | - log.warn("[cleanupZombieCalls][查询 LiveKit 失败 callId={} roomName={}]", | |
| 578 | - call.getCallId(), call.getRoomName(), e); | |
| 575 | + log.warn("[cleanupZombieCalls][查询 LiveKit 失败 room={}]", call.getRoom(), e); | |
| 579 | 576 | continue; |
| 580 | 577 | } |
| 581 | 578 | if (count != 0) { |
| 582 | 579 | continue; |
| 583 | 580 | } |
| 584 | - log.info("[cleanupZombieCalls][清理僵尸通话 callId={} roomName={}]", | |
| 585 | - call.getCallId(), call.getRoomName()); | |
| 581 | + log.info("[cleanupZombieCalls][清理僵尸通话 room={}]", call.getRoom()); | |
| 586 | 582 | endSession(call, null, ImRtcCallEndReasonEnum.HANGUP); |
| 587 | 583 | cleaned++; |
| 588 | 584 | } |
| ... | ... | @@ -604,8 +600,8 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 604 | 600 | /** |
| 605 | 601 | * 校验通话存在且活跃:不存在 / 已 ENDED 直接抛 |
| 606 | 602 | */ |
| 607 | - private ImRtcCallDO validateCallActive(String roomName) { | |
| 608 | - ImRtcCallDO call = rtcCallMapper.selectByRoomName(roomName); | |
| 603 | + private ImRtcCallDO validateCallActive(String room) { | |
| 604 | + ImRtcCallDO call = rtcCallMapper.selectByRoom(room); | |
| 609 | 605 | if (call == null || ImRtcCallStatusEnum.isEnded(call.getStatus())) { |
| 610 | 606 | throw exception(RTC_SESSION_NOT_EXISTS); |
| 611 | 607 | } |
| ... | ... | @@ -729,29 +725,29 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 729 | 725 | new ImRtcCallDO().setStatus(ImRtcCallStatusEnum.ENDED.getStatus()) |
| 730 | 726 | .setEndReason(reason.getReason()).setEndTime(now)); |
| 731 | 727 | if (updated == 0) { |
| 732 | - log.info("[endSession][已被另一路径终结,跳过 callId={} roomName={} operator={} reason={}]", | |
| 733 | - call.getCallId(), call.getRoomName(), operatorId, reason); | |
| 728 | + log.info("[endSession][已被另一路径终结,跳过 room={} operator={} reason={}]", | |
| 729 | + call.getRoom(), operatorId, reason); | |
| 734 | 730 | return; |
| 735 | 731 | } |
| 736 | 732 | // 1.2 更新参与表为已结束:残留 INVITING 改 NO_ANSWER;残留 JOINED 改 LEFT 并写 leaveTime |
| 737 | - rtcParticipantMapper.updateByCallIdAndStatus(call.getCallId(), ImRtcParticipantStatusEnum.INVITING.getStatus(), | |
| 733 | + rtcParticipantMapper.updateByRoomAndStatus(call.getRoom(), ImRtcParticipantStatusEnum.INVITING.getStatus(), | |
| 738 | 734 | new ImRtcParticipantDO().setStatus(ImRtcParticipantStatusEnum.NO_ANSWER.getStatus())); |
| 739 | - rtcParticipantMapper.updateByCallIdAndStatus(call.getCallId(), ImRtcParticipantStatusEnum.JOINED.getStatus(), | |
| 735 | + rtcParticipantMapper.updateByRoomAndStatus(call.getRoom(), ImRtcParticipantStatusEnum.JOINED.getStatus(), | |
| 740 | 736 | new ImRtcParticipantDO().setStatus(ImRtcParticipantStatusEnum.LEFT.getStatus()).setLeaveTime(now)); |
| 741 | 737 | |
| 742 | 738 | // 2. 兜底删除 LiveKit 房间,强制断开异常残留客户端;失败仅记日志,不阻断业务 |
| 743 | 739 | try { |
| 744 | - liveKitClient.deleteRoom(call.getRoomName()); | |
| 740 | + liveKitClient.deleteRoom(call.getRoom()); | |
| 745 | 741 | } catch (Exception e) { |
| 746 | - log.warn("[endSession][删除 LiveKit 房间失败 callId={} roomName={} operator={} reason={}]", | |
| 747 | - call.getCallId(), call.getRoomName(), operatorId, reason, e); | |
| 742 | + log.warn("[endSession][删除 LiveKit 房间失败 room={} operator={} reason={}]", | |
| 743 | + call.getRoom(), operatorId, reason, e); | |
| 748 | 744 | } |
| 749 | 745 | |
| 750 | 746 | // 3. 推 RTC_CALL_END |
| 751 | 747 | Long durationSeconds = call.getAcceptTime() != null ? |
| 752 | 748 | Duration.between(call.getAcceptTime(), now).getSeconds() : null; |
| 753 | 749 | pushCallEndNotification(call, operatorId, reason, durationSeconds); |
| 754 | - log.info("[endSession][roomName={} operator={} reason={}]", call.getRoomName(), operatorId, reason); | |
| 750 | + log.info("[endSession][room={} operator={} reason={}]", call.getRoom(), operatorId, reason); | |
| 755 | 751 | } |
| 756 | 752 | |
| 757 | 753 | // TODO @AI:getCallAudienceUserIdList? |
| ... | ... | @@ -765,7 +761,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 765 | 761 | return groupMemberService.getActiveGroupMemberUserIdsByGroupId(call.getGroupId()); |
| 766 | 762 | } |
| 767 | 763 | return CollectionUtils.convertSet( |
| 768 | - rtcParticipantMapper.selectListByCallId(call.getCallId()), ImRtcParticipantDO::getUserId); | |
| 764 | + rtcParticipantMapper.selectListByRoom(call.getRoom()), ImRtcParticipantDO::getUserId); | |
| 769 | 765 | } |
| 770 | 766 | |
| 771 | 767 | // TODO @AI:这里的 ========== 太短了; |
| ... | ... | @@ -779,7 +775,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 779 | 775 | * 不能走 imPrivateMessageService.send:persistent=false 时 send 仅推 sender 多端,receiver 不感知 |
| 780 | 776 | */ |
| 781 | 777 | private void pushCallInviteNotification(ImRtcCallDO call, AdminUserRespDTO inviter, Long inviteeId) { |
| 782 | - String token = signToken(inviteeId, inviter == null ? null : inviter.getNickname(), call.getRoomName()); | |
| 778 | + String token = signToken(inviteeId, inviter == null ? null : inviter.getNickname(), call.getRoom()); | |
| 783 | 779 | ImRtcCallNotification payload = ImRtcCallNotification.ofInvite( |
| 784 | 780 | call, inviter, imProperties.getRtc().getLivekitUrl(), token); |
| 785 | 781 | webSocketService.sendPrivateMessageAsync(inviteeId, ImPrivateMessageDTO.ofRtcNotification( |
| ... | ... | @@ -865,7 +861,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 865 | 861 | |
| 866 | 862 | // 私聊:发起人总是 sender;receiver = 通话另一方 |
| 867 | 863 | ImRtcParticipantDO peer = CollUtil.findOne( |
| 868 | - rtcParticipantMapper.selectListByCallId(call.getCallId()), | |
| 864 | + rtcParticipantMapper.selectListByRoom(call.getRoom()), | |
| 869 | 865 | p -> !Objects.equals(p.getUserId(), senderId)); |
| 870 | 866 | Long receiverId = peer != null ? peer.getUserId() : senderId; |
| 871 | 867 | ImPrivateMessageSendDTO dto = new ImPrivateMessageSendDTO().setReceiverId(receiverId) |
| ... | ... | @@ -879,9 +875,9 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { |
| 879 | 875 | * 签 LiveKit Token |
| 880 | 876 | */ |
| 881 | 877 | // TODO @AI:sign 方法,是不是传递 adminuserdo? |
| 882 | - private String signToken(Long userId, String displayName, String roomName) { | |
| 883 | - // TODO @AI:是不是抽一个 signJoinToken(userid, displayName, roomName)做减法 | |
| 884 | - return liveKitClient.signJoinToken(liveKitClient.buildIdentity(userId), displayName, roomName); | |
| 878 | + private String signToken(Long userId, String displayName, String room) { | |
| 879 | + // TODO @AI:是不是抽一个 signJoinToken(userid, displayName, room)做减法 | |
| 880 | + return liveKitClient.signJoinToken(liveKitClient.buildIdentity(userId), displayName, room); | |
| 885 | 881 | } |
| 886 | 882 | |
| 887 | 883 | } | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/rtc/ImRtcCallEndNotification.java
| ... | ... | @@ -25,7 +25,7 @@ public class ImRtcCallEndNotification { |
| 25 | 25 | /** |
| 26 | 26 | * 业务通话编号 |
| 27 | 27 | */ |
| 28 | - private String callId; | |
| 28 | + private String room; | |
| 29 | 29 | /** |
| 30 | 30 | * 会话类型 |
| 31 | 31 | */ |
| ... | ... | @@ -60,7 +60,7 @@ public class ImRtcCallEndNotification { |
| 60 | 60 | public static ImRtcCallEndNotification of(ImRtcCallDO call, ImRtcCallEndReasonEnum reason, Long durationSeconds, |
| 61 | 61 | Long operatorId, AdminUserRespDTO operator) { |
| 62 | 62 | ImRtcCallEndNotification notification = new ImRtcCallEndNotification(); |
| 63 | - notification.callId = call.getCallId(); | |
| 63 | + notification.room = call.getRoom(); | |
| 64 | 64 | notification.conversationType = call.getConversationType(); |
| 65 | 65 | notification.mediaType = call.getMediaType(); |
| 66 | 66 | notification.endReason = reason.getReason(); | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/rtc/ImRtcCallNotification.java
| ... | ... | @@ -27,11 +27,7 @@ public class ImRtcCallNotification { |
| 27 | 27 | /** |
| 28 | 28 | * 业务通话编号 |
| 29 | 29 | */ |
| 30 | - private String callId; | |
| 31 | - /** | |
| 32 | - * LiveKit 房间名 | |
| 33 | - */ | |
| 34 | - private String roomName; | |
| 30 | + private String room; | |
| 35 | 31 | /** |
| 36 | 32 | * 会话类型 |
| 37 | 33 | */ |
| ... | ... | @@ -176,8 +172,7 @@ public class ImRtcCallNotification { |
| 176 | 172 | private static ImRtcCallNotification baseOf(ImRtcCallDO call, Integer status) { |
| 177 | 173 | ImRtcCallNotification notification = new ImRtcCallNotification(); |
| 178 | 174 | notification.status = status; |
| 179 | - notification.callId = call.getCallId(); | |
| 180 | - notification.roomName = call.getRoomName(); | |
| 175 | + notification.room = call.getRoom(); | |
| 181 | 176 | notification.conversationType = call.getConversationType(); |
| 182 | 177 | notification.mediaType = call.getMediaType(); |
| 183 | 178 | notification.groupId = call.getGroupId(); | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/rtc/ImRtcCallStartNotification.java
| ... | ... | @@ -23,7 +23,7 @@ public class ImRtcCallStartNotification { |
| 23 | 23 | /** |
| 24 | 24 | * 业务通话编号 |
| 25 | 25 | */ |
| 26 | - private String callId; | |
| 26 | + private String room; | |
| 27 | 27 | /** |
| 28 | 28 | * 会话类型:当前固定 GROUP(私聊无 START) |
| 29 | 29 | */ |
| ... | ... | @@ -47,7 +47,7 @@ public class ImRtcCallStartNotification { |
| 47 | 47 | |
| 48 | 48 | public static ImRtcCallStartNotification of(ImRtcCallDO call, AdminUserRespDTO inviter) { |
| 49 | 49 | ImRtcCallStartNotification notification = new ImRtcCallStartNotification(); |
| 50 | - notification.callId = call.getCallId(); | |
| 50 | + notification.room = call.getRoom(); | |
| 51 | 51 | notification.conversationType = call.getConversationType(); |
| 52 | 52 | notification.mediaType = call.getMediaType(); |
| 53 | 53 | notification.inviterUserId = call.getInviterUserId(); | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/rtc/ImRtcParticipantConnectedNotification.java
| ... | ... | @@ -18,11 +18,7 @@ public class ImRtcParticipantConnectedNotification { |
| 18 | 18 | /** |
| 19 | 19 | * 业务通话编号 |
| 20 | 20 | */ |
| 21 | - private String callId; | |
| 22 | - /** | |
| 23 | - * LiveKit 房间名 | |
| 24 | - */ | |
| 25 | - private String roomName; | |
| 21 | + private String room; | |
| 26 | 22 | /** |
| 27 | 23 | * 加入的参与者用户编号 |
| 28 | 24 | */ |
| ... | ... | @@ -53,8 +49,7 @@ public class ImRtcParticipantConnectedNotification { |
| 53 | 49 | */ |
| 54 | 50 | public static ImRtcParticipantConnectedNotification of(ImRtcCallDO call, Long userId) { |
| 55 | 51 | ImRtcParticipantConnectedNotification notification = new ImRtcParticipantConnectedNotification(); |
| 56 | - notification.callId = call.getCallId(); | |
| 57 | - notification.roomName = call.getRoomName(); | |
| 52 | + notification.room = call.getRoom(); | |
| 58 | 53 | notification.userId = userId; |
| 59 | 54 | notification.conversationType = call.getConversationType(); |
| 60 | 55 | notification.groupId = call.getGroupId(); | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/rtc/ImRtcParticipantDisconnectedNotification.java
| ... | ... | @@ -18,11 +18,7 @@ public class ImRtcParticipantDisconnectedNotification { |
| 18 | 18 | /** |
| 19 | 19 | * 业务通话编号 |
| 20 | 20 | */ |
| 21 | - private String callId; | |
| 22 | - /** | |
| 23 | - * LiveKit 房间名 | |
| 24 | - */ | |
| 25 | - private String roomName; | |
| 21 | + private String room; | |
| 26 | 22 | /** |
| 27 | 23 | * 离开的参与者用户编号 |
| 28 | 24 | */ |
| ... | ... | @@ -45,8 +41,7 @@ public class ImRtcParticipantDisconnectedNotification { |
| 45 | 41 | */ |
| 46 | 42 | public static ImRtcParticipantDisconnectedNotification of(ImRtcCallDO call, Long userId) { |
| 47 | 43 | ImRtcParticipantDisconnectedNotification notification = new ImRtcParticipantDisconnectedNotification(); |
| 48 | - notification.callId = call.getCallId(); | |
| 49 | - notification.roomName = call.getRoomName(); | |
| 44 | + notification.room = call.getRoom(); | |
| 50 | 45 | notification.userId = userId; |
| 51 | 46 | notification.conversationType = call.getConversationType(); |
| 52 | 47 | notification.groupId = call.getGroupId(); | ... | ... |