// ==================== WebSocket 帧 / 事件 ====================
|
|
// 后端 WebSocket 统一帧结构:{ type, content }
|
export interface WebSocketFrame {
|
type: string // 帧类型,对齐 ImWebSocketMessageType
|
content: string // 帧内容(JSON 字符串)
|
}
|
|
// IM WebSocket 通知 DTO(对齐后端 ImNotificationWebSocketDTO)
|
export interface ImNotificationWebSocketDTO {
|
conversationType: number // 会话类型
|
contentType: number // 内容类型
|
payload: Record<string, any> // 负载数据
|
}
|
|
// 无会话在线通知(对齐后端 conversationType = NONE 的独立 payload)
|
export interface ImNoConversationNotification {
|
type: number // 内容类型
|
[key: string]: any
|
}
|
|
// 私聊消息 DTO(对齐后端 ImPrivateMessageNotification)
|
export interface ImPrivateMessageNotification {
|
id: number // 消息编号
|
clientMessageId: string // 客户端消息编号
|
senderId: number // 发送人编号
|
receiverId: number // 接收人编号
|
type: number // 内容类型
|
content: string // 消息内容
|
status: number // 消息状态
|
receiptStatus?: number // 回执状态(不需要 / 待完成 / 已完成)
|
sendTime: string // 发送时间
|
}
|
|
// 群聊消息 DTO(对齐后端 ImGroupMessageNotification)
|
export interface ImGroupMessageNotification {
|
id: number // 消息编号
|
clientMessageId: string // 客户端消息编号
|
senderId: number // 发送人编号
|
groupId: number // 群编号
|
type: number // 内容类型
|
content: string // 消息内容
|
status: number // 消息状态
|
sendTime: string // 发送时间
|
atUserIds?: number[] // 群 @ 目标用户列表
|
receiverUserIds?: number[] // 群定向接收用户列表
|
readCount?: number // 群回执已读人数(type = RECEIPT 时使用)
|
receiptStatus?: number // 群回执状态(type = RECEIPT 时使用)
|
readId?: number // 已读位置
|
}
|
|
// 消息已读同步通知(对齐后端 ImMessageReadNotification)
|
export interface ImMessageReadNotification {
|
id: number // 已读位置
|
type: number // 内容类型
|
senderId?: number // 发送人编号
|
receiverId?: number // 私聊接收人编号
|
groupId?: number // 群编号
|
channelId?: number // 频道编号
|
readId?: number // 已读位置
|
}
|
|
// 消息回执通知(对齐后端 ImMessageReceiptNotification)
|
export interface ImMessageReceiptNotification {
|
id: number // 消息编号
|
type: number // 内容类型
|
senderId?: number // 已读方用户编号
|
receiverId?: number // 私聊接收人编号
|
groupId?: number // 群编号
|
readCount?: number // 群回执已读人数
|
receiptStatus?: number // 群回执状态
|
}
|
|
// ==================== 本地会话 / 消息结构 ====================
|
|
/** 引用消息 */
|
export interface QuoteMessage {
|
messageId: number // 引用消息编号
|
senderId: number // 引用消息发送人编号
|
type: number // 引用内容类型
|
content: string // 引用消息内容
|
}
|
|
// 会话数据结构(前端自有结构,后端无对应实体)
|
export interface Conversation {
|
// ========== 核心标识 ==========
|
targetId: number // 会话目标编号:私聊=对方 userId;群聊=groupId
|
type: number // 会话类型,对齐 ImConversationType
|
|
// ========== 展示字段 ==========
|
name: string // 展示名称(私聊=好友昵称;群聊=群名)
|
avatar: string // 头像
|
unreadCount: number // 未读数
|
|
// ========== 最后一条消息事实索引 ==========
|
lastContent: string // 会话列表展示的最后一条消息摘要
|
lastSendTime: number // 最后一条消息时间,用于排序
|
lastSenderId?: number // 发送人编号
|
lastMessageType?: number // 内容类型,对齐 ImContentType
|
lastMessageId?: number // 最后一条服务端消息编号
|
lastClientMessageId?: string // 最后一条客户端消息编号
|
lastMessageStatus?: number // 最后一条消息状态
|
lastReceiptStatus?: number // 最后一条群回执状态
|
lastSelfSend?: boolean // 是否自己发的
|
lastSenderDisplayName?: string // 发送人显示名快照——仅作 utils/user.getSenderDisplayName 实时算不出真名时的 fallback
|
|
// ========== UI 状态 ==========
|
deleted?: boolean // 是否已删除(软删标记,持久化时过滤)
|
top?: boolean // 是否置顶(排序时优先)
|
silent?: boolean // 是否免打扰(不展示未读徽标 + 不响提示音)
|
atMe?: boolean // 群聊:是否有人 @我
|
atAll?: boolean // 群聊:是否有人 @全体成员
|
reportedReadMessageId?: number // 已上报到服务端的最大已读消息编号
|
draft?: {
|
html: string // 输入框 HTML
|
plain: string // 输入框纯文本
|
reply?: QuoteMessage // 引用消息
|
} // 输入框草稿
|
}
|
|
// 消息数据结构
|
export interface Message {
|
// ========== 后端字段(对齐 ImPrivateMessageNotification / ImGroupMessageNotification) ==========
|
id?: number // 服务端消息编号,发送中为空
|
clientMessageId: string // 客户端消息编号,本地生成用于合并去重
|
type: number // 内容类型,对齐 ImContentType
|
content: string // 消息内容,JSON 字符串
|
status: number // 消息状态,对齐 ImMessageStatus
|
sendTime: number // 发送时间(前端转毫秒时间戳;后端为 LocalDateTime 字符串)
|
senderId: number // 发送人编号
|
atUserIds?: number[] // 群 @ 目标用户列表
|
receiverUserIds?: number[] // 群定向接收用户列表
|
receiptStatus?: number // 回执状态,对齐 ImMessageReceiptStatus(私聊 / 群 / 频道通用)
|
readCount?: number // 群回执已读人数(仅群消息)
|
materialId?: number // 关联频道素材编号(仅频道消息 type=MATERIAL)
|
|
// ========== 前端扩展字段 ==========
|
// 发送人显示名一律渲染时实时算:utils/user.getSenderDisplayName / getSenderRealNickname
|
// 不在 Message 上存任何名字快照,避免备注 / 群昵称变更后历史消息显示陈旧
|
targetId: number // 会话目标编号(私聊=对端 userId / 群聊=groupId),与 Conversation.targetId 一致
|
selfSend: boolean // 是否自己发送(前端按 senderId 计算)
|
uploadProgress?: number // 媒体消息上传进度(0-100);status=SENDING 期间持续更新;ack 后置 undefined
|
// 媒体消息内存中保留的原始 File;下划线前缀表示不进 JSON / 不持久化(IDB 恢复后必为 undefined)
|
// 失败重试时按它重走上传;页面刷新后该字段丢失,恢复阶段直接 drop 整条消息
|
_localFile?: File
|
_ackMerging?: boolean // ack 合并中标记,不持久化
|
}
|
|
// ==================== IndexedDB 本地存储结构 ====================
|
|
/** 会话 IndexedDB 存储结构 */
|
export interface ConversationDO extends Conversation {
|
clientConversationId: string // `${type}:${targetId}`
|
}
|
|
export interface ConversationRead {
|
conversationType: number // 会话类型,对齐 ImConversationType
|
targetId: number // 会话目标编号
|
messageId: number // 当前用户已读到的最大消息编号
|
updateTime?: number // 更新时间
|
}
|
|
/** 会话读位置 IndexedDB 存储结构 */
|
export interface ConversationReadDO extends ConversationRead {
|
clientConversationId: string // `${conversationType}:${targetId}`
|
}
|
|
/** 消息 IndexedDB 存储结构 */
|
export interface MessageDO extends Omit<Message, '_ackMerging' | '_localFile' | 'uploadProgress'> {
|
messageKey: string // `${conversationType}:${id}` 或 `client:${clientMessageId}`
|
conversationType: number // 会话类型,对齐 ImConversationType
|
clientConversationId: string // ConversationDO.clientConversationId
|
}
|
|
/** 设置 IndexedDB 存储结构 */
|
export interface SettingDO<T = unknown> {
|
key: string
|
value: T
|
updateTime?: number
|
}
|
|
// ==================== 群 / 群成员 ====================
|
|
// 群实体(前端内部结构)
|
export interface Group {
|
// ========== 后端字段(对齐 ImGroupApi.GroupRespVO) ==========
|
id: number // 群编号
|
name: string // 群名称
|
avatar?: string // 群头像
|
notice?: string // 群公告
|
ownerUserId?: number // 群主用户编号
|
pinnedMessages?: Message[] // 群置顶消息列表
|
mutedAll?: boolean // 是否全群禁言
|
banned?: boolean // 是否被管理员封禁
|
joinApproval?: boolean // 进群是否需群主 / 管理员审批
|
joinStatus?: number // 当前登录用户在该群的成员状态(参见 CommonStatusEnum:0 在群 / 1 已退群);历史退群群仍返回,供展示历史消息的群名 / 头像
|
|
// ========== 前端扩展字段(user-per-group 维度) ==========
|
silent?: boolean // 是否免打扰。从当前用户的 GroupMember 回填
|
groupRemark?: string // 群备注。从当前用户的 GroupMember 回填(当前用户对该群的自定义名)
|
members?: GroupMember[] // 群成员缓存(按需懒加载)
|
infoLoaded?: boolean // 群详情是否已加载,本轮会话内存标记,不持久化
|
activeCallLoaded?: boolean // 群活跃通话是否已探测,本轮会话内存标记,不持久化
|
activeCallExpired?: boolean // 群活跃通话探测是否已过期
|
membersLoaded?: boolean // members 是否"完整加载"——只有整群 loadGroupMemberList / fetchGroupMemberList 命中时为 true;fetchGroupMember 单成员补齐不置位,避免 fetchGroupMemberList(force=false) 命中缓存时误判整群已加载
|
membersExpired?: boolean // 群成员缓存是否已过期;重连 / 重新进入 IM 后只标记不删除,下次进入群会话再刷新
|
memberCount?: number // 成员总数
|
}
|
|
/** 群 IndexedDB 存储结构 */
|
export type GroupDO = Omit<
|
Group,
|
'activeCallExpired' | 'activeCallLoaded' | 'infoLoaded' | 'members' | 'membersExpired' | 'membersLoaded'
|
>
|
|
// 群成员实体(前端内部结构)
|
export interface GroupMember {
|
// ========== 后端字段(对齐 ImGroupMemberApi.GroupMemberRespVO) ==========
|
id?: number // 群成员关系记录编号
|
groupId: number // 群编号
|
userId: number // 用户编号
|
avatar?: string // 头像
|
nickname: string // 用户昵称
|
displayUserName?: string // 该成员在群内自定义昵称(每个 member 一份;不与 nickname 合并,由消费方按需取舍)
|
status?: number // 在群 / 退群状态,对齐 CommonStatusEnum
|
role?: number // 成员角色,参见 ImGroupMemberRole 枚举:1=群主 2=管理员 3=普通成员
|
muteEndTime?: string // 禁言到期时间(ISO 字符串)
|
|
// ========== 前端扩展字段 ==========
|
isOwner?: boolean // 是否群主(前端从 Group.ownerUserId 计算)
|
}
|
|
// ==================== 好友 ====================
|
|
// 好友实体(前端内部结构)
|
export interface Friend {
|
// ========== 后端字段(对齐 ImFriendApi.FriendRespVO) ==========
|
id?: number // 好友关系记录编号(本地乐观新增时可能暂缺)
|
friendUserId: number // 好友用户编号(与 Conversation.targetId 对齐)
|
nickname: string // 好友昵称(对方真实昵称,永远不被备注覆盖;UI 显示走 displayName || nickname)
|
nicknamePinyin?: string // 昵称的拼音(后端用 Pinyin4j 算好回填,小写无空格)
|
avatar?: string // 好友头像
|
silent?: boolean // 是否免打扰(不展示未读徽标 + 不响提示音)
|
displayName?: string // 好友展示备注:仅自己可见的别名(单字段不歧义,不带 Friend 前缀)
|
displayNamePinyin?: string // 备注的拼音(后端用 Pinyin4j 算好回填,小写无空格)
|
status?: number // 好友状态,对齐 CommonStatusEnum(DISABLE = 已删除,软删保留记录)
|
addSource?: number // 添加来源;参见 ImFriendAddSourceEnum
|
pinned?: boolean // 是否置顶联系人
|
blocked?: boolean // 是否拉黑(仅自己可见,单边屏蔽对方私聊消息)
|
addTime?: number // 添加好友时间(毫秒时间戳;后端为 LocalDateTime 字符串,在 convertFriend 转换)
|
deleteTime?: number // 删除好友时间(毫秒时间戳;后端为 LocalDateTime 字符串,在 convertFriend 转换)
|
}
|
|
/**
|
* 好友申请记录(前端内部结构,对齐后端 ImFriendRequestApi.FriendRequestRespVO)
|
*/
|
export interface FriendRequest {
|
// ========== 后端字段(对齐 ImFriendRequestApi.FriendRequestRespVO) ==========
|
id: number // 申请编号
|
fromUserId: number // 发起方用户编号
|
toUserId: number // 接收方用户编号
|
handleResult: number // 处理结果:0=未处理;1=同意;2=拒绝
|
applyContent?: string // 申请理由(发起方填写)
|
handleContent?: string // 处理理由(接收方拒绝时可选填)
|
addSource?: number // 添加来源;参见 ImFriendAddSourceEnum
|
handleTime?: number // 处理时间(毫秒时间戳)
|
createTime: number // 申请创建时间(毫秒时间戳)
|
|
// ========== 聚合字段(自 AdminUser,仅展示用) ==========
|
fromNickname?: string // 发起方昵称
|
fromAvatar?: string // 发起方头像
|
toNickname?: string // 接收方昵称
|
toAvatar?: string // 接收方头像
|
}
|
|
// ==================== 用户名片 ====================
|
|
// 用户精简信息(对齐后端 UserSimpleRespVO,名片 / 头像 hover 等场景共用)
|
export interface User {
|
id: number
|
nickname?: string
|
avatar?: string
|
sex?: number
|
deptId?: number
|
deptName?: string
|
}
|
|
// ==================== 列表行展示用 Lite 类型 ====================
|
|
/**
|
* 好友列表行:从 Friend 派生的展示快照
|
* - id 用 friendUserId(与列表 click / 选中比对一致),不是 Friend.id(关系记录主键)
|
* - 软删(status === DISABLE)由上游 friendStore.getActiveFriendList / getActiveFriendLiteList 统一过滤掉
|
*/
|
export interface FriendLite {
|
id: number
|
nickname: string
|
nicknamePinyin?: string // 昵称拼音(用于字母分桶 / 拼音搜索)
|
avatar?: string
|
displayName?: string
|
displayNamePinyin?: string // 备注拼音(优先于 nicknamePinyin 参与分桶)
|
}
|
|
/**
|
* 群列表行:从 Group 派生的展示快照
|
* - showGroupName / showImage:调用方决定带不带备注(如个人备注群名);展示按 show* > 原值兜底
|
* - showImageThumb:高频列表用缩略图,避免拉原图阻塞滚动
|
*/
|
export interface GroupLite {
|
id: number
|
name?: string
|
showGroupName?: string
|
showImage?: string
|
showImageThumb?: string
|
memberCount?: number
|
ownerId?: number
|
joinApproval?: boolean // 进群是否需群主 / 管理员审批
|
}
|