fix: 修复 IM 申请与 RTC 边界问题
- 复用好友申请、群申请和群邀请唯一键冲突后的旧记录,并补充测试 - 收敛 RTC 旁观者加入、忙线校验、追加邀请超员和群通话通知逻辑 - 为 RTC 参与者补充房间用户唯一约束与 MySQL 迁移 - 统一群本体管理请求的 id 字段,并同步前端调用 - 修复前端来电活跃态守卫和 LiveKit 重连前断开旧房间 - 清理群成员通知基类命名和相关注释im
parent
a4dfb717aa
commit
e1b8370267
|
|
@ -20,7 +20,7 @@ export interface ImGroupRespVO {
|
||||||
|
|
||||||
// 群消息置顶 / 取消置顶 Request VO
|
// 群消息置顶 / 取消置顶 Request VO
|
||||||
export interface ImGroupMessagePinReqVO {
|
export interface ImGroupMessagePinReqVO {
|
||||||
groupId: number // 群编号
|
id: number // 群编号
|
||||||
messageId: number // 消息编号
|
messageId: number // 消息编号
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,16 +42,35 @@ export interface ImGroupUpdateReqVO {
|
||||||
|
|
||||||
// 添加 / 撤销群管理员 Request VO
|
// 添加 / 撤销群管理员 Request VO
|
||||||
export interface ImGroupAdminReqVO {
|
export interface ImGroupAdminReqVO {
|
||||||
groupId: number // 群编号
|
id: number // 群编号
|
||||||
userIds: number[] // 目标用户编号列表
|
userIds: number[] // 目标用户编号列表
|
||||||
}
|
}
|
||||||
|
|
||||||
// 群主转让 Request VO
|
// 群主转让 Request VO
|
||||||
export interface ImGroupTransferOwnerReqVO {
|
export interface ImGroupTransferOwnerReqVO {
|
||||||
groupId: number // 群编号
|
id: number // 群编号
|
||||||
newOwnerUserId: 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 = () => {
|
export const getMyGroupList = () => {
|
||||||
return request.get<ImGroupRespVO[]>({ url: '/im/group/list' })
|
return request.get<ImGroupRespVO[]>({ 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<boolean>({ url: '/im/group/mute-all', data })
|
return request.put<boolean>({ url: '/im/group/mute-all', data })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 禁言成员
|
// 禁言成员
|
||||||
export const muteMember = (data: { groupId: number; userId: number; mutedSeconds: number }) => {
|
export const muteMember = (data: ImGroupMuteMemberReqVO) => {
|
||||||
return request.put<boolean>({ url: '/im/group/mute-member', data })
|
return request.put<boolean>({ url: '/im/group/mute-member', data })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 取消成员禁言
|
// 取消成员禁言
|
||||||
export const cancelMuteMember = (data: { groupId: number; userId: number }) => {
|
export const cancelMuteMember = (data: ImGroupCancelMuteMemberReqVO) => {
|
||||||
return request.put<boolean>({ url: '/im/group/cancel-mute-member', data })
|
return request.put<boolean>({ url: '/im/group/cancel-mute-member', data })
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -98,10 +98,10 @@ async function handleOk() {
|
||||||
submitting.value = true
|
submitting.value = true
|
||||||
try {
|
try {
|
||||||
if (addedIds.length > 0) {
|
if (addedIds.length > 0) {
|
||||||
await addGroupAdmin({ groupId: groupId.value, userIds: addedIds })
|
await addGroupAdmin({ id: groupId.value, userIds: addedIds })
|
||||||
}
|
}
|
||||||
if (removedIds.length > 0) {
|
if (removedIds.length > 0) {
|
||||||
await removeGroupAdmin({ groupId: groupId.value, userIds: removedIds })
|
await removeGroupAdmin({ id: groupId.value, userIds: removedIds })
|
||||||
}
|
}
|
||||||
message.success(`已更新群管理员(新增 ${addedIds.length} 位,撤销 ${removedIds.length} 位)`)
|
message.success(`已更新群管理员(新增 ${addedIds.length} 位,撤销 ${removedIds.length} 位)`)
|
||||||
emit('reload')
|
emit('reload')
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ async function handleConfirm() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
await muteMember({
|
await muteMember({
|
||||||
groupId: groupId.value,
|
id: groupId.value,
|
||||||
userId: userId.value,
|
userId: userId.value,
|
||||||
mutedSeconds: selected.value
|
mutedSeconds: selected.value
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ async function handleOk() {
|
||||||
submitting.value = true
|
submitting.value = true
|
||||||
try {
|
try {
|
||||||
await transferGroupOwner({
|
await transferGroupOwner({
|
||||||
groupId: groupId.value,
|
id: groupId.value,
|
||||||
newOwnerUserId: newOwner.value.userId
|
newOwnerUserId: newOwner.value.userId
|
||||||
})
|
})
|
||||||
message.success('群主转让成功')
|
message.success('群主转让成功')
|
||||||
|
|
@ -121,4 +121,3 @@ async function handleOk() {
|
||||||
@include picker.styles;
|
@include picker.styles;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,10 @@ export function useLiveKitRoom() {
|
||||||
|
|
||||||
/** 连接 LiveKit Server;audio / video 控制初始默认开关 */
|
/** 连接 LiveKit Server;audio / video 控制初始默认开关 */
|
||||||
async function connect(url: string, token: string, opts: { audio?: boolean; video?: boolean }) {
|
async function connect(url: string, token: string, opts: { audio?: boolean; video?: boolean }) {
|
||||||
|
// 新连接前先断开旧 Room;保留本次注册的事件回调
|
||||||
|
if (_room.value) {
|
||||||
|
await disconnectRoom(false)
|
||||||
|
}
|
||||||
const r = new Room({
|
const r = new Room({
|
||||||
// 按格子尺寸自动选 simulcast 层
|
// 按格子尺寸自动选 simulcast 层
|
||||||
adaptiveStream: true,
|
adaptiveStream: true,
|
||||||
|
|
@ -267,18 +271,23 @@ export function useLiveKitRoom() {
|
||||||
return stream
|
return stream
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 主动断开;通话结束统一调 */
|
/** 断开当前 Room;clearHandlers 为 true 时同步清理外部注册的事件回调 */
|
||||||
async function disconnect() {
|
async function disconnectRoom(clearHandlers: boolean) {
|
||||||
disconnectedHandlers.clear()
|
// 清理通话结束后不再复用的订阅回调
|
||||||
participantConnectedHandlers.clear()
|
if (clearHandlers) {
|
||||||
participantDisconnectedHandlers.clear()
|
disconnectedHandlers.clear()
|
||||||
|
participantConnectedHandlers.clear()
|
||||||
|
participantDisconnectedHandlers.clear()
|
||||||
|
}
|
||||||
|
// 清理音视频轨道缓存
|
||||||
streamCache.clear()
|
streamCache.clear()
|
||||||
if (_room.value) {
|
if (_room.value) {
|
||||||
// 断开前先卸事件,避免 disconnect 期间 ParticipantDisconnected / TrackUnsubscribed 仍触发 syncRemotes
|
// 卸载 Room 事件并断开连接
|
||||||
_room.value.removeAllListeners()
|
_room.value.removeAllListeners()
|
||||||
await _room.value.disconnect()
|
await _room.value.disconnect()
|
||||||
_room.value = null
|
_room.value = null
|
||||||
}
|
}
|
||||||
|
// 重置连接和设备状态
|
||||||
localParticipant.value = null
|
localParticipant.value = null
|
||||||
remoteParticipants.value = []
|
remoteParticipants.value = []
|
||||||
isConnected.value = false
|
isConnected.value = false
|
||||||
|
|
@ -289,6 +298,11 @@ export function useLiveKitRoom() {
|
||||||
screenShareEnabled.value = false
|
screenShareEnabled.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 主动断开;通话结束统一调 */
|
||||||
|
async function disconnect() {
|
||||||
|
await disconnectRoom(true)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
room,
|
room,
|
||||||
localParticipant,
|
localParticipant,
|
||||||
|
|
|
||||||
|
|
@ -649,7 +649,7 @@ async function onMuteAllChange(value: boolean | string | number) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const newValue = !!value
|
const newValue = !!value
|
||||||
await muteAll({ groupId: props.group.id, mutedAll: newValue })
|
await muteAll({ id: props.group.id, mutedAll: newValue })
|
||||||
message.success(newValue ? '已开启全群禁言' : '已关闭全群禁言')
|
message.success(newValue ? '已开启全群禁言' : '已关闭全群禁言')
|
||||||
emit('reload')
|
emit('reload')
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -147,7 +147,7 @@ async function handleRemove(msg: Message) {
|
||||||
}
|
}
|
||||||
removingId.value = msg.id
|
removingId.value = msg.id
|
||||||
try {
|
try {
|
||||||
await apiUnpinGroupMessage({ groupId: group.value.id, messageId: msg.id })
|
await apiUnpinGroupMessage({ id: group.value.id, messageId: msg.id })
|
||||||
message.success('已取消置顶')
|
message.success('已取消置顶')
|
||||||
} finally {
|
} finally {
|
||||||
removingId.value = null
|
removingId.value = null
|
||||||
|
|
|
||||||
|
|
@ -875,7 +875,7 @@ async function handlePin() {
|
||||||
cancelButtonText: '取消',
|
cancelButtonText: '取消',
|
||||||
type: 'warning'
|
type: 'warning'
|
||||||
})
|
})
|
||||||
await apiPinGroupMessage({ groupId: group.id, messageId: props.message.id })
|
await apiPinGroupMessage({ id: group.id, messageId: props.message.id })
|
||||||
successMessage('已置顶')
|
successMessage('已置顶')
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
@ -1007,7 +1007,7 @@ async function handleUnmute() {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await confirmDialog('确定解除该成员的禁言吗?', '解除禁言')
|
await confirmDialog('确定解除该成员的禁言吗?', '解除禁言')
|
||||||
await cancelMuteMember({ groupId: group.id, userId: props.message.senderId })
|
await cancelMuteMember({ id: group.id, userId: props.message.senderId })
|
||||||
successMessage('已解除禁言')
|
successMessage('已解除禁言')
|
||||||
emit('reload')
|
emit('reload')
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
|
||||||
|
|
@ -163,6 +163,9 @@ export const useRtcStore = defineStore('imRtc', () => {
|
||||||
|
|
||||||
/** 被叫收到来电;切到 INCOMING;接收 RTC_CALL(INVITE) payload */
|
/** 被叫收到来电;切到 INCOMING;接收 RTC_CALL(INVITE) payload */
|
||||||
function showIncoming(payload: ImRtcCallNotification) {
|
function showIncoming(payload: ImRtcCallNotification) {
|
||||||
|
if (isActive.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
incomingPayload.value = payload
|
incomingPayload.value = payload
|
||||||
stage.value = ImRtcCallStage.INCOMING
|
stage.value = ImRtcCallStage.INCOMING
|
||||||
// 按 inviter 兜底首次填充胶囊条
|
// 按 inviter 兜底首次填充胶囊条
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue