import { v4 as uuid_v4 } from 'uuid' import { FetchClient, type FetchResponse } from './fetchClient' import { useUserStore } from '@/stores/user' import { jumpToSignin } from '@/utils/casdoor/casdoor' import { refreshToken } from './auth' const baseUrl = import.meta.env.BASE_URL export interface Response { code: number message: string data: T traceId: string timestamp: number } const http = new FetchClient(baseUrl + 'api', 60 * 1000) // 超时时间设置为60s let isRefreshing = false let pendingQueue: (() => void)[] = [] async function handleTokenExpired( response: FetchResponse>, ): Promise>> { if (!('retry' in response)) { return response } // 原始请求包装成函数 const originalRequest = response.retry if (originalRequest === undefined) { return response } // 如果已经在刷新,挂起请求 if (isRefreshing) { return new Promise>>((resolve, reject) => { pendingQueue.push(() => originalRequest().then(resolve).catch(reject)) }) } isRefreshing = true try { // 调用刷新接口 const res = await refreshToken() const userStore = useUserStore() userStore.setToken(res) // 刷新完成,重放队列请求 await Promise.all(pendingQueue.map((fn) => fn())) pendingQueue = [] // 重新执行当前请求 return originalRequest() } catch (err) { handleRefreshFailed() throw err } finally { isRefreshing = false } } function handleRefreshFailed() { const userStore = useUserStore() userStore.setToken(undefined) pendingQueue = [] isRefreshing = false console.log('jumpToSignin') // 跳转登录页 jumpToSignin() } // 设置拦截器(可选) http.setRequestInterceptor((config) => { const userStore = useUserStore() // 自动加 token const token = userStore.token if (token) { config.headers = { ...config.headers, Authorization: `Bearer ${token}`, 'X-Trace-Id': uuid_v4(), } } return config }) http.setResponseInterceptor(async (response: FetchResponse>): Promise => { // 全局响应处理,比如统一格式校验 if (!response.ok) { console.log({ response }) switch (response.status) { case 401: // Unauthorized Token过期 // 刷新Token response = await handleTokenExpired(response) if (!response.ok) { console.log({ response }) throw new Error(response.data) } break case 403: // Forbidden 刷新Token失败 // 重新登录 handleRefreshFailed() break case 204: // No Content break default: console.log({ response }) throw new Error(response.data) } return response.data } return response.data.data }) export default http