From e1b83702670c3ede6f804c716aed7cfaff4ade50 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 25 May 2026 20:54:11 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20IM=20=E7=94=B3?= =?UTF-8?q?=E8=AF=B7=E4=B8=8E=20RTC=20=E8=BE=B9=E7=95=8C=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 复用好友申请、群申请和群邀请唯一键冲突后的旧记录,并补充测试 - 收敛 RTC 旁观者加入、忙线校验、追加邀请超员和群通话通知逻辑 - 为 RTC 参与者补充房间用户唯一约束与 MySQL 迁移 - 统一群本体管理请求的 id 字段,并同步前端调用 - 修复前端来电活跃态守卫和 LiveKit 重连前断开旧房间 - 清理群成员通知基类命名和相关注释 --- src/api/im/group/index.ts | 31 +++++++++++++++---- .../components/group/GroupAdminSetDialog.vue | 4 +-- .../group/GroupMuteMemberDialog.vue | 2 +- .../group/GroupOwnerTransferDialog.vue | 3 +- .../im/home/composables/useLiveKitRoom.ts | 26 ++++++++++++---- .../conversation/ConversationGroupSide.vue | 2 +- .../components/message/GroupPinnedMessage.vue | 2 +- .../components/message/MessageItem.vue | 4 +-- src/views/im/home/store/rtcStore.ts | 3 ++ 9 files changed, 56 insertions(+), 21 deletions(-) diff --git a/src/api/im/group/index.ts b/src/api/im/group/index.ts index dc07fac4d..6aefb7fbd 100644 --- a/src/api/im/group/index.ts +++ b/src/api/im/group/index.ts @@ -20,7 +20,7 @@ export interface ImGroupRespVO { // 群消息置顶 / 取消置顶 Request VO export interface ImGroupMessagePinReqVO { - groupId: number // 群编号 + id: number // 群编号 messageId: number // 消息编号 } @@ -42,16 +42,35 @@ export interface ImGroupUpdateReqVO { // 添加 / 撤销群管理员 Request VO export interface ImGroupAdminReqVO { - groupId: number // 群编号 + id: number // 群编号 userIds: number[] // 目标用户编号列表 } // 群主转让 Request VO export interface ImGroupTransferOwnerReqVO { - groupId: number // 群编号 + id: number // 群编号 newOwnerUserId: number // 新群主用户编号 } +// 全群禁言 / 取消 Request VO +export interface ImGroupMuteAllReqVO { + id: number // 群编号 + mutedAll: boolean // 是否全群禁言 +} + +// 成员禁言 Request VO +export interface ImGroupMuteMemberReqVO { + id: number // 群编号 + userId: number // 被禁言的用户编号 + mutedSeconds: number // 禁言时长(秒),0 表示永久禁言 +} + +// 取消成员禁言 Request VO +export interface ImGroupCancelMuteMemberReqVO { + id: number // 群编号 + userId: number // 被取消禁言的用户编号 +} + // 获得当前登录用户的群列表 export const getMyGroupList = () => { return request.get({ url: '/im/group/list' }) @@ -103,16 +122,16 @@ export const unpinGroupMessage = (data: ImGroupMessagePinReqVO) => { } // 全群禁言 / 取消(仅群主 / 管理员可调) -export const muteAll = (data: { groupId: number; mutedAll: boolean }) => { +export const muteAll = (data: ImGroupMuteAllReqVO) => { return request.put({ url: '/im/group/mute-all', data }) } // 禁言成员 -export const muteMember = (data: { groupId: number; userId: number; mutedSeconds: number }) => { +export const muteMember = (data: ImGroupMuteMemberReqVO) => { return request.put({ url: '/im/group/mute-member', data }) } // 取消成员禁言 -export const cancelMuteMember = (data: { groupId: number; userId: number }) => { +export const cancelMuteMember = (data: ImGroupCancelMuteMemberReqVO) => { return request.put({ url: '/im/group/cancel-mute-member', data }) } diff --git a/src/views/im/home/components/group/GroupAdminSetDialog.vue b/src/views/im/home/components/group/GroupAdminSetDialog.vue index 1aa3d15e3..93f5e527d 100644 --- a/src/views/im/home/components/group/GroupAdminSetDialog.vue +++ b/src/views/im/home/components/group/GroupAdminSetDialog.vue @@ -98,10 +98,10 @@ async function handleOk() { submitting.value = true try { if (addedIds.length > 0) { - await addGroupAdmin({ groupId: groupId.value, userIds: addedIds }) + await addGroupAdmin({ id: groupId.value, userIds: addedIds }) } if (removedIds.length > 0) { - await removeGroupAdmin({ groupId: groupId.value, userIds: removedIds }) + await removeGroupAdmin({ id: groupId.value, userIds: removedIds }) } message.success(`已更新群管理员(新增 ${addedIds.length} 位,撤销 ${removedIds.length} 位)`) emit('reload') diff --git a/src/views/im/home/components/group/GroupMuteMemberDialog.vue b/src/views/im/home/components/group/GroupMuteMemberDialog.vue index 41ecce6b7..35f0b1aac 100644 --- a/src/views/im/home/components/group/GroupMuteMemberDialog.vue +++ b/src/views/im/home/components/group/GroupMuteMemberDialog.vue @@ -81,7 +81,7 @@ async function handleConfirm() { loading.value = true try { await muteMember({ - groupId: groupId.value, + id: groupId.value, userId: userId.value, mutedSeconds: selected.value }) diff --git a/src/views/im/home/components/group/GroupOwnerTransferDialog.vue b/src/views/im/home/components/group/GroupOwnerTransferDialog.vue index 18884bb7f..80caed5a6 100644 --- a/src/views/im/home/components/group/GroupOwnerTransferDialog.vue +++ b/src/views/im/home/components/group/GroupOwnerTransferDialog.vue @@ -101,7 +101,7 @@ async function handleOk() { submitting.value = true try { await transferGroupOwner({ - groupId: groupId.value, + id: groupId.value, newOwnerUserId: newOwner.value.userId }) message.success('群主转让成功') @@ -121,4 +121,3 @@ async function handleOk() { @include picker.styles; } - diff --git a/src/views/im/home/composables/useLiveKitRoom.ts b/src/views/im/home/composables/useLiveKitRoom.ts index 769e2f73b..b061ae25d 100644 --- a/src/views/im/home/composables/useLiveKitRoom.ts +++ b/src/views/im/home/composables/useLiveKitRoom.ts @@ -50,6 +50,10 @@ export function useLiveKitRoom() { /** 连接 LiveKit Server;audio / video 控制初始默认开关 */ async function connect(url: string, token: string, opts: { audio?: boolean; video?: boolean }) { + // 新连接前先断开旧 Room;保留本次注册的事件回调 + if (_room.value) { + await disconnectRoom(false) + } const r = new Room({ // 按格子尺寸自动选 simulcast 层 adaptiveStream: true, @@ -267,18 +271,23 @@ export function useLiveKitRoom() { return stream } - /** 主动断开;通话结束统一调 */ - async function disconnect() { - disconnectedHandlers.clear() - participantConnectedHandlers.clear() - participantDisconnectedHandlers.clear() + /** 断开当前 Room;clearHandlers 为 true 时同步清理外部注册的事件回调 */ + async function disconnectRoom(clearHandlers: boolean) { + // 清理通话结束后不再复用的订阅回调 + if (clearHandlers) { + disconnectedHandlers.clear() + participantConnectedHandlers.clear() + participantDisconnectedHandlers.clear() + } + // 清理音视频轨道缓存 streamCache.clear() if (_room.value) { - // 断开前先卸事件,避免 disconnect 期间 ParticipantDisconnected / TrackUnsubscribed 仍触发 syncRemotes + // 卸载 Room 事件并断开连接 _room.value.removeAllListeners() await _room.value.disconnect() _room.value = null } + // 重置连接和设备状态 localParticipant.value = null remoteParticipants.value = [] isConnected.value = false @@ -289,6 +298,11 @@ export function useLiveKitRoom() { screenShareEnabled.value = false } + /** 主动断开;通话结束统一调 */ + async function disconnect() { + await disconnectRoom(true) + } + return { room, localParticipant, diff --git a/src/views/im/home/pages/conversation/components/conversation/ConversationGroupSide.vue b/src/views/im/home/pages/conversation/components/conversation/ConversationGroupSide.vue index 6484bd696..8bcc4928b 100644 --- a/src/views/im/home/pages/conversation/components/conversation/ConversationGroupSide.vue +++ b/src/views/im/home/pages/conversation/components/conversation/ConversationGroupSide.vue @@ -649,7 +649,7 @@ async function onMuteAllChange(value: boolean | string | number) { return } const newValue = !!value - await muteAll({ groupId: props.group.id, mutedAll: newValue }) + await muteAll({ id: props.group.id, mutedAll: newValue }) message.success(newValue ? '已开启全群禁言' : '已关闭全群禁言') emit('reload') } diff --git a/src/views/im/home/pages/conversation/components/message/GroupPinnedMessage.vue b/src/views/im/home/pages/conversation/components/message/GroupPinnedMessage.vue index 48e9b1b98..7850c790b 100644 --- a/src/views/im/home/pages/conversation/components/message/GroupPinnedMessage.vue +++ b/src/views/im/home/pages/conversation/components/message/GroupPinnedMessage.vue @@ -147,7 +147,7 @@ async function handleRemove(msg: Message) { } removingId.value = msg.id try { - await apiUnpinGroupMessage({ groupId: group.value.id, messageId: msg.id }) + await apiUnpinGroupMessage({ id: group.value.id, messageId: msg.id }) message.success('已取消置顶') } finally { removingId.value = null diff --git a/src/views/im/home/pages/conversation/components/message/MessageItem.vue b/src/views/im/home/pages/conversation/components/message/MessageItem.vue index 260300b48..1db2274f8 100644 --- a/src/views/im/home/pages/conversation/components/message/MessageItem.vue +++ b/src/views/im/home/pages/conversation/components/message/MessageItem.vue @@ -875,7 +875,7 @@ async function handlePin() { cancelButtonText: '取消', type: 'warning' }) - await apiPinGroupMessage({ groupId: group.id, messageId: props.message.id }) + await apiPinGroupMessage({ id: group.id, messageId: props.message.id }) successMessage('已置顶') } catch {} } @@ -1007,7 +1007,7 @@ async function handleUnmute() { } try { await confirmDialog('确定解除该成员的禁言吗?', '解除禁言') - await cancelMuteMember({ groupId: group.id, userId: props.message.senderId }) + await cancelMuteMember({ id: group.id, userId: props.message.senderId }) successMessage('已解除禁言') emit('reload') } catch {} diff --git a/src/views/im/home/store/rtcStore.ts b/src/views/im/home/store/rtcStore.ts index 3e7c146df..4cb0f99c7 100644 --- a/src/views/im/home/store/rtcStore.ts +++ b/src/views/im/home/store/rtcStore.ts @@ -163,6 +163,9 @@ export const useRtcStore = defineStore('imRtc', () => { /** 被叫收到来电;切到 INCOMING;接收 RTC_CALL(INVITE) payload */ function showIncoming(payload: ImRtcCallNotification) { + if (isActive.value) { + return + } incomingPayload.value = payload stage.value = ImRtcCallStage.INCOMING // 按 inviter 兜底首次填充胶囊条