import qs from 'qs' import Axios, { HttpStatusCode } from 'axios' import type { AxiosInstance, AxiosResponse, CreateAxiosDefaults } from 'axios' import type { AccessToken, AccessTokenResponse } from './models/AccessToken' import { type AntiForgeryTokenResponse, type AntiForgeryToken } from './models/AntiForgeryToken' import { type ProblemDetails } from './ProblemDetails' class AxiosHelper { private readonly REFRESH_TOKEN_URL: string = '/common/refreshtoken' private constructor(config?: CreateAxiosDefaults) { this.axios = Axios.create({ withCredentials: true, baseURL: import.meta.env.VITE_API_BASE, paramsSerializer: (params) => { return qs.stringify(params, { indices: false, arrayFormat: 'repeat', }) }, ...config, }) this.axios.interceptors.request.use(async (config) => { if (this.accessToken?.token) { config.headers.Authorization = `Bearer ${this.accessToken?.token}` } if (this.csrfToken) config.headers[this.csrfToken.headerName] = this.csrfToken.token return config }) this.axios.interceptors.response.use(null, async (err) => { if (err.response?.status === HttpStatusCode.Unauthorized) { if (err.config.url == this.REFRESH_TOKEN_URL) { window.location.href = '/signin' return } const succed = await this.refreshAccessToken() if (succed) return await this.axios(err.config) else window.location.href = '/signin' } else if (err.response?.status === HttpStatusCode.BadRequest) { const details = err.response.data as ProblemDetails if (details.code == 1000) { const succed = await this.refreshCsrfToken() if (succed) return await this.axios(err.config) else throw Error('网络错误') } else if (details.code == 1001) { throw Error(details.detail || '') } else { throw Error(details.title || '') } } else { throw Error('网络错误') } }) } private accessToken: AccessToken | null = null private csrfToken: AntiForgeryToken | null = null private axios: AxiosInstance private static instance: AxiosHelper static getInstance(config?: CreateAxiosDefaults): AxiosHelper { if (!this.instance) { this.instance = new AxiosHelper(config) } return this.instance } private async refreshCsrfToken(): Promise { const csrfResp = await AxiosHelper.getInstance().post( '/common/antiforgery-token', ) if (csrfResp.isSucced) { this.csrfToken = csrfResp.data } else { throw Error(csrfResp.message || '服务器错误,请刷新重试') } return csrfResp.isSucced } private async refreshAccessToken(): Promise { const tokenResp = await AxiosHelper.getInstance().post( this.REFRESH_TOKEN_URL, ) if (tokenResp.isSucced) { this.accessToken = tokenResp.data this.autoRefreshToken() } return tokenResp.isSucced } private autoRefreshToken() { if (this.accessToken?.expiresIn) { setTimeout( async () => { await this.refreshAccessToken() }, (this.accessToken.expiresIn - 10) * 1000, ) } } private handleResult(result: AxiosResponse): T { if (result.status === HttpStatusCode.Ok) { return result.data } else { console.error(result) throw new Error(`${result.status}:${result.statusText}`) } } /** * 登录成功后调用 * @param accessToken */ async initToken(accessToken: AccessToken) { this.accessToken = accessToken if (this.accessToken?.expiresIn) { this.autoRefreshToken() } } async initCsrfToken(csrfToken: AntiForgeryToken) { this.csrfToken = csrfToken } async get(path: string, data?: TIn): Promise { const result = await AxiosHelper.getInstance().axios.get(path, { params: data, }) return this.handleResult(result) } async post(path: string, data?: TIn): Promise { const result = await AxiosHelper.getInstance().axios.post(path, data) return this.handleResult(result) } async postFormData(path: string, data?: TIn): Promise { const result = await AxiosHelper.getInstance().axios.postForm(path, data) return this.handleResult(result) } } export { AxiosHelper }