✨ feat(im): 将后端的 roomName 和 callId 融合,简化字段和逻辑(一致性更好、概念更简洁)
parent
38cb980ce4
commit
18e5c97bf3
|
|
@ -1,33 +1,8 @@
|
|||
import request from '@/config/axios'
|
||||
|
||||
/** 会话场景;对齐后端 ImConversationTypeEnum */
|
||||
export const ImCallScene = {
|
||||
PRIVATE: 1,
|
||||
GROUP: 2
|
||||
} as const
|
||||
|
||||
/** 媒体类型;对齐后端 ImCallMediaTypeEnum */
|
||||
export const ImCallMediaType = {
|
||||
VOICE: 1,
|
||||
VIDEO: 2
|
||||
} as const
|
||||
|
||||
/** 通话状态;对齐后端 ImCallStatusEnum */
|
||||
export const ImCallStatus = {
|
||||
INVITING: 10,
|
||||
ONGOING: 20,
|
||||
ENDED: 30
|
||||
} as const
|
||||
|
||||
/** 通话结束原因;对齐后端 ImCallEndReasonEnum */
|
||||
export const ImCallEndReason = {
|
||||
HANGUP: 1,
|
||||
REJECT: 2,
|
||||
CANCEL: 3,
|
||||
TIMEOUT: 4,
|
||||
BUSY: 5,
|
||||
ERROR: 9
|
||||
} as const
|
||||
import type {
|
||||
ImCallEndReasonValue,
|
||||
ImCallParticipantStatusValue
|
||||
} from '@/views/im/utils/constants'
|
||||
|
||||
/** 发起通话请求 VO */
|
||||
export interface ImRtcCallInviteReqVO {
|
||||
|
|
@ -41,14 +16,14 @@ export interface ImRtcCallInviteReqVO {
|
|||
|
||||
/** 通话中添加成员请求 VO */
|
||||
export interface ImRtcCallInviteMoreReqVO {
|
||||
roomName: string
|
||||
room: string
|
||||
inviteeIds: number[]
|
||||
}
|
||||
|
||||
/** 通话会话 VO;invite / accept / refreshToken / getActiveSessions 共用 */
|
||||
/** 通话会话 VO;invite / join / accept / refreshToken 共用 */
|
||||
export interface ImRtcCallRespVO {
|
||||
callId: string
|
||||
roomName: string
|
||||
/** 业务通话编号(同时作为 LiveKit 房间名) */
|
||||
room: string
|
||||
livekitUrl: string
|
||||
token?: string
|
||||
scene: number
|
||||
|
|
@ -58,54 +33,81 @@ export interface ImRtcCallRespVO {
|
|||
groupId?: number
|
||||
inviteeIds?: number[]
|
||||
joinedUserIds?: number[]
|
||||
newCreated?: boolean
|
||||
}
|
||||
|
||||
/** RTC_INVITE 信令载荷;payload 走 ImPrivateMessageDTO.content(JSON 字符串) */
|
||||
export interface ImRtcInviteNotification {
|
||||
callId: string
|
||||
roomName: string
|
||||
livekitUrl: string
|
||||
token: string
|
||||
scene: number
|
||||
/** RTC_CALL 通话信令载荷(通话信令统一入口);status 区分子类型(复用参与者状态枚举);走 ImPrivateMessageDTO.content 仅推参与方 */
|
||||
export interface ImRtcCallNotification {
|
||||
/** 信令对应的参与者状态变迁;取值参见 ImCallParticipantStatus */
|
||||
status: ImCallParticipantStatusValue
|
||||
/** 业务通话编号(同时作为 LiveKit 房间名) */
|
||||
room: string
|
||||
conversationType: number
|
||||
mediaType: number
|
||||
inviterId: number
|
||||
groupId?: number
|
||||
/** INVITE 专属 */
|
||||
livekitUrl?: string
|
||||
/** INVITE 专属 */
|
||||
token?: string
|
||||
/** INVITE 专属 */
|
||||
inviterUserId?: number
|
||||
/** INVITE 专属 */
|
||||
inviterNickname?: string
|
||||
/** INVITE 专属 */
|
||||
inviterAvatar?: string
|
||||
/** ACCEPT / REJECT / CANCEL / HUNGUP 专属 */
|
||||
operatorUserId?: number
|
||||
/** 操作者昵称;按需展示,普通文案不依赖 */
|
||||
operatorNickname?: string
|
||||
/** 操作者头像;按需展示,普通文案不依赖 */
|
||||
operatorAvatar?: string
|
||||
}
|
||||
|
||||
/** RTC_PARTICIPANT_CONNECTED 通话参与者加入载荷;LiveKit webhook participant_joined 转推;callStore +userId 进 joinedUserIds;群聊场景非邀请成员靠 mediaType/inviterUserId 字段首次填充胶囊条 */
|
||||
export interface ImRtcParticipantConnectedNotification {
|
||||
room: string
|
||||
userId: number
|
||||
conversationType: number
|
||||
groupId?: number
|
||||
mediaType?: number
|
||||
inviterUserId?: number
|
||||
}
|
||||
|
||||
/** RTC_PARTICIPANT_DISCONNECTED 通话参与者离开载荷;LiveKit webhook participant_left 转推;callStore -userId 出 joinedUserIds */
|
||||
export interface ImRtcParticipantDisconnectedNotification {
|
||||
room: string
|
||||
userId: number
|
||||
conversationType: number
|
||||
groupId?: number
|
||||
}
|
||||
|
||||
/** RTC_ACCEPT 信令载荷 */
|
||||
export interface ImRtcAcceptNotification {
|
||||
callId: string
|
||||
roomName: string
|
||||
acceptorId: number
|
||||
}
|
||||
|
||||
/** RTC_END 信令载荷 */
|
||||
export interface ImRtcEndNotification {
|
||||
callId: string
|
||||
roomName: string
|
||||
operatorId?: number
|
||||
reason: number
|
||||
durationSeconds?: number
|
||||
}
|
||||
|
||||
/** RTC_GROUP_STARTED / RTC_GROUP_UPDATED / RTC_GROUP_ENDED 共用载荷 */
|
||||
export interface ImRtcGroupNotification {
|
||||
callId: string
|
||||
roomName: string
|
||||
groupId: number
|
||||
/** RTC_CALL_START 通话开始载荷;仅群聊;入消息流;前端渲染聊天 tip「{inviterNickname} 发起了{voice/video}通话」;与 END 两段式配对 */
|
||||
export interface ImRtcCallStartNotification {
|
||||
room: string
|
||||
conversationType: number
|
||||
mediaType: number
|
||||
inviterId: number
|
||||
joinedUserIds?: number[]
|
||||
inviteeIds?: number[]
|
||||
inviterUserId: number
|
||||
inviterNickname?: string
|
||||
inviterAvatar?: string
|
||||
}
|
||||
|
||||
/** RTC_CALL_END 通话结束载荷;入消息流;私聊渲染准气泡,群聊渲染 tip「{voice/video}通话已结束 [时长 X]」 */
|
||||
export interface ImRtcCallEndNotification {
|
||||
room: string
|
||||
conversationType: number
|
||||
mediaType: number
|
||||
endReason: ImCallEndReasonValue
|
||||
durationSeconds?: number
|
||||
/** 操作者用户编号;HANGUP/CANCEL/REJECT 触发人;webhook 兜底为 null */
|
||||
operatorUserId?: number
|
||||
/** 操作者昵称;按需展示,普通文案不依赖 */
|
||||
operatorNickname?: string
|
||||
/** 操作者头像;按需展示,普通文案不依赖 */
|
||||
operatorAvatar?: string
|
||||
}
|
||||
|
||||
/** 群活跃通话查询响应;不含 token */
|
||||
export interface ImRtcGroupCallRespVO {
|
||||
callId: string
|
||||
roomName: string
|
||||
room: string
|
||||
groupId: number
|
||||
mediaType: number
|
||||
inviterId: number
|
||||
|
|
@ -113,50 +115,50 @@ export interface ImRtcGroupCallRespVO {
|
|||
inviteeIds?: number[]
|
||||
}
|
||||
|
||||
/** 发起通话;同好友对 / 群已有进行中通话则返回该会话并标记 newCreated=false */
|
||||
/** 发起新通话;同好友对 / 同群已有进行中通话直接抛错(群场景应改走 joinCall) */
|
||||
export const inviteCall = (data: ImRtcCallInviteReqVO) => {
|
||||
return request.post<ImRtcCallRespVO>({ url: '/im/rtc/invite', data })
|
||||
}
|
||||
|
||||
/** 加入已有群通话;用于胶囊条「加入」按钮 */
|
||||
export const joinCall = (room: string) => {
|
||||
return request.post<ImRtcCallRespVO>({ url: '/im/rtc/join', params: { room } })
|
||||
}
|
||||
|
||||
/** 通话中添加成员;仅群通话可用 */
|
||||
export const inviteMoreCall = (data: ImRtcCallInviteMoreReqVO) => {
|
||||
return request.post<boolean>({ url: '/im/rtc/invite-more', data })
|
||||
}
|
||||
|
||||
/** 接听通话 */
|
||||
export const acceptCall = (roomName: string) => {
|
||||
return request.post<ImRtcCallRespVO>({ url: '/im/rtc/accept', params: { roomName } })
|
||||
export const acceptCall = (room: string) => {
|
||||
return request.post<ImRtcCallRespVO>({ url: '/im/rtc/accept', params: { room } })
|
||||
}
|
||||
|
||||
/** 拒绝通话 */
|
||||
export const rejectCall = (roomName: string) => {
|
||||
return request.post<boolean>({ url: '/im/rtc/reject', params: { roomName } })
|
||||
export const rejectCall = (room: string) => {
|
||||
return request.post<boolean>({ url: '/im/rtc/reject', params: { room } })
|
||||
}
|
||||
|
||||
/** 取消邀请;主叫接通前调用 */
|
||||
export const cancelCall = (roomName: string) => {
|
||||
return request.post<boolean>({ url: '/im/rtc/cancel', params: { roomName } })
|
||||
export const cancelCall = (room: string) => {
|
||||
return request.post<boolean>({ url: '/im/rtc/cancel', params: { room } })
|
||||
}
|
||||
|
||||
/** 离开通话;接通后调用 */
|
||||
export const leaveCall = (roomName: string) => {
|
||||
return request.post<boolean>({ url: '/im/rtc/leave', params: { roomName } })
|
||||
export const leaveCall = (room: string) => {
|
||||
return request.post<boolean>({ url: '/im/rtc/leave', params: { room } })
|
||||
}
|
||||
|
||||
/** 重新签发 Token;客户端重连或 Token 过期续期 */
|
||||
export const refreshCallToken = (roomName: string) => {
|
||||
return request.get<ImRtcCallRespVO>({ url: '/im/rtc/refresh-token', params: { roomName } })
|
||||
export const refreshCallToken = (room: string) => {
|
||||
return request.get<ImRtcCallRespVO>({ url: '/im/rtc/refresh-token', params: { room } })
|
||||
}
|
||||
|
||||
/** 查询当前用户活跃通话;冷启动 / 推送点开恢复 */
|
||||
export const getActiveCallSessions = () => {
|
||||
return request.get<ImRtcCallRespVO[]>({ url: '/im/rtc/active-sessions' })
|
||||
}
|
||||
|
||||
/** 查询群当前进行中的通话;用于群聊顶部胶囊条;返回 null 表示无活跃通话 */
|
||||
export const getGroupActiveCall = (groupId: number) => {
|
||||
/** 查询当前进行中的通话;目前仅群聊场景(胶囊条),后端 API 已留扩展点;返回 null 表示无活跃通话 */
|
||||
export const getActiveCall = (groupId: number) => {
|
||||
return request.get<ImRtcGroupCallRespVO | null>({
|
||||
url: '/im/rtc/group-active-call',
|
||||
url: '/im/rtc/get-active-call',
|
||||
params: { groupId }
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ export const useMessagePuller = () => {
|
|||
* 首次 pull 是否已完成。仅在置 true 后,isConnected watch 才会触发 pull。
|
||||
* 防止 socket onopen 比 friendStore/groupStore 预拉先到达时,watcher 抢跑造成消息插入早于会话元数据可见
|
||||
*/
|
||||
let bootstrapped = false
|
||||
let initialPulled = false
|
||||
|
||||
/** 执行一次全量增量拉取(重入安全:进行中再次调用复用同一个 promise) */
|
||||
const pullOnce = (): Promise<void> => {
|
||||
|
|
@ -246,7 +246,7 @@ export const useMessagePuller = () => {
|
|||
} finally {
|
||||
// 整个 IIFE 全部完成(含已读位置补齐)后才允许下一次 pullOnce 重入
|
||||
pullPromise = null
|
||||
bootstrapped = true
|
||||
initialPulled = true
|
||||
}
|
||||
})()
|
||||
return pullPromise
|
||||
|
|
@ -254,12 +254,12 @@ export const useMessagePuller = () => {
|
|||
|
||||
/**
|
||||
* 断网期间 WS 收不到推送,期间产生的消息只能靠拉取接口按 minId 游标补齐;
|
||||
* 首次连接由 Index.vue 显式调 pullOnce 完成 bootstrap,这里仅覆盖之后的重连
|
||||
* 首次连接由 Index.vue 显式调 pullOnce 完成首拉,这里仅覆盖之后的重连
|
||||
*/
|
||||
watch(
|
||||
() => wsStore.isConnected,
|
||||
(isConnected) => {
|
||||
if (isConnected && bootstrapped) {
|
||||
if (isConnected && initialPulled) {
|
||||
void pullOnce()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -585,7 +585,7 @@ export const useGroupStore = defineStore('imGroupStore', {
|
|||
}
|
||||
},
|
||||
|
||||
/** 创建群广播:创建者多端同步 + 初始成员 bootstrap;payload.memberUserIds 含自己 → 拉群详情 / 成员;本端发起者已经 upsert 过本群,跳过避免双拉 */
|
||||
/** 创建群广播:创建者多端同步 + 初始成员首次拉取;payload.memberUserIds 含自己 → 拉群详情 / 成员;本端发起者已经 upsert 过本群,跳过避免双拉 */
|
||||
async applyGroupCreateNotification(groupId: number, payload: GroupNotificationPayload) {
|
||||
if (!isSelfInPayloadMembers(payload)) {
|
||||
return
|
||||
|
|
@ -623,7 +623,7 @@ export const useGroupStore = defineStore('imGroupStore', {
|
|||
}
|
||||
},
|
||||
|
||||
/** 成员加入:被邀请者本端 group 未就位先 fetchGroupInfo bootstrap;所有人都刷成员列表(新成员 nickname / avatar 不在 payload) */
|
||||
/** 成员加入:被邀请者本端 group 未就位先 fetchGroupInfo 初次拉取;所有人都刷成员列表(新成员 nickname / avatar 不在 payload) */
|
||||
async applyGroupMemberInviteNotification(groupId: number, payload: GroupNotificationPayload) {
|
||||
// 自己刚被拉进来:必须 await fetchGroupInfo 让群入 state.groups,否则 fetchGroupMembers 的 guard 会兜空
|
||||
if (isSelfInPayloadMembers(payload) && !this.getGroup(groupId)) {
|
||||
|
|
@ -632,7 +632,7 @@ export const useGroupStore = defineStore('imGroupStore', {
|
|||
this.fetchGroupMembers(groupId, true).catch(() => undefined)
|
||||
},
|
||||
|
||||
/** 自由进群:进群者本端 group 未就位先 fetchGroupInfo bootstrap;所有人都刷成员列表 */
|
||||
/** 自由进群:进群者本端 group 未就位先 fetchGroupInfo 初次拉取;所有人都刷成员列表 */
|
||||
async applyGroupMemberEnterNotification(groupId: number, payload: GroupNotificationPayload) {
|
||||
const selfUserId = getCurrentUserId()
|
||||
// 自己自由进群:必须 await fetchGroupInfo 让群入 state.groups,否则 fetchGroupMembers 的 guard 会兜空
|
||||
|
|
|
|||
|
|
@ -13,15 +13,13 @@ export const ImMessageType = {
|
|||
RECALL: 2101, // 撤回(对应 OpenIM RevokeNotification=2101)
|
||||
RECEIPT: 2200, // 回执(对应 OpenIM HasReadReceipt=2200)
|
||||
READ: 2201, // 已读(多端同步,OpenIM 无对应;自有扩展)
|
||||
// TODO @AI:是不是要把单聊、群聊的信令融合?
|
||||
// ========== 实时通话信令(2300-2302) ==========
|
||||
RTC_INVITE: 2300, // 通话邀请(推给被叫弹来电)
|
||||
RTC_ACCEPT: 2301, // 通话接通(推给主叫切到通话中 UI)
|
||||
RTC_END: 2302, // 通话结束(拒绝/取消/挂断/超时统一)
|
||||
// ========== 群通话广播(2310-2312):让所有群成员能看胶囊条 / 主动加入 ==========
|
||||
RTC_GROUP_STARTED: 2310, // 群通话开始(全群广播)
|
||||
RTC_GROUP_ENDED: 2311, // 群通话结束(全群广播;胶囊条移除)
|
||||
RTC_GROUP_UPDATED: 2312, // 群通话成员变更(全群广播;胶囊条人数刷新)
|
||||
// ========== 实时通话信令(1601-1605 段位与 OpenIM 对齐;1610+ 自有扩展) ==========
|
||||
RTC_CALL: 1601, // 通话信令统一入口(对应 OpenIM SignalingNotification=1601)
|
||||
RTC_PARTICIPANT_CONNECTED: 1602, // 通话参与者加入(对应 OpenIM RoomParticipantsConnectedNotification=1602)
|
||||
RTC_PARTICIPANT_DISCONNECTED: 1603, // 通话参与者离开(对应 OpenIM RoomParticipantsDisconnectedNotification=1603)
|
||||
// 1604-1609 OpenIM 已用 / 留作扩展,本系统暂不使用
|
||||
RTC_CALL_START: 1610, // 通话开始(自有扩展,OpenIM 无;仅群聊;与 END 两段式配对)
|
||||
RTC_CALL_END: 1611, // 通话结束(自有扩展,OpenIM 无;私聊 / 群聊)
|
||||
// ========== 好友通知(1201-1210 直接复用 OpenIM 段位编号) ==========
|
||||
FRIEND_REQUEST_APPROVED: 1201, // 好友申请被同意
|
||||
FRIEND_REQUEST_REJECTED: 1202, // 好友申请被拒绝
|
||||
|
|
@ -89,6 +87,11 @@ export function isFriendChatTip(type: number): boolean {
|
|||
return type === ImMessageType.FRIEND_ADD || type === ImMessageType.FRIEND_DELETE
|
||||
}
|
||||
|
||||
/** 判断是否「会话内的通话事件气泡」:RTC_CALL_START / RTC_CALL_END 渲染成灰色提示 */
|
||||
export function isRtcCallTip(type: number): boolean {
|
||||
return type === ImMessageType.RTC_CALL_START || type === ImMessageType.RTC_CALL_END
|
||||
}
|
||||
|
||||
/**
|
||||
* IM 普通消息类型集合(normal vs event 二分;与后端 ImMessageTypeEnum.normal 字段语义一致)
|
||||
*
|
||||
|
|
@ -161,6 +164,44 @@ export function isGroupConversation(type: number | undefined): boolean {
|
|||
return type === ImConversationType.GROUP
|
||||
}
|
||||
|
||||
/** IM 通话媒体类型(对齐后端 ImRtcCallMediaTypeEnum) */
|
||||
export const ImCallMediaType = {
|
||||
VOICE: 1,
|
||||
VIDEO: 2
|
||||
} as const
|
||||
|
||||
/** IM 通话状态(对齐后端 ImRtcCallStatusEnum) */
|
||||
export const ImCallStatus = {
|
||||
CREATED: 10, // 创建:私聊等被叫接听;群聊发起人已进房等其他人加入
|
||||
RUNNING: 20, // 进行中:第一个非发起人接通后进入
|
||||
ENDED: 30 // 已结束
|
||||
} as const
|
||||
|
||||
/** IM 通话结束原因(对齐后端 ImRtcCallEndReasonEnum) */
|
||||
export const ImCallEndReason = {
|
||||
HANGUP: 1, // 接通后任一方主动挂断
|
||||
REJECT: 2, // 被叫接通前点拒接
|
||||
CANCEL: 3, // 主叫接通前主动取消
|
||||
BUSY: 5, // 私聊呼叫时对方正忙
|
||||
ERROR: 9 // 网络中断 / 设备失败
|
||||
} as const
|
||||
|
||||
/** ImCallEndReason 取值类型 */
|
||||
export type ImCallEndReasonValue = (typeof ImCallEndReason)[keyof typeof ImCallEndReason]
|
||||
|
||||
/** IM 通话参与者状态(对齐后端 ImRtcParticipantStatusEnum);同时作为 RTC_CALL 信令 status 字段取值 */
|
||||
export const ImCallParticipantStatus = {
|
||||
INVITING: 10, // 来电邀请
|
||||
JOINED: 20, // 接听 / 已加入
|
||||
REJECTED: 30, // 拒接
|
||||
NO_ANSWER: 40, // 主叫取消,被邀请方未应答
|
||||
LEFT: 50 // 挂断离开
|
||||
} as const
|
||||
|
||||
/** ImCallParticipantStatus 取值类型 */
|
||||
export type ImCallParticipantStatusValue =
|
||||
(typeof ImCallParticipantStatus)[keyof typeof ImCallParticipantStatus]
|
||||
|
||||
/** IM WebSocket 外层帧类型(对齐后端 ImPrivateMessageDTO.TYPE / ImGroupMessageDTO.TYPE) */
|
||||
export const ImWebSocketMessageType = {
|
||||
PRIVATE_MESSAGE: 'im-private-message', // 私聊通道
|
||||
|
|
|
|||
Loading…
Reference in New Issue