✨ feat(im): 增加 useMessagePuller 用于首次消息的拉取
parent
8c1f17f5a6
commit
e573462cb7
|
|
@ -51,6 +51,14 @@ export const readPrivateMessages = (receiverId: number | string, messageId: numb
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 查询对方已读到我发的最大消息 id(多端 / 离线后用于补齐已读状态)
|
||||||
|
export const getPrivateMaxReadMessageId = (peerId: number | string) => {
|
||||||
|
return request.get<number | null>({
|
||||||
|
url: '/im/message/private/max-read-message-id',
|
||||||
|
params: { peerId }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 撤回私聊消息
|
// 撤回私聊消息
|
||||||
export const recallPrivateMessage = (id: number | string) => {
|
export const recallPrivateMessage = (id: number | string) => {
|
||||||
return request.delete<ImPrivateMessageRespVO>({
|
return request.delete<ImPrivateMessageRespVO>({
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
import { watch } from 'vue'
|
import { watch } from 'vue'
|
||||||
import { useConversationStore } from '../store/conversationStore'
|
import { useConversationStore } from '../store/conversationStore'
|
||||||
import { useImWebSocketStore } from '../store/websocketStore'
|
import { useImWebSocketStore } from '../store/websocketStore'
|
||||||
|
import { useFriendStore } from '../store/friendStore'
|
||||||
|
import { useGroupStore } from '../store/groupStore'
|
||||||
import {
|
import {
|
||||||
pullPrivateMessages as apiPullPrivateMessages,
|
pullPrivateMessages as apiPullPrivateMessages,
|
||||||
|
getPrivateMaxReadMessageId as apiGetPrivateMaxReadMessageId,
|
||||||
type ImPrivateMessageRespVO
|
type ImPrivateMessageRespVO
|
||||||
} from '@/api/im/message/private'
|
} from '@/api/im/message/private'
|
||||||
import {
|
import {
|
||||||
|
|
@ -33,10 +36,24 @@ export const useMessagePuller = () => {
|
||||||
const conversationStore = useConversationStore()
|
const conversationStore = useConversationStore()
|
||||||
const wsStore = useImWebSocketStore()
|
const wsStore = useImWebSocketStore()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
const friendStore = useFriendStore()
|
||||||
|
const groupStore = useGroupStore()
|
||||||
const currentUserId = Number(userStore.getUser?.id) || 0
|
const currentUserId = Number(userStore.getUser?.id) || 0
|
||||||
|
|
||||||
|
/** 私聊会话归属:自己发的算"发给 receiverId 的会话",否则算"发送方的会话" */
|
||||||
|
const getPrivatePeerId = (message: ImPrivateMessageRespVO) =>
|
||||||
|
message.senderId === currentUserId ? message.receiverId : message.senderId
|
||||||
|
|
||||||
|
/** 群消息发送者在群内的展示名(群备注 > 用户昵称) */
|
||||||
|
const getGroupSenderNickName = (message: ImGroupMessageRespVO): string => {
|
||||||
|
const group = groupStore.getGroup(message.groupId)
|
||||||
|
const member = group?.members?.find((m) => m.userId === message.senderId)
|
||||||
|
return member?.displayUserName || member?.nickname || ''
|
||||||
|
}
|
||||||
|
|
||||||
/** 服务端私聊消息 -> 本地 Message */
|
/** 服务端私聊消息 -> 本地 Message */
|
||||||
const convertPrivateMessage = (message: ImPrivateMessageRespVO): Message => {
|
const convertPrivateMessage = (message: ImPrivateMessageRespVO): Message => {
|
||||||
|
const friend = friendStore.getFriend(getPrivatePeerId(message))
|
||||||
return {
|
return {
|
||||||
id: message.id,
|
id: message.id,
|
||||||
clientMessageId: message.clientMessageId || '',
|
clientMessageId: message.clientMessageId || '',
|
||||||
|
|
@ -45,7 +62,7 @@ export const useMessagePuller = () => {
|
||||||
status: message.status,
|
status: message.status,
|
||||||
sendTime: new Date(message.sendTime).getTime(),
|
sendTime: new Date(message.sendTime).getTime(),
|
||||||
senderId: message.senderId,
|
senderId: message.senderId,
|
||||||
senderNickName: '',
|
senderNickName: friend?.nickname || '',
|
||||||
targetId: message.receiverId,
|
targetId: message.receiverId,
|
||||||
selfSend: message.senderId === currentUserId
|
selfSend: message.senderId === currentUserId
|
||||||
}
|
}
|
||||||
|
|
@ -61,7 +78,7 @@ export const useMessagePuller = () => {
|
||||||
status: message.status,
|
status: message.status,
|
||||||
sendTime: new Date(message.sendTime).getTime(),
|
sendTime: new Date(message.sendTime).getTime(),
|
||||||
senderId: message.senderId,
|
senderId: message.senderId,
|
||||||
senderNickName: '',
|
senderNickName: getGroupSenderNickName(message),
|
||||||
targetId: message.groupId,
|
targetId: message.groupId,
|
||||||
selfSend: message.senderId === currentUserId,
|
selfSend: message.senderId === currentUserId,
|
||||||
atUserIds: message.atUserIds || [],
|
atUserIds: message.atUserIds || [],
|
||||||
|
|
@ -73,22 +90,24 @@ export const useMessagePuller = () => {
|
||||||
|
|
||||||
/** 私聊:会话归属到对端 userId */
|
/** 私聊:会话归属到对端 userId */
|
||||||
const convertPrivateConversation = (message: ImPrivateMessageRespVO) => {
|
const convertPrivateConversation = (message: ImPrivateMessageRespVO) => {
|
||||||
const targetId = message.senderId === currentUserId ? message.receiverId : message.senderId
|
const targetId = getPrivatePeerId(message)
|
||||||
|
const friend = friendStore.getFriend(targetId)
|
||||||
return {
|
return {
|
||||||
type: ImConversationType.PRIVATE,
|
type: ImConversationType.PRIVATE,
|
||||||
targetId,
|
targetId,
|
||||||
name: String(targetId),
|
name: friend?.nickname || String(targetId),
|
||||||
avatar: ''
|
avatar: friend?.avatar || ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 群聊:会话归属到 groupId */
|
/** 群聊:会话归属到 groupId */
|
||||||
const convertGroupConversation = (message: ImGroupMessageRespVO) => {
|
const convertGroupConversation = (message: ImGroupMessageRespVO) => {
|
||||||
|
const group = groupStore.getGroup(message.groupId)
|
||||||
return {
|
return {
|
||||||
type: ImConversationType.GROUP,
|
type: ImConversationType.GROUP,
|
||||||
targetId: message.groupId,
|
targetId: message.groupId,
|
||||||
name: String(message.groupId),
|
name: group?.name || String(message.groupId),
|
||||||
avatar: ''
|
avatar: group?.avatar || ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,11 +131,12 @@ export const useMessagePuller = () => {
|
||||||
if (isPrivate) {
|
if (isPrivate) {
|
||||||
const message = raw as ImPrivateMessageRespVO
|
const message = raw as ImPrivateMessageRespVO
|
||||||
if (message.type === ImMessageType.RECALL) {
|
if (message.type === ImMessageType.RECALL) {
|
||||||
|
const peerId = getPrivatePeerId(message)
|
||||||
conversationStore.recallMessage(
|
conversationStore.recallMessage(
|
||||||
ImConversationType.PRIVATE,
|
ImConversationType.PRIVATE,
|
||||||
message.senderId === currentUserId ? message.receiverId : message.senderId,
|
peerId,
|
||||||
message.content,
|
message.content,
|
||||||
'',
|
friendStore.getFriend(peerId)?.nickname || '',
|
||||||
message.senderId === currentUserId
|
message.senderId === currentUserId
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
@ -132,7 +152,7 @@ export const useMessagePuller = () => {
|
||||||
ImConversationType.GROUP,
|
ImConversationType.GROUP,
|
||||||
message.groupId,
|
message.groupId,
|
||||||
message.content,
|
message.content,
|
||||||
'',
|
getGroupSenderNickName(message),
|
||||||
message.senderId === currentUserId
|
message.senderId === currentUserId
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
@ -164,28 +184,50 @@ export const useMessagePuller = () => {
|
||||||
return pullPromise
|
return pullPromise
|
||||||
}
|
}
|
||||||
pullPromise = (async () => {
|
pullPromise = (async () => {
|
||||||
conversationStore.loading = true
|
|
||||||
try {
|
try {
|
||||||
// 并发拉取私聊 + 群聊,降低初始加载耗时
|
conversationStore.loading = true
|
||||||
await Promise.all([
|
try {
|
||||||
pullByType(ImConversationType.PRIVATE, conversationStore.privateMessageMaxId),
|
// 并发拉取私聊 + 群聊,降低初始加载耗时
|
||||||
pullByType(ImConversationType.GROUP, conversationStore.groupMessageMaxId)
|
await Promise.all([
|
||||||
])
|
pullByType(ImConversationType.PRIVATE, conversationStore.privateMessageMaxId),
|
||||||
|
pullByType(ImConversationType.GROUP, conversationStore.groupMessageMaxId)
|
||||||
|
])
|
||||||
|
|
||||||
// 回放 WebSocket 在 loading 期间收到的缓冲消息
|
// 回放 WebSocket 在 loading 期间收到的缓冲消息
|
||||||
const buffered = wsStore.flushBuffer()
|
const buffered = wsStore.flushBuffer()
|
||||||
for (const item of buffered) {
|
for (const item of buffered) {
|
||||||
if (item.conversationType === ImConversationType.PRIVATE) {
|
if (item.conversationType === ImConversationType.PRIVATE) {
|
||||||
wsStore.handlePrivateMessage(item.payload)
|
wsStore.handlePrivateMessage(item.payload)
|
||||||
} else {
|
} else {
|
||||||
wsStore.handleGroupMessage(item.payload)
|
wsStore.handleGroupMessage(item.payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[IM] 拉取离线消息失败:', e)
|
||||||
|
} finally {
|
||||||
|
conversationStore.loading = false
|
||||||
|
conversationStore.sortConversations()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重连 / 冷启动后补齐当前激活私聊会话的「对方已读位置」
|
||||||
|
// 离线期间错过的 RECEIPT 推送会被这里补回;其他私聊会话等用户点开时由 Index.vue 的 watch 触发
|
||||||
|
const active = conversationStore.activeConversation
|
||||||
|
if (active && active.type === ImConversationType.PRIVATE) {
|
||||||
|
try {
|
||||||
|
const maxReadId = await apiGetPrivateMaxReadMessageId(active.targetId)
|
||||||
|
if (maxReadId) {
|
||||||
|
conversationStore.applyReadReceipt({
|
||||||
|
conversationType: ImConversationType.PRIVATE,
|
||||||
|
targetId: active.targetId,
|
||||||
|
privateReadMaxId: maxReadId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[IM] 拉取对方已读位置失败', e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
console.error('[IM] 拉取离线消息失败:', e)
|
|
||||||
} finally {
|
} finally {
|
||||||
conversationStore.loading = false
|
// 整个 IIFE 全部完成(含已读位置补齐)后才允许下一次 pullOnce 重入
|
||||||
conversationStore.sortConversations()
|
|
||||||
pullPromise = null
|
pullPromise = null
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue