liyong
38 分钟以前 8f5be003e52fdbf034d1955216143bd184ebbdb4
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
package cn.iocoder.yudao.module.im.enums;
 
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
 
import java.util.Arrays;
import java.util.Set;
 
/**
 * IM 内容类型枚举
 *
 * @author 芋道源码
 */
@Getter
@RequiredArgsConstructor
public enum ImContentTypeEnum implements ArrayValuable<Integer> {
 
    // ========== 用户聊天消息(101-105 直接复用 OpenIM 段位编号) ==========
    /**
     * 对应 OpenIM:Text 101
     * 对应自己的类:TextMessage
     */
    TEXT(101, "文本", true, true),
    /**
     * 对应 OpenIM:Picture 102
     * 对应自己的类:ImageMessage
     */
    IMAGE(102, "图片", true, true),
    /**
     * 对应 OpenIM:Sound 103
     * 对应自己的类:AudioMessage
     */
    VOICE(103, "语音", true, true),
    /**
     * 对应 OpenIM:Video 104
     * 对应自己的类:VideoMessage
     */
    VIDEO(104, "视频", true, true),
    /**
     * 对应 OpenIM:File 105
     * 对应自己的类:FileMessage
     */
    FILE(105, "文件", true, true),
    /**
     * 对应 OpenIM:Merger 107
     * 对应自己的类:MergeMessage
     */
    MERGE(107, "合并转发", true, true),
    /**
     * 对应 OpenIM:Card 108(OpenIM 仅用户名片;本系统扩展为用户 / 群双类型,按 targetType 区分)
     * 对应自己的类:CardMessage
     * 场景:把用户名片 / 群名片推荐给其他会话;用户名片点击打开 UserInfoCard,群名片点击「已加群跳会话 / 未加群弹申请加群」
     */
    CARD(108, "名片", true, true),
    /**
     * 对应 OpenIM:Face 115
     * 对应自己的类:FaceMessage
     * 场景:表情贴图(运营配置的系统表情包 + 用户私有表情包);Unicode emoji 仍走 TEXT
     */
    FACE(115, "表情", true, true),
 
    // ========== 频道消息扩展段(125+;OpenIM 122 之后未占用,本系统在 125 起步给频道 / 公众号类消息扩展) ==========
    /**
     * 对应 OpenIM:无(125 段位 OpenIM 未占用,作为频道消息扩展起始位)
     * 对应自己的类:MaterialMessage
     * 场景:频道运营推送的素材消息;当前形态为图文卡片(title + coverUrl + summary + url)
     * 详情:url 非空跳 url;url 为空时客户端按 materialId 拉 /get-content 渲染富文本正文
     */
    MATERIAL(125, "素材", true, true),
 
    // ========== 信号类(2101 / 2200 直接复用 OpenIM 段位编号;2201 自有扩展) ==========
    /**
     * 对应 OpenIM:RevokeNotification 2101
     * 对应自己的类:RecallMessage
     */
    RECALL(2101, "撤回", true, false),
    /**
     * 对应 OpenIM:HasReadReceipt 2200
     * 对应自己的类:无(payload 走 ImXxxMessageDTO 顶层字段)
     */
    RECEIPT(2200, "回执", false, false),
    /**
     * 对应 OpenIM:无(自有扩展,OpenIM 走 ConversationChangeNotification 1300 路径)
     * 对应自己的类:无(payload 走 ImXxxMessageDTO 顶层字段)
     */
    READ(2201, "已读", false, false),
 
    // ========== 实时通话信令(1601-1605 段位与 OpenIM 对齐;1610+ 自有扩展) ==========
    /**
     * 对应 OpenIM:SignalingNotification 1601(通话信令统一入口)
     * 对应自己的类:ImRtcCallNotification
     * 场景:通话信令;不入库,走 imWebSocketService 仅推参与方;status 复用参与者状态枚举区分 INVITING / JOINED / REJECTED / NO_ANSWER / LEFT
     */
    RTC_CALL(1601, "通话信令", false, false),
    /**
     * 对应 OpenIM:RoomParticipantsConnectedNotification 1602
     * 对应自己的类:ImRtcParticipantConnectedNotification
     * 场景:通话参与者加入;LiveKit webhook participant_joined 触发;私聊推 peer 多端 + inviter 多端,群聊全群广播;不入库
     */
    RTC_PARTICIPANT_CONNECTED(1602, "通话参与者加入", false, false),
    /**
     * 对应 OpenIM:RoomParticipantsDisconnectedNotification 1603
     * 对应自己的类:ImRtcParticipantDisconnectedNotification
     * 场景:通话参与者离开;LiveKit webhook participant_left 触发;推送范围同 1602;不入库
     */
    RTC_PARTICIPANT_DISCONNECTED(1603, "通话参与者离开", false, false),
    // 1604-1609 OpenIM 已用 / 留作扩展,本系统暂不使用
    /**
     * 对应 OpenIM:无(自有扩展,OpenIM 通话事件不入消息流)
     * 对应自己的类:ImRtcCallStartNotification
     * 场景:通话开始;群聊入 im_group_message 全群广播,前端渲染聊天 tip「{inviterNickname} 发起了语音通话」;
     * 私聊入 im_private_message 定向给被叫,仅用于会话列表预览展示「[语音通话]」(不渲染聊天 tip)
     * <p>
     * 与 RTC_CALL_END(1611) 两段式配对:START 一定先于 END 入库(START 在 invite 接口事务里、END 在 cancel/leave 接口事务里,自然按请求顺序串行)
     */
    RTC_CALL_START(1610, "通话开始", true, false),
    /**
     * 对应 OpenIM:无(自有扩展,OpenIM 通话事件不入消息流)
     * 对应自己的类:ImRtcCallEndNotification
     * 场景:通话结束;入 im_private_message / im_group_message;私聊渲染准气泡,群聊渲染 tip「语音通话已经结束」
     * <p>
     * 与 RTC_CALL_START(1610) 两段式配对
     */
    RTC_CALL_END(1611, "通话结束", true, false),
 
    // ========== 好友通知(1201-1210 直接复用 OpenIM 段位编号) ==========
    /**
     * 对应 OpenIM:FriendApplicationApprovedNotification 1201
     * 对应自己的类:FriendRequestApprovedNotification
     * 场景:B 同意 A 的好友申请,推给 A 多端
     */
    FRIEND_REQUEST_APPROVED(1201, "好友申请被同意", false, false),
    /**
     * 对应 OpenIM:FriendApplicationRejectedNotification 1202
     * 对应自己的类:FriendRequestRejectedNotification
     * 场景:B 拒绝 A 的好友申请,推给 A 多端
     */
    FRIEND_REQUEST_REJECTED(1202, "好友申请被拒绝", false, false),
    /**
     * 对应 OpenIM:FriendApplicationNotification 1203
     * 对应自己的类:FriendRequestNotification
     * 场景:A 申请加 B,推给 B 多端,前端落到「新的朋友」列表
     */
    FRIEND_REQUEST_RECEIVED(1203, "收到新的好友申请", false, false),
    /**
     * 对应 OpenIM:FriendAddedNotification 1204(OpenIM friendAdded.isSendMsg=false 默认不入消息流;本系统改为入库当会话气泡)
     * 对应自己的类:FriendAddNotification
     * 场景:双方建立好友关系,单条入库(sender=fromUserId, receiver=toUserId);双向 WebSocket 自动覆盖双方多端
     * 注意:silentReAddFriend 单边语义场景,发送时显式 setPersistent(false) 覆盖默认值
     */
    FRIEND_ADD(1204, "新增好友", true, false),
    /**
     * 对应 OpenIM:FriendDeletedNotification 1205
     * 对应自己的类:FriendDeleteNotification
     * 场景:A 删除 B,推给 A、B 双方多端
     */
    FRIEND_DELETE(1205, "好友被删除", false, false),
    // 1206 对应 OpenIM FriendRemarkSetNotification;本系统并入 FRIEND_UPDATE(1210) 统一推送,单一字段变更不再独立通道
    /**
     * 对应 OpenIM:BlackAddedNotification 1207
     * 对应自己的类:FriendBlockNotification
     * 场景:A 拉黑 B,仅推 A 多端
     */
    FRIEND_BLOCK(1207, "加入黑名单", false, false),
    /**
     * 对应 OpenIM:BlackDeletedNotification 1208
     * 对应自己的类:FriendUnblockNotification
     * 场景:A 移出 B 的黑名单,仅推 A 多端
     */
    FRIEND_UNBLOCK(1208, "移出黑名单", false, false),
    /**
     * 对应 OpenIM:FriendInfoUpdatedNotification 1209
     * 对应自己的类:FriendInfoUpdatedNotification
     * 场景:B 改了昵称 / 头像后,推给 B 的所有好友
     * 触发:system 模块发 AdminUserProfileUpdateMessage,IM 消费者 AdminUserProfileUpdateConsumer 批量推此通知
     */
    FRIEND_INFO_UPDATED(1209, "好友资料变更", false, false),
    /**
     * 对应 OpenIM:FriendsInfoUpdateNotification 1210(窄化到 silent / pinned 单边属性)
     * 对应自己的类:FriendUpdateNotification
     * 场景:A 改了 silent / pinned 等单边属性,推 A 多端
     */
    FRIEND_UPDATE(1210, "好友信息批量更新", false, false),
 
    // ========== 群事件(1501-1520 直接复用 OpenIM 段位编号;1530+ 我们独有扩展) ==========
    // 1500 对应 OpenIM GroupNotificationBegin 起始位,仅作占位,不使用
    /**
     * 对应 OpenIM:sdkws.GroupCreatedTips(GroupCreatedNotification 1501)
     * 对应自己的类:GroupCreateNotification
     * 场景:用户创建群(同时邀请初始成员),全员广播(含创建者多端同步 + 初始成员)
     */
    GROUP_CREATE(1501, "群创建", true, false),
    /**
     * 对应 OpenIM:sdkws.GroupInfoSetTips(GroupInfoSetNotification 1502,NAME / NOTICE 之外字段的 generic 兜底)
     * 对应自己的类:GroupInfoUpdateNotification
     * 场景:群主修改群头像 / 简介等字段后全员广播
     */
    GROUP_INFO_UPDATE(1502, "群信息变更", true, false),
    /**
     * 对应 OpenIM:sdkws.JoinGroupApplicationTips(JoinGroupApplicationNotification 1503)
     * 对应自己的类:GroupRequestReceivedNotification
     * 场景:用户申请加群 / 普通成员邀请待审批,定向私聊推送给群主 + 全部管理员(多端同步);不入群消息流
     */
    GROUP_REQUEST_RECEIVED(1503, "收到新的入群申请", false, false),
    /**
     * 对应 OpenIM:sdkws.MemberQuitTips(MemberQuitNotification 1504)
     * 对应自己的类:GroupMemberQuitNotification
     * 场景:成员主动退群(send-before-remove),全员广播(含 quitter);quitter 自判 operatorUserId === self → removeGroup
     */
    GROUP_MEMBER_QUIT(1504, "成员退群", true, false),
    /**
     * 对应 OpenIM:sdkws.GroupApplicationAcceptedTips(GroupApplicationAcceptedNotification 1505)
     * 对应自己的类:GroupRequestApprovedNotification
     * 场景:群主 / 管理员同意申请,定向私聊推送给申请人 + 群主 + 全部管理员;申请人侧弹 toast,admin 侧 pendingRequestCount-1;不入群消息流
     */
    GROUP_REQUEST_APPROVED(1505, "入群申请被同意", false, false),
    /**
     * 对应 OpenIM:sdkws.GroupApplicationRejectedTips(GroupApplicationRejectedNotification 1506)
     * 对应自己的类:GroupRequestRejectedNotification
     * 场景:群主 / 管理员拒绝申请,定向私聊推送给申请人 + 群主 + 全部管理员;不入群消息流
     */
    GROUP_REQUEST_REJECTED(1506, "入群申请被拒绝", false, false),
    /**
     * 对应 OpenIM:sdkws.GroupOwnerTransferredTips(GroupOwnerTransferredNotification 1507)
     * 对应自己的类:GroupOwnerTransferNotification
     * 场景:群主转让,全员广播;前端 transferOwner 把 ownerUserId 切到新值 + 旧群主 role → NORMAL / 新群主 role → OWNER
     */
    GROUP_OWNER_TRANSFER(1507, "群主转让", true, false),
    /**
     * 对应 OpenIM:sdkws.MemberKickedTips(MemberKickedNotification 1508)
     * 对应自己的类:GroupMemberKickNotification
     * 场景:群主 / 管理员移出成员(send-before-remove),全员广播(含被踢者);被踢者自判 memberUserIds 含 self → removeGroup
     */
    GROUP_MEMBER_KICK(1508, "成员被移出", true, false),
    /**
     * 对应 OpenIM:sdkws.MemberInvitedTips(MemberInvitedNotification 1509)
     * 对应自己的类:GroupMemberInviteNotification
     * 场景:成员邀请新人入群,全员广播(含被邀请者);被邀请人前端按 memberUserIds 含自己自判,初次拉取 fetchGroupInfo + fetchGroupMembers
     */
    GROUP_MEMBER_INVITE(1509, "成员加入", true, false),
    /**
     * 对应 OpenIM:sdkws.MemberEnterTips(MemberEnterNotification 1510)
     * 对应自己的类:GroupMemberEnterNotification
     * 场景:用户经搜索 / 二维码 / 分享链接自由进群(FREE 模式或审批通过后),全员广播;前端按 entrantUserId 局部添加成员
     */
    GROUP_MEMBER_ENTER(1510, "自由进群", true, false),
    /**
     * 对应 OpenIM:sdkws.GroupDismissedTips(GroupDismissedNotification 1511)
     * 对应自己的类:GroupDissolveNotification
     * 场景:群主解散群(send-before-remove),全员广播(含群主多端同步);前端 removeGroup 清群;离场用户离线 pull 通过 quit 路径(send_time < quit_time)也能拉到
     */
    GROUP_DISSOLVE(1511, "群解散", true, false),
    /**
     * 对应 OpenIM:sdkws.GroupMemberMutedTips(GroupMemberMutedNotification 1512)
     * 对应自己的类:GroupMemberMutedNotification
     * 场景:群主 / 管理员禁言某成员,全员广播
     */
    GROUP_MEMBER_MUTED(1512, "成员禁言", true, false),
    /**
     * 对应 OpenIM:sdkws.GroupMemberCancelMutedTips(GroupMemberCancelMutedNotification 1513)
     * 对应自己的类:GroupMemberCancelMutedNotification
     * 场景:群主 / 管理员取消某成员禁言,全员广播
     */
    GROUP_MEMBER_CANCEL_MUTED(1513, "成员取消禁言", true, false),
    /**
     * 对应 OpenIM:sdkws.GroupMutedTips(GroupMutedNotification 1514)
     * 对应自己的类:GroupMutedNotification
     * 场景:群主 / 管理员开启全群禁言,全员广播
     */
    GROUP_MUTED(1514, "全群禁言", true, false),
    /**
     * 对应 OpenIM:sdkws.GroupCancelMutedTips(GroupCancelMutedNotification 1515)
     * 对应自己的类:GroupCancelMutedNotification
     * 场景:群主 / 管理员取消全群禁言,全员广播
     */
    GROUP_CANCEL_MUTED(1515, "全群取消禁言", true, false),
    /**
     * 对应 OpenIM:sdkws.GroupMemberInfoSetTips(GroupMemberInfoSetNotification 1516,窄化到 displayUserName)
     * 对应自己的类:GroupMemberNicknameUpdateNotification
     * 场景:成员修改自己在群里的昵称,在线成员同步对应 member
     */
    GROUP_MEMBER_NICKNAME_UPDATE(1516, "成员昵称变更", false, false),
    /**
     * 对应 OpenIM:GroupMemberSetToAdminNotification 1517
     * 对应自己的类:GroupAdminAddNotification
     * 场景:群主设置管理员,全员广播;前端 updateMembersRole 把对应成员 role 提升为 ADMIN
     */
    GROUP_ADMIN_ADD(1517, "添加管理员", true, false),
    /**
     * 对应 OpenIM:GroupMemberSetToOrdinaryUserNotification 1518
     * 对应自己的类:GroupAdminRemoveNotification
     * 场景:群主撤销管理员,全员广播;前端 updateMembersRole 把对应成员 role 降级为 NORMAL
     */
    GROUP_ADMIN_REMOVE(1518, "撤销管理员", true, false),
    /**
     * 对应 OpenIM:sdkws.GroupInfoSetAnnouncementTips(GroupInfoSetAnnouncementNotification 1519)
     * 对应自己的类:GroupNoticeUpdateNotification
     * 场景:群主修改群公告后全员广播
     */
    GROUP_NOTICE_UPDATE(1519, "群公告变更", true, false),
    /**
     * 对应 OpenIM:sdkws.GroupInfoSetNameTips(GroupInfoSetNameNotification 1520)
     * 对应自己的类:GroupNameUpdateNotification
     * 场景:群主修改群名后全员广播
     */
    GROUP_NAME_UPDATE(1520, "群名变更", true, false),
 
    // 1530+ 我们独有扩展段(OpenIM 1500-1520 段位无对应物)
    /**
     * 对应 OpenIM:无直接对应(OpenIM 走 ConversationChangeNotification 1300 单聊路径)
     * 对应自己的类:GroupMemberSettingUpdateNotification
     * 场景:用户改自己的群免打扰 / 群备注,仅推该用户其他在线终端做多端同步
     */
    GROUP_MEMBER_SETTING_UPDATE(1530, "群成员个人设置变更", false, false),
    /**
     * 对应 OpenIM:无(OpenIM 无群消息置顶功能,自有扩展)
     * 对应自己的类:GroupMessagePinNotification
     * 场景:群主 / 管理员置顶一条群消息,全员广播;payload 直接带消息对象,前端把 message push 进 group.pinnedMessages
     */
    GROUP_MESSAGE_PIN(1531, "群消息置顶", true, false),
    /**
     * 对应 OpenIM:无(OpenIM 无群消息置顶功能,自有扩展)
     * 对应自己的类:GroupMessageUnpinNotification
     * 场景:群主 / 管理员取消置顶,全员广播;前端按 messageId 从 group.pinnedMessages 移除
     */
    GROUP_MESSAGE_UNPIN(1532, "群消息取消置顶", true, false),
    /**
     * 对应 OpenIM:无(OpenIM 无群封禁概念,自有扩展)
     * 对应自己的类:GroupBannedNotification
     * 场景:管理后台封禁 / 解封群,全员广播;前端按 banned 字段切换输入栏封禁覆盖层
     */
    GROUP_BANNED(1533, "群封禁变更", true, false);
 
    public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImContentTypeEnum::getType).toArray(Integer[]::new);
 
    private static final Set<Integer> FRIEND_NOTIFICATION_TYPES = CollUtil.newHashSet(
            FRIEND_REQUEST_APPROVED.type,
            FRIEND_REQUEST_REJECTED.type,
            FRIEND_REQUEST_RECEIVED.type,
            FRIEND_ADD.type,
            FRIEND_DELETE.type,
            FRIEND_BLOCK.type,
            FRIEND_UNBLOCK.type,
            FRIEND_INFO_UPDATED.type,
            FRIEND_UPDATE.type);
 
    private static final Set<Integer> GROUP_REQUEST_NOTIFICATION_TYPES = CollUtil.newHashSet(
            GROUP_REQUEST_RECEIVED.type,
            GROUP_REQUEST_APPROVED.type,
            GROUP_REQUEST_REJECTED.type);
 
    private static final Set<Integer> RTC_NOTIFICATION_TYPES = CollUtil.newHashSet(
            RTC_CALL.type,
            RTC_PARTICIPANT_CONNECTED.type,
            RTC_PARTICIPANT_DISCONNECTED.type);
 
    /**
     * 类型
     */
    private final Integer type;
    /**
     * 名字
     */
    private final String name;
    /**
     * 是否入库
     * <p>
     * true:插入 im_xxx_message 消息表,离线 pull 能拉到;
     * false:仅 WebSocket 推送,离线丢弃;状态由专用存储维护(如 READ 走 Redis 游标)
     */
    private final boolean persistent;
    /**
     * 是不是用户聊天消息(normal vs event 二分)
     * <p>
     * true:用户主动发的聊天消息,计入会话未读数(接收方非激活会话时 unreadCount + 1);用户发送入口仅允许这类;
     * false:系统事件 / 信号 / 提示,不参与未读计数
     */
    private final boolean normal;
 
    @Override
    public Integer[] array() {
        return ARRAYS;
    }
 
    /**
     * 校验 type 已注册,并返回对应枚举;未注册立刻抛异常,避免新增 type 时漏配 persistent / normal 属性
     *
     * @param type 消息类型
     * @return 枚举实例
     */
    public static ImContentTypeEnum validate(Integer type) {
        ImContentTypeEnum result = ArrayUtil.firstMatch(item -> item.type.equals(type), values());
        Assert.notNull(result, "未注册的消息类型 type={}", type);
        return result;
    }
 
    /**
     * 判断是否为好友通知
     */
    public static boolean isFriendNotification(Integer type) {
        return type != null && FRIEND_NOTIFICATION_TYPES.contains(type);
    }
 
    /**
     * 判断是否为群申请定向通知
     */
    public static boolean isGroupRequestNotification(Integer type) {
        return type != null && GROUP_REQUEST_NOTIFICATION_TYPES.contains(type);
    }
 
    /**
     * 判断是否为通话信令通知
     */
    public static boolean isRtcNotification(Integer type) {
        return type != null && RTC_NOTIFICATION_TYPES.contains(type);
    }
 
}