From dd97f9cb701d162beaf152166cd0190fed95f057 Mon Sep 17 00:00:00 2001
From: Azure <983547216@qq.com>
Date: Wed, 24 Jun 2026 12:27:55 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=B7=E6=96=B0=20Access=20Token?=
=?UTF-8?q?=EF=BC=88=E5=B8=A6=E5=B9=B6=E5=8F=91=E9=94=81=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/common/sse/useSse.ts | 14 ++++++-------
src/stores/modules/account.ts | 38 ++++++++++++++++++++++++++---------
2 files changed, 34 insertions(+), 18 deletions(-)
diff --git a/src/common/sse/useSse.ts b/src/common/sse/useSse.ts
index b0a9d58..901570d 100644
--- a/src/common/sse/useSse.ts
+++ b/src/common/sse/useSse.ts
@@ -7,9 +7,6 @@ import { useAccountStore } from '@/stores'
/** SSE 连接 URL */
const SSE_URL = '/api/sse/subscribe'
-/** 是否正在刷新 token(防并发) */
-let isRefreshing = false
-
// ==================== 模块级单例 ====================
const isConnected = ref(false)
@@ -37,16 +34,19 @@ const client = new SseClient(SSE_URL, () => useAccountStore().accessToken, {
// ==================== Token 刷新 ====================
+/**
+ * SSE 收到 401 → 调用 store 的 refreshAccessToken() 刷新 token 后重连
+ *
+ * 无需维护独立并发锁:accountStore.refreshAccessToken() 内部已有 Promise 级锁,
+ * 若 HTTP 刷新拦截器已在刷新中,此调用会复用同一个 Promise,确保只发一次请求。
+ */
async function refreshTokenAndReconnect(): Promise {
- if (isRefreshing) return
-
const accountStore = useAccountStore()
if (!accountStore.refreshToken) {
sseBus.emit('error', { message: '登录已过期,请重新登录', retryCount: 0 })
return
}
- isRefreshing = true
try {
const newToken = await accountStore.refreshAccessToken()
if (newToken) {
@@ -54,8 +54,6 @@ async function refreshTokenAndReconnect(): Promise {
}
} catch {
sseBus.emit('error', { message: '实时连接异常,请刷新页面', retryCount: 0 })
- } finally {
- isRefreshing = false
}
}
diff --git a/src/stores/modules/account.ts b/src/stores/modules/account.ts
index e4797f3..d25c95d 100644
--- a/src/stores/modules/account.ts
+++ b/src/stores/modules/account.ts
@@ -95,26 +95,44 @@ export const useAccountStore = defineStore(
}
/**
- * 刷新 Access Token
+ * 刷新 Access Token(带并发锁)
*
- * 由刷新拦截器的 doRefreshToken 调用。
- * 返回新的 Access Token 字符串,方便拦截器直接使用。
+ * 由刷新拦截器的 doRefreshToken 和 SSE 的 onUnauthorized 调用。
+ * 返回新的 Access Token 字符串,方便调用方直接使用。
+ *
+ * 并发控制:若已有刷新正在进行中,返回同一个 Promise,
+ * 确保无论并发触发多少次,实际只发出一次 /account/refresh 请求。
*
* @returns 新的 Access Token
*/
+ /** 正在进行的刷新 Promise(null 表示无刷新进行中) */
+ let refreshPromise: Promise | null = null
+
const refreshAccessToken = async (): Promise => {
+ // 已有刷新进行中 → 复用同一个 Promise,避免并发重复请求
+ if (refreshPromise) {
+ return refreshPromise
+ }
+
if (!refreshToken.value) {
throw new Error('无 Refresh Token,无法刷新')
}
- const res = await refreshApi({ refreshToken: refreshToken.value })
- accessToken.value = res.data.accessToken
- if (res.data.refreshToken) {
- refreshToken.value = res.data.refreshToken
- }
- isTokenValid.value = true
+ refreshPromise = (async () => {
+ try {
+ const res = await refreshApi({ refreshToken: refreshToken.value })
+ accessToken.value = res.data.accessToken
+ if (res.data.refreshToken) {
+ refreshToken.value = res.data.refreshToken
+ }
+ isTokenValid.value = true
+ return accessToken.value
+ } finally {
+ refreshPromise = null
+ }
+ })()
- return accessToken.value
+ return refreshPromise
}
/**