From e1aa8fc2a1410553a0218b90c49d763f03d7be2f Mon Sep 17 00:00:00 2001 From: "Ray.Hao" <1490493387@qq.com> Date: Wed, 12 Aug 2026 14:55:46 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20SSE=20=E4=BB=A4?= =?UTF-8?q?=E7=89=8C=E8=BF=87=E6=9C=9F=E5=90=8E=E8=87=AA=E5=8A=A8=E5=88=B7?= =?UTF-8?q?=E6=96=B0=E9=87=8D=E8=BF=9E,=E6=97=A0=E4=BB=A4=E7=89=8C?= =?UTF-8?q?=E9=87=8D=E8=BF=9E=E5=8F=97=E6=9C=80=E5=A4=A7=E6=AC=A1=E6=95=B0?= =?UTF-8?q?=E7=BA=A6=E6=9D=9F,=E5=AD=97=E5=85=B8=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E8=AE=A2=E9=98=85=E6=94=B9=E4=B8=BA=E5=B9=82=E7=AD=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/composables/sse/useDictSync.ts | 6 +++++- src/composables/sse/useSse.ts | 25 +++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/composables/sse/useDictSync.ts b/src/composables/sse/useDictSync.ts index d174f9c2..cc59185d 100644 --- a/src/composables/sse/useDictSync.ts +++ b/src/composables/sse/useDictSync.ts @@ -19,6 +19,7 @@ function createDictSyncComposable() { const callbacks: DictChangeCallback[] = []; let unsubscribe: (() => void) | null = null; + let initialized = false; // 防止重复初始化导致重复订阅 // 处理字典变更消息:清除指定字典缓存,并通知所有已注册回调 const handleDictChange = (data: DictChangeMessage) => { @@ -38,13 +39,16 @@ function createDictSyncComposable() { }); }; - // 订阅 SSE 字典变更事件 + // 订阅 SSE 字典变更事件(幂等:重复调用不会产生重复订阅) const initialize = () => { + if (initialized) return; + initialized = true; unsubscribe = sse.on(SseTopics.DICT, handleDictChange); }; // 取消 SSE 订阅并清空所有回调 const cleanup = () => { + initialized = false; unsubscribe?.(); unsubscribe = null; callbacks.length = 0; diff --git a/src/composables/sse/useSse.ts b/src/composables/sse/useSse.ts index 7f901a29..60c9492c 100644 --- a/src/composables/sse/useSse.ts +++ b/src/composables/sse/useSse.ts @@ -1,4 +1,5 @@ import { AuthStorage } from "@/utils/auth"; +import { useUserStoreHook } from "@/stores/user"; /** SSE 连接配置选项 */ export interface UseSseOptions { @@ -58,6 +59,7 @@ function createSseConnection(options: UseSseOptions = {}) { let reconnectTimer: ReturnType | null = null; let reconnectAttempts = 0; let currentReconnectInterval = config.reconnectInterval; + let tokenRefreshed = false; // 本轮拒绝是否已刷新过令牌,防止无限刷新 const eventHandlers = new Map>(); @@ -189,7 +191,8 @@ function createSseConnection(options: UseSseOptions = {}) { const token = AuthStorage.getAccessToken(); if (!token) { log("未检测到有效令牌,稍后重试"); - reconnectTimer = setTimeout(() => connect(), config.reconnectInterval); + // 走统一重连调度,受 maxReconnectAttempts 上限约束 + scheduleReconnect(); return; } @@ -213,9 +216,26 @@ function createSseConnection(options: UseSseOptions = {}) { }, signal: abortController.signal, }) - .then((response) => { + .then(async (response) => { if (!response.ok) { if (response.status === 401 || response.status === 403) { + // 令牌过期:刷新后用新令牌重连,刷新失败或令牌仍无效则停止重连 + if (!tokenRefreshed) { + tokenRefreshed = true; + connectionTimeoutTimer = clearTimer(connectionTimeoutTimer); + try { + const userStore = useUserStoreHook(); + await userStore.refreshTokenOnce(); + if (AuthStorage.getAccessToken()) { + connectionState.value = SseConnectionState.DISCONNECTED; + log(`SSE 连接被拒绝(HTTP ${response.status}),令牌已刷新,使用新令牌重连`); + connect(); + return null; + } + } catch (err) { + logError("SSE 令牌刷新失败:", err); + } + } isManualDisconnect = true; connectionState.value = SseConnectionState.DISCONNECTED; log(`SSE 连接被拒绝(HTTP ${response.status}),不再重连`); @@ -225,6 +245,7 @@ function createSseConnection(options: UseSseOptions = {}) { } connectionTimeoutTimer = clearTimer(connectionTimeoutTimer); connectionState.value = SseConnectionState.CONNECTED; + tokenRefreshed = false; resetReconnectState(); log("SSE 连接已建立"); return response.body?.getReader();