fix(playground): 修复 401 鉴权恢复跳转

This commit is contained in:
sunlei 2026-05-17 15:32:10 +08:00
parent e28c425603
commit d1812d3c3d
2 changed files with 77 additions and 8 deletions

View File

@ -17,6 +17,7 @@ const ACCESS_CODES_KEY = 'kt-admin-access-codes'
const USER_INFO_KEY = 'kt-admin-user-info' const USER_INFO_KEY = 'kt-admin-user-info'
let refreshPromise: Promise<string | null> | null = null let refreshPromise: Promise<string | null> | null = null
let redirectingToAdminLogin = false
function getApiBase() { function getApiBase() {
return import.meta.env.VITE_APP_API_BASE || '/api' return import.meta.env.VITE_APP_API_BASE || '/api'
@ -76,6 +77,9 @@ function buildAdminLoginUrl(redirect: string) {
} }
export function redirectToAdminLogin() { export function redirectToAdminLogin() {
if (redirectingToAdminLogin) return
redirectingToAdminLogin = true
window.location.href = buildAdminLoginUrl(window.location.href) window.location.href = buildAdminLoginUrl(window.location.href)
} }

View File

@ -8,10 +8,19 @@ import {
export type ApiResponse<T = unknown> = { export type ApiResponse<T = unknown> = {
code: number code: number
message?: string
msg: string msg: string
data: T data: T
} }
type AuthRetryConfig = AxiosRequestConfig & {
_authRetried?: boolean
}
type AuthHeaderMap = Record<string, unknown> & {
get?: (name: string) => unknown
}
const request = axios.create({ const request = axios.create({
baseURL: import.meta.env.VITE_APP_API_BASE || '/api', baseURL: import.meta.env.VITE_APP_API_BASE || '/api',
timeout: 1000 * 30, timeout: 1000 * 30,
@ -40,14 +49,61 @@ request.interceptors.request.use(async (config) => {
return config return config
}) })
function getAuthErrorMessage(data?: Partial<ApiResponse>) {
return data?.msg || data?.message || '登录已过期'
}
function getRequestAuthorization(config?: AuthRetryConfig) {
const headers = config?.headers as AuthHeaderMap | undefined
if (!headers) return null
if (typeof headers.get === 'function') {
return headers.get('Authorization') || headers.get('authorization')
}
return headers.Authorization || headers.authorization
}
async function retryRequestWithFreshToken(config?: AuthRetryConfig) {
if (!config || config._authRetried) return null
const hasOldAccessToken = Boolean(
getStoredAccessToken() || getRequestAuthorization(config),
)
if (!hasOldAccessToken) return null
config._authRetried = true
clearPersistedAuth()
const accessToken = await refreshPersistedAuth()
if (!accessToken) return null
config.headers = {
...(config.headers || {}),
Authorization: `Bearer ${accessToken}`,
}
// 只有旧 accessToken 过期时才尝试刷新并重放一次,未登录 401 直接去 Admin。
return request.request(config)
}
function redirectAfterAuthExpired() {
clearPersistedAuth()
redirectToAdminLogin()
}
request.interceptors.response.use( request.interceptors.response.use(
(response) => { async (response) => {
const data = response.data as ApiResponse<any> const data = response.data as ApiResponse<any>
if (response.status === 401 || data.code === 401) { if (response.status === 401 || data.code === 401) {
clearPersistedAuth() const retryResponse = await retryRequestWithFreshToken(
redirectToAdminLogin() response.config as AuthRetryConfig,
return Promise.reject(new Error(data.msg || '登录已过期')) )
if (retryResponse) return retryResponse
redirectAfterAuthExpired()
return Promise.reject(new Error(getAuthErrorMessage(data)))
} }
if (data.code !== 200) { if (data.code !== 200) {
@ -56,15 +112,24 @@ request.interceptors.response.use(
return data.data as any return data.data as any
}, },
(error) => { async (error) => {
if (axios.isAxiosError<ApiResponse>(error)) { if (axios.isAxiosError<ApiResponse>(error)) {
if (error.response?.status === 401) { if (error.response?.status === 401) {
clearPersistedAuth() const retryResponse = await retryRequestWithFreshToken(
redirectToAdminLogin() error.config as AuthRetryConfig | undefined,
)
if (retryResponse) return retryResponse
redirectAfterAuthExpired()
} }
return Promise.reject( return Promise.reject(
new Error(error.response?.data?.msg || error.message || '请求失败'), new Error(
error.response?.data?.msg ||
error.response?.data?.message ||
error.message ||
'请求失败',
),
) )
} }