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 { // ========== 用户聊天消息(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) *

* 与 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「语音通话已经结束」 *

* 与 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 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 GROUP_REQUEST_NOTIFICATION_TYPES = CollUtil.newHashSet( GROUP_REQUEST_RECEIVED.type, GROUP_REQUEST_APPROVED.type, GROUP_REQUEST_REJECTED.type); private static final Set RTC_NOTIFICATION_TYPES = CollUtil.newHashSet( RTC_CALL.type, RTC_PARTICIPANT_CONNECTED.type, RTC_PARTICIPANT_DISCONNECTED.type); /** * 类型 */ private final Integer type; /** * 名字 */ private final String name; /** * 是否入库 *

* true:插入 im_xxx_message 消息表,离线 pull 能拉到; * false:仅 WebSocket 推送,离线丢弃;状态由专用存储维护(如 READ 走 Redis 游标) */ private final boolean persistent; /** * 是不是用户聊天消息(normal vs event 二分) *

* 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); } }