优化
This commit is contained in:
+2
-2
@@ -57,7 +57,7 @@ function redirectToLoginAfterSessionExpiry() {
|
||||
window.location.replace(`/login?redirect=${encodeURIComponent(redirect)}`)
|
||||
}
|
||||
|
||||
async function refreshSession() {
|
||||
export async function refreshAuthenticatedSession() {
|
||||
const observedRefreshToken = getRefreshToken()
|
||||
if (!observedRefreshToken) throw new ApiError('登录已过期,请重新登录', 'A0401', 401)
|
||||
if (!refreshPromise) {
|
||||
@@ -103,7 +103,7 @@ transport.interceptors.response.use(
|
||||
|
||||
request._retriedAfterRefresh = true
|
||||
try {
|
||||
const accessToken = await refreshSession()
|
||||
const accessToken = await refreshAuthenticatedSession()
|
||||
request.headers.Authorization = `Bearer ${accessToken}`
|
||||
return await transport.request(request)
|
||||
} catch (refreshError) {
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import axios, { type AxiosRequestConfig, type InternalAxiosRequestConfig } from 'axios'
|
||||
import { parseApiJsonResponse } from '@chongchong/shared'
|
||||
import { ApiError, isApiEnvelope, type ApiEnvelope } from '@/api/types'
|
||||
import { getOverseasAccessToken } from '@/api/session'
|
||||
import { refreshAuthenticatedSession } from '@/api/http'
|
||||
|
||||
interface RetryableRequest extends InternalAxiosRequestConfig { _retriedOverseasAuth?: boolean }
|
||||
|
||||
const transport = axios.create({
|
||||
baseURL: import.meta.env.VITE_OVERSEAS_API_BASE_URL || '/overseas-api',
|
||||
timeout: 15_000,
|
||||
transformResponse: [(data: unknown) => parseApiJsonResponse(data)],
|
||||
})
|
||||
|
||||
async function requireToken() {
|
||||
const current = getOverseasAccessToken()
|
||||
if (current) return current
|
||||
await refreshAuthenticatedSession()
|
||||
const refreshed = getOverseasAccessToken()
|
||||
if (!refreshed) throw new ApiError('当前环境未签发海外访问令牌', 'OVERSEAS_TOKEN_UNAVAILABLE')
|
||||
return refreshed
|
||||
}
|
||||
|
||||
transport.interceptors.request.use(async (config) => {
|
||||
config.headers.Authorization = `Bearer ${await requireToken()}`
|
||||
return config
|
||||
})
|
||||
|
||||
transport.interceptors.response.use((response) => response, async (error) => {
|
||||
const request = error.config as RetryableRequest | undefined
|
||||
if (error.response?.status !== 401 || !request || request._retriedOverseasAuth) throw error
|
||||
request._retriedOverseasAuth = true
|
||||
await refreshAuthenticatedSession()
|
||||
request.headers.Authorization = `Bearer ${await requireToken()}`
|
||||
return transport.request(request)
|
||||
})
|
||||
|
||||
export async function overseasApiRequest<T>(config: AxiosRequestConfig) {
|
||||
try {
|
||||
const response = await transport.request<ApiEnvelope<T>>(config)
|
||||
if (!isApiEnvelope(response.data) || response.data.code !== '200') {
|
||||
throw new ApiError(isApiEnvelope(response.data) ? response.data.message : '海外接口响应格式错误', isApiEnvelope(response.data) ? response.data.code : 'INVALID_RESPONSE')
|
||||
}
|
||||
return response.data.data
|
||||
} catch (error) {
|
||||
if (error instanceof ApiError) throw error
|
||||
if (axios.isAxiosError(error)) {
|
||||
const body = error.response?.data
|
||||
if (isApiEnvelope(body)) throw new ApiError(body.message, body.code, error.response?.status)
|
||||
throw new ApiError(error.code === 'ERR_NETWORK' ? '无法连接海外服务' : error.message, error.code || 'OVERSEAS_HTTP_ERROR', error.response?.status)
|
||||
}
|
||||
throw new ApiError(error instanceof Error ? error.message : '海外接口请求失败')
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
export const ACCESS_TOKEN_KEY = 'cc-client-access-token'
|
||||
export const REFRESH_TOKEN_KEY = 'cc-client-refresh-token'
|
||||
export const OVERSEAS_ACCESS_TOKEN_KEY = 'cc-client-overseas-access-token'
|
||||
export const OVERSEAS_EXPIRES_AT_KEY = 'cc-client-overseas-expires-at'
|
||||
|
||||
export interface TokenPair {
|
||||
tokenType: string
|
||||
@@ -7,6 +9,8 @@ export interface TokenPair {
|
||||
expiresIn: number
|
||||
refreshToken: string
|
||||
refreshExpiresIn: number
|
||||
overseasAccessToken?: string | null
|
||||
overseasExpiresIn?: number
|
||||
}
|
||||
|
||||
function activeStorage() {
|
||||
@@ -22,11 +26,22 @@ export function getRefreshToken() {
|
||||
return localStorage.getItem(REFRESH_TOKEN_KEY) || sessionStorage.getItem(REFRESH_TOKEN_KEY) || ''
|
||||
}
|
||||
|
||||
export function getOverseasAccessToken() {
|
||||
const storage = activeStorage()
|
||||
const expiresAt = Number(storage.getItem(OVERSEAS_EXPIRES_AT_KEY) || 0)
|
||||
if (expiresAt <= Date.now() + 30_000) return ''
|
||||
return storage.getItem(OVERSEAS_ACCESS_TOKEN_KEY) || ''
|
||||
}
|
||||
|
||||
export function persistTokenPair(tokens: TokenPair, remember?: boolean) {
|
||||
const storage = remember === undefined ? activeStorage() : remember ? localStorage : sessionStorage
|
||||
clearSessionTokens()
|
||||
storage.setItem(ACCESS_TOKEN_KEY, tokens.accessToken)
|
||||
storage.setItem(REFRESH_TOKEN_KEY, tokens.refreshToken)
|
||||
if (tokens.overseasAccessToken && Number(tokens.overseasExpiresIn) > 0) {
|
||||
storage.setItem(OVERSEAS_ACCESS_TOKEN_KEY, tokens.overseasAccessToken)
|
||||
storage.setItem(OVERSEAS_EXPIRES_AT_KEY, String(Date.now() + Number(tokens.overseasExpiresIn) * 1000))
|
||||
}
|
||||
}
|
||||
|
||||
export function persistTemporaryAccessToken(accessToken: string) {
|
||||
@@ -38,6 +53,8 @@ export function clearSessionTokens() {
|
||||
for (const storage of [localStorage, sessionStorage]) {
|
||||
storage.removeItem(ACCESS_TOKEN_KEY)
|
||||
storage.removeItem(REFRESH_TOKEN_KEY)
|
||||
storage.removeItem(OVERSEAS_ACCESS_TOKEN_KEY)
|
||||
storage.removeItem(OVERSEAS_EXPIRES_AT_KEY)
|
||||
storage.removeItem('cc-client-token')
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user