154 lines
4.5 KiB
TypeScript
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 }
|