Files
stop-shoping-admin/src/api/AxiosHelper.ts
2026-03-25 14:57:39 +08:00

154 lines
4.5 KiB
TypeScript

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<boolean> {
const csrfResp = await AxiosHelper.getInstance().post<AntiForgeryTokenResponse>(
'/common/antiforgery-token',
)
if (csrfResp.isSucced) {
this.csrfToken = csrfResp.data
} else {
throw Error(csrfResp.message || '服务器错误,请刷新重试')
}
return csrfResp.isSucced
}
private async refreshAccessToken(): Promise<boolean> {
const tokenResp = await AxiosHelper.getInstance().post<AccessTokenResponse>(
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<T>(result: AxiosResponse<T, unknown, unknown>): 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<TOut, TIn = unknown>(path: string, data?: TIn): Promise<TOut> {
const result = await AxiosHelper.getInstance().axios.get(path, {
params: data,
})
return this.handleResult(result)
}
async post<TOut, TIn = unknown>(path: string, data?: TIn): Promise<TOut> {
const result = await AxiosHelper.getInstance().axios.post(path, data)
return this.handleResult(result)
}
async postFormData<TOut, TIn = unknown>(path: string, data?: TIn): Promise<TOut> {
const result = await AxiosHelper.getInstance().axios.postForm(path, data)
return this.handleResult(result)
}
}
export { AxiosHelper }