From 3a77001b42b658bad7ac428c85d5dfd967f40f30 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 26 Apr 2026 23:32:55 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(im):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E4=B8=BB=E5=A3=B3=E5=88=9D=E5=A7=8B=E5=8C=96=E6=9C=9F=E9=97=B4?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E6=BC=8F=E6=8B=89=20/=20=E7=BC=93=E5=86=B2?= =?UTF-8?q?=E5=9B=9E=E6=94=BE=E5=A4=B1=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 三处时序竞态修复: - loading=true 提前到 connect 前,避免 WS 早于 pullOnce 推进 maxId 漏拉断线积压 - loading=false 提到 flushBuffer 前,让回放走正常 insertMessage 而非被 push 回 buffer - 加 bootstrapped 守卫,避免 isConnected watcher 在 friend/group 加载完前抢跑 附带:主壳文件名 Index.vue → index.vue 对齐其他模块小写惯例;清理 5 个 TODO @AI。 --- src/router/modules/remaining.ts | 2 +- .../im/home/composables/useMessagePuller.ts | 36 ++++++++++++------- src/views/im/home/{Index.vue => index.vue} | 30 +++++++++------- 3 files changed, 42 insertions(+), 26 deletions(-) rename src/views/im/home/{Index.vue => index.vue} (68%) diff --git a/src/router/modules/remaining.ts b/src/router/modules/remaining.ts index 9c2c1837d..d0d0f5474 100644 --- a/src/router/modules/remaining.ts +++ b/src/router/modules/remaining.ts @@ -756,7 +756,7 @@ const remainingRouter: AppRouteRecordRaw[] = [ children: [ { path: 'home', - component: () => import('@/views/im/home/Index.vue'), + component: () => import('@/views/im/home/index.vue'), name: 'ImHome', redirect: '/im/home/conversation', meta: { hidden: true, title: '聊天' }, diff --git a/src/views/im/home/composables/useMessagePuller.ts b/src/views/im/home/composables/useMessagePuller.ts index c193457ed..e3204f0c3 100644 --- a/src/views/im/home/composables/useMessagePuller.ts +++ b/src/views/im/home/composables/useMessagePuller.ts @@ -175,6 +175,12 @@ export const useMessagePuller = () => { /** 同一时刻只允许一次 pull:Index.vue 的手动调用与重连 watch 触发可能并发,共用同一个 promise 即可去重 */ let pullPromise: Promise | null = null + /** + * 首次 pull 是否已完成。仅在置 true 后,isConnected watch 才会触发 pull。 + * 防止 socket onopen 比 friendStore/groupStore 预拉先到达时,watcher 抢跑导致群消息缺 senderNickName + */ + let bootstrapped = false + /** 执行一次全量增量拉取(重入安全:进行中再次调用复用同一个 promise) */ const pullOnce = (): Promise => { if (!currentUserId) { @@ -192,23 +198,26 @@ export const useMessagePuller = () => { pullByType(ImConversationType.PRIVATE, conversationStore.privateMessageMaxId), pullByType(ImConversationType.GROUP, conversationStore.groupMessageMaxId) ]) - - // 回放 WebSocket 在 loading 期间收到的缓冲消息 - const buffered = wsStore.flushBuffer() - for (const item of buffered) { - if (item.conversationType === ImConversationType.PRIVATE) { - wsStore.handlePrivateMessage(item.payload) - } else { - wsStore.handleGroupMessage(item.payload) - } - } } catch (e) { console.error('[IM] 拉取离线消息失败:', e) } finally { + // 关闭 buffer 模式必须早于 flushBuffer,否则 handler 看到 loading=true 会把消息又 push 回 buffer conversationStore.loading = false - conversationStore.sortConversations() } + // 回放 WebSocket 在 loading 期间收到的缓冲消息(此刻走正常 insertMessage 路径) + const buffered = wsStore.flushBuffer() + for (const item of buffered) { + if (item.conversationType === ImConversationType.PRIVATE) { + wsStore.handlePrivateMessage(item.payload) + } else { + wsStore.handleGroupMessage(item.payload) + } + } + + // pull + replay 都完成后再排序,避免回放消息打乱顺序 + conversationStore.sortConversations() + // 重连 / 冷启动后补齐当前激活私聊会话的「对方已读位置」 // 离线期间错过的 RECEIPT 推送会被这里补回;其他私聊会话等用户点开时由 Index.vue 的 watch 触发 const active = conversationStore.activeConversation @@ -229,6 +238,7 @@ export const useMessagePuller = () => { } finally { // 整个 IIFE 全部完成(含已读位置补齐)后才允许下一次 pullOnce 重入 pullPromise = null + bootstrapped = true } })() return pullPromise @@ -236,12 +246,12 @@ export const useMessagePuller = () => { /** * 断网期间 WS 收不到推送,期间产生的消息只能靠拉取接口按 minId 游标补齐; - * 首次连接由 Index.vue 显式调 pullOnce,这里订阅 isConnected 的 false→true 转换,覆盖后续每次重连 + * 首次连接由 Index.vue 显式调 pullOnce 完成 bootstrap,这里仅覆盖之后的重连 */ watch( () => wsStore.isConnected, (isConnected) => { - if (isConnected) { + if (isConnected && bootstrapped) { void pullOnce() } } diff --git a/src/views/im/home/Index.vue b/src/views/im/home/index.vue similarity index 68% rename from src/views/im/home/Index.vue rename to src/views/im/home/index.vue index d4778e29d..75c69ebc8 100644 --- a/src/views/im/home/Index.vue +++ b/src/views/im/home/index.vue @@ -1,10 +1,9 @@ -