2026-06-30 24681c81c09022f584a57006f2534b5f74723414
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package cn.iocoder.yudao.module.im.service.websocket.notification.rtc;
 
import cn.iocoder.yudao.module.im.dal.dataobject.rtc.ImRtcCallDO;
import cn.iocoder.yudao.module.im.enums.rtc.ImRtcCallEndReasonEnum;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import lombok.Data;
 
/**
 * RTC_CALL_END 通话结束通知
 * <p>
 * 入 im_private_message / im_group_message;接收方关闭通话窗 + 渲染聊天 tip
 * <p>
 * 文案分场景:
 * 群聊:「语音通话已经结束」;发起人信息走配对的 {@link ImRtcCallStartNotification}
 * 私聊:仿微信准气泡,按 endReason × selfSend 视角转换文案(HANGUP / CANCEL / REJECT / BUSY / ERROR)
 * <p>
 * 与 {@link ImRtcCallStartNotification} 两段式配对:START 在 invite 事务里 INSERT,END 在 cancel / leave 事务里 INSERT;
 * 两段位于不同请求 / 事务,自增 id 保证聊天流顺序
 *
 * @author 芋道源码
 */
@Data
public class ImRtcCallEndNotification {
 
    /**
     * 业务通话编号
     */
    private String room;
    /**
     * 会话类型
     */
    private Integer conversationType;
    /**
     * 媒体类型
     */
    private Integer mediaType;
    /**
     * 结束原因
     */
    private Integer endReason;
    /**
     * 通话时长(秒):接通过为 endTime - acceptTime;未接通为 null
     */
    private Long durationSeconds;
    /**
     * 操作者用户编号:HANGUP / CANCEL / REJECT 是触发结束的人;HANGUP webhook 兜底为 null
     * <p>
     * 用于前端「被某某挂断」类文案;普通文案不依赖此字段
     */
    private Long operatorUserId;
    /**
     * 操作者昵称:前端按需展示(被某某挂断 / 头像 tip);操作者为空时随之为空
     */
    private String operatorNickname;
    /**
     * 操作者头像:前端按需展示;操作者为空时随之为空
     */
    private String operatorAvatar;
 
    public static ImRtcCallEndNotification of(ImRtcCallDO call, ImRtcCallEndReasonEnum reason, Long durationSeconds,
                                              Long operatorId, AdminUserRespDTO operator) {
        ImRtcCallEndNotification notification = new ImRtcCallEndNotification();
        notification.room = call.getRoom();
        notification.conversationType = call.getConversationType();
        notification.mediaType = call.getMediaType();
        notification.endReason = reason.getReason();
        notification.durationSeconds = durationSeconds;
        notification.operatorUserId = operatorId;
        if (operator != null) {
            notification.operatorNickname = operator.getNickname();
            notification.operatorAvatar = operator.getAvatar();
        }
        return notification;
    }
 
}