From 5bd99c53c2aa1d92f894b1f8003903bd0b6aa014 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 5 May 2026 22:32:04 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(im)=EF=BC=9Acodex=20?= =?UTF-8?q?=E8=AF=84=E5=AE=A1=E4=BF=AE=E5=A4=8D=20FRIEND=5FADD=20/=20FRIEN?= =?UTF-8?q?D=5FDELETE=20=E6=8E=A5=E6=94=B6=E6=96=B9=20peer=20=E4=B8=8E=20c?= =?UTF-8?q?lear=20=E6=B0=94=E6=B3=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FRIEND_ADD 接收方 peer 改按帧 sender / receiver 反推:becomeFriends 单条入库后双方收到同一份 payload,payload.friendUserId 固定是 toUserId,本端真正的对端要看自己是 sender 还是 receiver;新增 websocketStore.computeFriendPeerId 算好后传给 friendStore.applyFriendAdd/DeleteNotification - FRIEND_DELETE clear=true 跳过气泡插入:clear 语义是清会话本身,气泡分支按 isFriendDeleteWithClear 校验,避免在已清会话里写虚拟消息 Co-Authored-By: Claude Opus 4.7 --- src/views/im/home/store/friendStore.ts | 19 +++++++---- src/views/im/home/store/websocketStore.ts | 41 +++++++++++++++++++---- 2 files changed, 47 insertions(+), 13 deletions(-) diff --git a/src/views/im/home/store/friendStore.ts b/src/views/im/home/store/friendStore.ts index b59391f09..75624d23d 100644 --- a/src/views/im/home/store/friendStore.ts +++ b/src/views/im/home/store/friendStore.ts @@ -437,14 +437,21 @@ export const useFriendStore = defineStore('imFriendStore', { ) }, - /** FRIEND_ADD(1204):新增好友;本端拉取好友详情并入库 */ - applyFriendAddNotification(payload: FriendNotificationPayload) { - void this.loadFriendInfo(payload.friendUserId) + /** + * FRIEND_ADD(1204):新增好友;本端拉取好友详情并入库 + * peerUserId 由 websocketStore 按帧 sender / receiver 算好传入:becomeFriends 单条入库后双方收到同一份 payload, + * 本端真正的「对端」是帧上的另一个用户,不是 payload.friendUserId(payload 里固定是 toUserId)。 + */ + applyFriendAddNotification(_payload: FriendNotificationPayload, peerUserId: number) { + void this.loadFriendInfo(peerUserId) }, - /** FRIEND_DELETE(1205):好友被删除;本端清理 + 按 payload.clear 决定是否级联清会话(多端跟主操作端一致) */ - applyFriendDeleteNotification(payload: FriendNotificationPayload) { - this.removeFriend(payload.friendUserId, payload.clear !== false) + /** + * FRIEND_DELETE(1205):好友被删除;本端清理 + 按 payload.clear 决定是否级联清会话(多端跟主操作端一致) + * peerUserId 由 websocketStore 按帧 sender / receiver 算好传入;与 FRIEND_ADD 保持一致的 peer 推断 + */ + applyFriendDeleteNotification(payload: FriendNotificationPayload, peerUserId: number) { + this.removeFriend(peerUserId, payload.clear !== false) }, /** FRIEND_BLOCK(1207):拉黑;多端同步 */ diff --git a/src/views/im/home/store/websocketStore.ts b/src/views/im/home/store/websocketStore.ts index 2258d3256..afb038a1d 100644 --- a/src/views/im/home/store/websocketStore.ts +++ b/src/views/im/home/store/websocketStore.ts @@ -26,6 +26,19 @@ import type { Group } from '../types' +/** FRIEND_DELETE 帧 payload 是否带 clear=true:clear 语义是清会话本身,跳过气泡渲染 */ +const isFriendDeleteWithClear = (frame: ImPrivateMessageDTO): boolean => { + if (frame.type !== ImMessageType.FRIEND_DELETE) { + return false + } + try { + const payload = JSON.parse(frame.content || '{}') as { clear?: boolean } + return payload.clear === true + } catch { + return false + } +} + /** * WebSocket 私聊 DTO -> 前端 Message * 不写发送人名字段:渲染层走 utils/user 实时算(备注 / 群昵称变更后历史消息自动刷新) @@ -225,9 +238,13 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', { default: if (isFriendNotification(websocketMessage.type)) { this.handleFriendNotification(websocketMessage) - // FRIEND_ADD / FRIEND_DELETE 同时作为会话事件气泡插入消息列表(becomeFriends 入库 - // 帧 + silent / delete 单边推送帧统一走入库去重路径,前端按 type 渲染灰色提示) - if (isFriendChatTip(websocketMessage.type)) { + // FRIEND_ADD / FRIEND_DELETE:同时作为会话事件气泡插入消息列表 + // (becomeFriends 入库帧 + silent / delete 单边推送帧统一走入库去重路径,前端按 type 渲染灰色提示); + // FRIEND_DELETE 的 clear=true 语义是清会话本身,跳过气泡避免在已清会话里写入虚拟消息 + if ( + isFriendChatTip(websocketMessage.type) && + !isFriendDeleteWithClear(websocketMessage) + ) { this.handlePrivateMessage(websocketMessage) } } else { @@ -481,11 +498,21 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', { // ==================== 好友通知(1201-1210 段位,承载于私聊通道) ==================== + /** + * 算 FRIEND_ADD / FRIEND_DELETE 帧的「对端 userId」: + * becomeFriends 单条入库后双方收到同一份 payload,payload.friendUserId 固定是 toUserId,本端真正的对端要从帧 sender / receiver 反推 + */ + computeFriendPeerId(frame: ImPrivateMessageDTO): number { + const userStore = useUserStore() + const currentUserId = Number(userStore.getUser?.id) || 0 + return frame.senderId === currentUserId ? frame.receiverId : frame.senderId + }, + /** * 好友通知统一入口:解析 content 里的 payload,按 type 分发到 friendStore 内部 dispatcher * - * 对应后端 ImPrivateMessageDTO.ofFriendNotification 系列;payload 实际类型见 - * BaseFriendNotification 子类(FriendRequestNotification / FriendAddNotification 等) + * 对应后端 ImPrivateMessageDTO.ofFriendNotification 系列; + * payload 实际类型见 BaseFriendNotification 子类(FriendRequestNotification / FriendAddNotification 等) */ handleFriendNotification(websocketMessage: ImPrivateMessageDTO) { // content 解析失败由外层 dispatchPrivateFrame 的 try-catch 兜底(含 websocketMessage 打印),不重复 catch @@ -502,10 +529,10 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', { friendStore.applyFriendRequestRejectedNotification(payload) break case ImMessageType.FRIEND_ADD: - friendStore.applyFriendAddNotification(payload) + friendStore.applyFriendAddNotification(payload, this.computeFriendPeerId(websocketMessage)) break case ImMessageType.FRIEND_DELETE: - friendStore.applyFriendDeleteNotification(payload) + friendStore.applyFriendDeleteNotification(payload, this.computeFriendPeerId(websocketMessage)) break case ImMessageType.FRIEND_BLOCK: friendStore.applyFriendBlockNotification(payload)