Commit 1eda319ea09583b5e799cb1864e067bc8f00bb61

Authored by YunaiV
1 parent dc081cfd

✨ feat(im): 通话事件接入会话列表预览(私聊补 START 入消息流);文案统一「语音通话」

yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageTypeEnum.java
@@ -101,7 +101,8 @@ public enum ImMessageTypeEnum implements ArrayValuable<Integer> { @@ -101,7 +101,8 @@ public enum ImMessageTypeEnum implements ArrayValuable<Integer> {
101 /** 101 /**
102 * 对应 OpenIM:无(自有扩展,OpenIM 通话事件不入消息流) 102 * 对应 OpenIM:无(自有扩展,OpenIM 通话事件不入消息流)
103 * 对应自己的类:ImRtcCallStartNotification 103 * 对应自己的类:ImRtcCallStartNotification
104 - * 场景:群通话开始;入 im_group_message + 全群广播;前端渲染聊天 tip「{inviterNickname} 发起了{voice/video}通话」 104 + * 场景:通话开始;群聊入 im_group_message 全群广播,前端渲染聊天 tip「{inviterNickname} 发起了语音通话」;
  105 + * 私聊入 im_private_message 定向给被叫,仅用于会话列表预览展示「[语音通话]」(不渲染聊天 tip)
105 * <p> 106 * <p>
106 * 与 RTC_CALL_END(1611) 两段式配对:START 一定先于 END 入库(START 在 invite 接口事务里、END 在 cancel/leave 接口事务里,自然按请求顺序串行) 107 * 与 RTC_CALL_END(1611) 两段式配对:START 一定先于 END 入库(START 在 invite 接口事务里、END 在 cancel/leave 接口事务里,自然按请求顺序串行)
107 */ 108 */
@@ -109,9 +110,9 @@ public enum ImMessageTypeEnum implements ArrayValuable&lt;Integer&gt; { @@ -109,9 +110,9 @@ public enum ImMessageTypeEnum implements ArrayValuable&lt;Integer&gt; {
109 /** 110 /**
110 * 对应 OpenIM:无(自有扩展,OpenIM 通话事件不入消息流) 111 * 对应 OpenIM:无(自有扩展,OpenIM 通话事件不入消息流)
111 * 对应自己的类:ImRtcCallEndNotification 112 * 对应自己的类:ImRtcCallEndNotification
112 - * 场景:通话结束;入 im_private_message / im_group_message;私聊渲染准气泡,群聊渲染 tip「{voice/video}通话已结束 [时长 X]」 113 + * 场景:通话结束;入 im_private_message / im_group_message;私聊渲染准气泡,群聊渲染 tip「语音通话已经结束」
113 * <p> 114 * <p>
114 - * 群通话两段式配对前导是 RTC_CALL_START(1610);私聊场景没有 START,单 END 准气泡承载完整生命周期 115 + * 与 RTC_CALL_START(1610) 两段式配对
115 */ 116 */
116 RTC_CALL_END(1611, "通话结束", true, false), 117 RTC_CALL_END(1611, "通话结束", true, false),
117 118
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallServiceImpl.java
@@ -234,10 +234,8 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { @@ -234,10 +234,8 @@ public class ImRtcCallServiceImpl implements ImRtcCallService {
234 for (Long inviteeId : invitees) { 234 for (Long inviteeId : invitees) {
235 pushCallInviteNotification(call, inviterUser, inviteeId, inviteeMap.get(inviteeId), invitees); 235 pushCallInviteNotification(call, inviterUser, inviteeId, inviteeMap.get(inviteeId), invitees);
236 } 236 }
237 - // 3.2 群通话再向全群广播 RTC_CALL_START 入聊天流  
238 - if (ImConversationTypeEnum.isGroup(reqVO.getConversationType())) {  
239 - pushCallStartNotification(call, inviterUser);  
240 - } 237 + // 3.2 向消息流写入 RTC_CALL_START;群聊全群广播,私聊定向给被叫,作为会话列表预览的依据
  238 + pushCallStartNotification(call, inviterUser, invitees);
241 return call; 239 return call;
242 } 240 }
243 241
@@ -891,16 +889,18 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { @@ -891,16 +889,18 @@ public class ImRtcCallServiceImpl implements ImRtcCallService {
891 } 889 }
892 890
893 /** 891 /**
894 - * RTC_CALL_START:仅群通话场景;走 imGroupMessageService.send,入 im_group_message 全群广播;与 RTC_CALL_END 两段式配对 892 + * RTC_CALL_START:群聊走 imGroupMessageService.send 全群广播;私聊走 imPrivateMessageService.send 定向给被叫
  893 + * <p>
  894 + * 私聊 peer 直接复用 createCall 解析好的 invitees,避免再查参与表;用于会话列表预览展示「[语音通话]」
895 * 895 *
896 - * @param call 通话主表  
897 - * @param inviter 发起人;可空 896 + * @param call 通话主表
  897 + * @param inviter 发起人;可空
  898 + * @param invitees 本次邀请池;私聊场景取首个作为 peer
898 */ 899 */
899 - private void pushCallStartNotification(ImRtcCallDO call, AdminUserRespDTO inviter) { 900 + private void pushCallStartNotification(ImRtcCallDO call, AdminUserRespDTO inviter, Set<Long> invitees) {
900 ImRtcCallStartNotification payload = ImRtcCallStartNotification.of(call, inviter); 901 ImRtcCallStartNotification payload = ImRtcCallStartNotification.of(call, inviter);
901 - ImGroupMessageSendDTO dto = new ImGroupMessageSendDTO().setGroupId(call.getGroupId())  
902 - .setType(ImMessageTypeEnum.RTC_CALL_START.getType()).setContent(payload);  
903 - groupMessageService.sendGroupMessage(call.getInviterUserId(), dto); 902 + Long peerUserId = ImConversationTypeEnum.isGroup(call.getConversationType()) ? null : CollUtil.getFirst(invitees);
  903 + pushCallChatMessage(call, ImMessageTypeEnum.RTC_CALL_START, payload, peerUserId);
904 } 904 }
905 905
906 /** 906 /**
@@ -917,23 +917,37 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { @@ -917,23 +917,37 @@ public class ImRtcCallServiceImpl implements ImRtcCallService {
917 Long durationSeconds) { 917 Long durationSeconds) {
918 AdminUserRespDTO operator = operatorId != null ? adminUserApi.getUser(operatorId) : null; 918 AdminUserRespDTO operator = operatorId != null ? adminUserApi.getUser(operatorId) : null;
919 ImRtcCallEndNotification payload = ImRtcCallEndNotification.of(call, reason, durationSeconds, operatorId, operator); 919 ImRtcCallEndNotification payload = ImRtcCallEndNotification.of(call, reason, durationSeconds, operatorId, operator);
920 - Long senderId = call.getInviterUserId(); 920 + Long peerUserId = null;
  921 + if (!ImConversationTypeEnum.isGroup(call.getConversationType())) {
  922 + ImRtcParticipantDO peer = CollUtil.findOne(
  923 + rtcParticipantMapper.selectListByRoom(call.getRoom()),
  924 + p -> ObjUtil.notEqual(p.getUserId(), call.getInviterUserId()));
  925 + peerUserId = peer != null ? peer.getUserId() : null;
  926 + }
  927 + pushCallChatMessage(call, ImMessageTypeEnum.RTC_CALL_END, payload, peerUserId);
  928 + }
921 929
922 - // 群聊:发起人总是 sender;receiver = 全群;走 imGroupMessageService.send 全群广播 930 + /**
  931 + * RTC 通话事件入消息流:群聊走 groupMessageService 全群广播,私聊走 privateMessageService 定向给 peer
  932 + * <p>
  933 + * senderId 固定取 inviterUserId,让前端按「谁发起」决定气泡左右;私聊 peer 缺失时降级为发给自己作兜底
  934 + *
  935 + * @param call 通话主表
  936 + * @param type 消息类型;RTC_CALL_START / RTC_CALL_END
  937 + * @param payload 推送 payload
  938 + * @param peerUserId 私聊对端用户编号;群聊忽略,私聊缺失时回退为 senderId
  939 + */
  940 + private void pushCallChatMessage(ImRtcCallDO call, ImMessageTypeEnum type, Object payload, Long peerUserId) {
  941 + Long senderId = call.getInviterUserId();
923 if (ImConversationTypeEnum.isGroup(call.getConversationType())) { 942 if (ImConversationTypeEnum.isGroup(call.getConversationType())) {
924 ImGroupMessageSendDTO dto = new ImGroupMessageSendDTO().setGroupId(call.getGroupId()) 943 ImGroupMessageSendDTO dto = new ImGroupMessageSendDTO().setGroupId(call.getGroupId())
925 - .setType(ImMessageTypeEnum.RTC_CALL_END.getType()).setContent(payload); 944 + .setType(type.getType()).setContent(payload);
926 groupMessageService.sendGroupMessage(senderId, dto); 945 groupMessageService.sendGroupMessage(senderId, dto);
927 return; 946 return;
928 } 947 }
929 -  
930 - // 私聊:发起人总是 sender;receiver = 通话另一方  
931 - ImRtcParticipantDO peer = CollUtil.findOne(  
932 - rtcParticipantMapper.selectListByRoom(call.getRoom()),  
933 - p -> ObjUtil.notEqual(p.getUserId(), senderId));  
934 - Long receiverId = peer != null ? peer.getUserId() : senderId; 948 + Long receiverId = peerUserId != null ? peerUserId : senderId;
935 ImPrivateMessageSendDTO dto = new ImPrivateMessageSendDTO().setReceiverId(receiverId) 949 ImPrivateMessageSendDTO dto = new ImPrivateMessageSendDTO().setReceiverId(receiverId)
936 - .setType(ImMessageTypeEnum.RTC_CALL_END.getType()).setContent(payload); 950 + .setType(type.getType()).setContent(payload);
937 privateMessageService.sendPrivateMessage(senderId, dto); 951 privateMessageService.sendPrivateMessage(senderId, dto);
938 } 952 }
939 953
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/rtc/ImRtcCallEndNotification.java
@@ -11,10 +11,10 @@ import lombok.Data; @@ -11,10 +11,10 @@ import lombok.Data;
11 * 入 im_private_message / im_group_message;接收方关闭通话窗 + 渲染聊天 tip 11 * 入 im_private_message / im_group_message;接收方关闭通话窗 + 渲染聊天 tip
12 * <p> 12 * <p>
13 * 文案分场景: 13 * 文案分场景:
14 - * 群聊:「{voice/video}通话已结束 [时长 X]」;发起人信息走配对的 {@link ImRtcCallStartNotification} 14 + * 群聊:「语音通话已经结束」;发起人信息走配对的 {@link ImRtcCallStartNotification}
15 * 私聊:仿微信准气泡,按 endReason × selfSend 视角转换文案(HANGUP / CANCEL / REJECT / BUSY / ERROR) 15 * 私聊:仿微信准气泡,按 endReason × selfSend 视角转换文案(HANGUP / CANCEL / REJECT / BUSY / ERROR)
16 * <p> 16 * <p>
17 - * 与 {@link ImRtcCallStartNotification} 两段式配对(仅群通话):START 在 invite 事务里 INSERT,END 在 cancel / leave 事务里 INSERT; 17 + * 与 {@link ImRtcCallStartNotification} 两段式配对:START 在 invite 事务里 INSERT,END 在 cancel / leave 事务里 INSERT;
18 * 两段位于不同请求 / 事务,自增 id 保证聊天流顺序 18 * 两段位于不同请求 / 事务,自增 id 保证聊天流顺序
19 * 19 *
20 * @author 芋道源码 20 * @author 芋道源码
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/rtc/ImRtcCallStartNotification.java
@@ -7,13 +7,13 @@ import lombok.Data; @@ -7,13 +7,13 @@ import lombok.Data;
7 /** 7 /**
8 * RTC_CALL_START 通话开始通知 8 * RTC_CALL_START 通话开始通知
9 * <p> 9 * <p>
10 - * 仅群通话场景;入 im_group_message + 全群广播;前端渲染聊天 tip「{inviterNickname} 发起了{voice/video}通话」 10 + * 群聊入 im_group_message 全群广播;前端渲染聊天 tip「{inviterNickname} 发起了语音通话」
  11 + * <p>
  12 + * 私聊入 im_private_message 定向给被叫;前端不渲染聊天 tip,仅用于会话列表预览展示「[语音通话]」(刷新后仍可见)
11 * <p> 13 * <p>
12 * 与 {@link ImRtcCallEndNotification} 两段式配对: 14 * 与 {@link ImRtcCallEndNotification} 两段式配对:
13 * START 在 invite 接口事务里 INSERT,END 在 cancel / leave 接口事务里 INSERT, 15 * START 在 invite 接口事务里 INSERT,END 在 cancel / leave 接口事务里 INSERT,
14 * 两段位于不同请求 / 事务,自增 id 保证聊天流顺序;后续如果合并到同一事务里 push,需要额外保证 START 先于 END 16 * 两段位于不同请求 / 事务,自增 id 保证聊天流顺序;后续如果合并到同一事务里 push,需要额外保证 START 先于 END
15 - * <p>  
16 - * 私聊场景没有 START(单 END 准气泡足够),本通知不会用于 conversationType=PRIVATE  
17 * 17 *
18 * @author 芋道源码 18 * @author 芋道源码
19 */ 19 */