🐛 fix(im):codex 评审修复 FRIEND_ADD / FRIEND_DELETE 接收方 peer 与 clear 气泡

- 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 <noreply@anthropic.com>
im
YunaiV 2026-05-05 22:32:04 +08:00
parent c653c2fa2b
commit 5bd99c53c2
2 changed files with 47 additions and 13 deletions

View File

@ -437,14 +437,21 @@ export const useFriendStore = defineStore('imFriendStore', {
) )
}, },
/** FRIEND_ADD(1204):新增好友;本端拉取好友详情并入库 */ /**
applyFriendAddNotification(payload: FriendNotificationPayload) { * FRIEND_ADD(1204)
void this.loadFriendInfo(payload.friendUserId) * peerUserId websocketStore sender / receiver becomeFriends payload
* payload.friendUserIdpayload toUserId
*/
applyFriendAddNotification(_payload: FriendNotificationPayload, peerUserId: number) {
void this.loadFriendInfo(peerUserId)
}, },
/** FRIEND_DELETE(1205):好友被删除;本端清理 + 按 payload.clear 决定是否级联清会话(多端跟主操作端一致) */ /**
applyFriendDeleteNotification(payload: FriendNotificationPayload) { * FRIEND_DELETE(1205) + payload.clear
this.removeFriend(payload.friendUserId, payload.clear !== false) * peerUserId websocketStore sender / receiver FRIEND_ADD peer
*/
applyFriendDeleteNotification(payload: FriendNotificationPayload, peerUserId: number) {
this.removeFriend(peerUserId, payload.clear !== false)
}, },
/** FRIEND_BLOCK(1207):拉黑;多端同步 */ /** FRIEND_BLOCK(1207):拉黑;多端同步 */

View File

@ -26,6 +26,19 @@ import type {
Group Group
} from '../types' } from '../types'
/** FRIEND_DELETE 帧 payload 是否带 clear=trueclear 语义是清会话本身,跳过气泡渲染 */
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 * WebSocket DTO -> Message
* utils/user / * utils/user /
@ -225,9 +238,13 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
default: default:
if (isFriendNotification(websocketMessage.type)) { if (isFriendNotification(websocketMessage.type)) {
this.handleFriendNotification(websocketMessage) this.handleFriendNotification(websocketMessage)
// FRIEND_ADD / FRIEND_DELETE 同时作为会话事件气泡插入消息列表becomeFriends 入库 // FRIEND_ADD / FRIEND_DELETE同时作为会话事件气泡插入消息列表
// 帧 + silent / delete 单边推送帧统一走入库去重路径,前端按 type 渲染灰色提示) // becomeFriends 入库帧 + silent / delete 单边推送帧统一走入库去重路径,前端按 type 渲染灰色提示);
if (isFriendChatTip(websocketMessage.type)) { // FRIEND_DELETE 的 clear=true 语义是清会话本身,跳过气泡避免在已清会话里写入虚拟消息
if (
isFriendChatTip(websocketMessage.type) &&
!isFriendDeleteWithClear(websocketMessage)
) {
this.handlePrivateMessage(websocketMessage) this.handlePrivateMessage(websocketMessage)
} }
} else { } else {
@ -481,11 +498,21 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
// ==================== 好友通知1201-1210 段位,承载于私聊通道) ==================== // ==================== 好友通知1201-1210 段位,承载于私聊通道) ====================
/**
* FRIEND_ADD / FRIEND_DELETE userId
* becomeFriends payloadpayload.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 * content payload type friendStore dispatcher
* *
* ImPrivateMessageDTO.ofFriendNotification payload * ImPrivateMessageDTO.ofFriendNotification
* BaseFriendNotification FriendRequestNotification / FriendAddNotification * payload BaseFriendNotification FriendRequestNotification / FriendAddNotification
*/ */
handleFriendNotification(websocketMessage: ImPrivateMessageDTO) { handleFriendNotification(websocketMessage: ImPrivateMessageDTO) {
// content 解析失败由外层 dispatchPrivateFrame 的 try-catch 兜底(含 websocketMessage 打印),不重复 catch // content 解析失败由外层 dispatchPrivateFrame 的 try-catch 兜底(含 websocketMessage 打印),不重复 catch
@ -502,10 +529,10 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
friendStore.applyFriendRequestRejectedNotification(payload) friendStore.applyFriendRequestRejectedNotification(payload)
break break
case ImMessageType.FRIEND_ADD: case ImMessageType.FRIEND_ADD:
friendStore.applyFriendAddNotification(payload) friendStore.applyFriendAddNotification(payload, this.computeFriendPeerId(websocketMessage))
break break
case ImMessageType.FRIEND_DELETE: case ImMessageType.FRIEND_DELETE:
friendStore.applyFriendDeleteNotification(payload) friendStore.applyFriendDeleteNotification(payload, this.computeFriendPeerId(websocketMessage))
break break
case ImMessageType.FRIEND_BLOCK: case ImMessageType.FRIEND_BLOCK:
friendStore.applyFriendBlockNotification(payload) friendStore.applyFriendBlockNotification(payload)