feat: 刷新 Access Token(带并发锁)
This commit is contained in:
@@ -7,9 +7,6 @@ import { useAccountStore } from '@/stores'
|
|||||||
/** SSE 连接 URL */
|
/** SSE 连接 URL */
|
||||||
const SSE_URL = '/api/sse/subscribe'
|
const SSE_URL = '/api/sse/subscribe'
|
||||||
|
|
||||||
/** 是否正在刷新 token(防并发) */
|
|
||||||
let isRefreshing = false
|
|
||||||
|
|
||||||
// ==================== 模块级单例 ====================
|
// ==================== 模块级单例 ====================
|
||||||
|
|
||||||
const isConnected = ref(false)
|
const isConnected = ref(false)
|
||||||
@@ -37,16 +34,19 @@ const client = new SseClient(SSE_URL, () => useAccountStore().accessToken, {
|
|||||||
|
|
||||||
// ==================== Token 刷新 ====================
|
// ==================== Token 刷新 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SSE 收到 401 → 调用 store 的 refreshAccessToken() 刷新 token 后重连
|
||||||
|
* <p>
|
||||||
|
* 无需维护独立并发锁:accountStore.refreshAccessToken() 内部已有 Promise 级锁,
|
||||||
|
* 若 HTTP 刷新拦截器已在刷新中,此调用会复用同一个 Promise,确保只发一次请求。
|
||||||
|
*/
|
||||||
async function refreshTokenAndReconnect(): Promise<void> {
|
async function refreshTokenAndReconnect(): Promise<void> {
|
||||||
if (isRefreshing) return
|
|
||||||
|
|
||||||
const accountStore = useAccountStore()
|
const accountStore = useAccountStore()
|
||||||
if (!accountStore.refreshToken) {
|
if (!accountStore.refreshToken) {
|
||||||
sseBus.emit('error', { message: '登录已过期,请重新登录', retryCount: 0 })
|
sseBus.emit('error', { message: '登录已过期,请重新登录', retryCount: 0 })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
isRefreshing = true
|
|
||||||
try {
|
try {
|
||||||
const newToken = await accountStore.refreshAccessToken()
|
const newToken = await accountStore.refreshAccessToken()
|
||||||
if (newToken) {
|
if (newToken) {
|
||||||
@@ -54,8 +54,6 @@ async function refreshTokenAndReconnect(): Promise<void> {
|
|||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
sseBus.emit('error', { message: '实时连接异常,请刷新页面', retryCount: 0 })
|
sseBus.emit('error', { message: '实时连接异常,请刷新页面', retryCount: 0 })
|
||||||
} finally {
|
|
||||||
isRefreshing = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -95,26 +95,44 @@ export const useAccountStore = defineStore(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 刷新 Access Token
|
* 刷新 Access Token(带并发锁)
|
||||||
*
|
*
|
||||||
* 由刷新拦截器的 doRefreshToken 调用。
|
* 由刷新拦截器的 doRefreshToken 和 SSE 的 onUnauthorized 调用。
|
||||||
* 返回新的 Access Token 字符串,方便拦截器直接使用。
|
* 返回新的 Access Token 字符串,方便调用方直接使用。
|
||||||
|
*
|
||||||
|
* 并发控制:若已有刷新正在进行中,返回同一个 Promise,
|
||||||
|
* 确保无论并发触发多少次,实际只发出一次 /account/refresh 请求。
|
||||||
*
|
*
|
||||||
* @returns 新的 Access Token
|
* @returns 新的 Access Token
|
||||||
*/
|
*/
|
||||||
|
/** 正在进行的刷新 Promise(null 表示无刷新进行中) */
|
||||||
|
let refreshPromise: Promise<string> | null = null
|
||||||
|
|
||||||
const refreshAccessToken = async (): Promise<string> => {
|
const refreshAccessToken = async (): Promise<string> => {
|
||||||
|
// 已有刷新进行中 → 复用同一个 Promise,避免并发重复请求
|
||||||
|
if (refreshPromise) {
|
||||||
|
return refreshPromise
|
||||||
|
}
|
||||||
|
|
||||||
if (!refreshToken.value) {
|
if (!refreshToken.value) {
|
||||||
throw new Error('无 Refresh Token,无法刷新')
|
throw new Error('无 Refresh Token,无法刷新')
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await refreshApi({ refreshToken: refreshToken.value })
|
refreshPromise = (async () => {
|
||||||
accessToken.value = res.data.accessToken
|
try {
|
||||||
if (res.data.refreshToken) {
|
const res = await refreshApi({ refreshToken: refreshToken.value })
|
||||||
refreshToken.value = res.data.refreshToken
|
accessToken.value = res.data.accessToken
|
||||||
}
|
if (res.data.refreshToken) {
|
||||||
isTokenValid.value = true
|
refreshToken.value = res.data.refreshToken
|
||||||
|
}
|
||||||
|
isTokenValid.value = true
|
||||||
|
return accessToken.value
|
||||||
|
} finally {
|
||||||
|
refreshPromise = null
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
return accessToken.value
|
return refreshPromise
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user