import type { UseFetchOptions } from '#app' import type { BasicResponse } from '~/models/common' import type { RequestQueueItem, IInterceptor, IConfig, IOption } from './interface' import { useLoginStore } from '~/stores/user/login' import { getToken, getRefreshToken } from '../auth' class Request { public baseURL: string public interceptor: IInterceptor private isRefreshing = false // 是否正在刷新token,开启请求队列 private requestQueue: RequestQueueItem[] // 待请求队列 constructor({ baseURL, interceptor }: IConfig) { this.baseURL = baseURL this.interceptor = interceptor as IInterceptor this.isRefreshing = false this.requestQueue = [] } request({ url, method, params, data, options }: IOption): Promise { const newOptions: UseFetchOptions = { baseURL: this.baseURL, method, query: params, body: data, ...options, onRequest: this.interceptor?.onRequest, onRequestError: this.interceptor?.onRequestError, onResponse: this.interceptor?.onResponse, onResponseError: this.interceptor?.onResponseError, } return new Promise((resolve, reject) => { this.requestPipeline(url, newOptions, resolve, reject) }) } // 请求管道处理具体细节 requestPipeline( url: string, options: UseFetchOptions, resolve: (data: T) => void, reject: (data: unknown) => void ): void { const token = getToken() const newOptions = { ...options, headers: { Authorization: token ? `Bearer ${token}` : '', ...options?.headers, }, } $fetch(url, newOptions as any) .then((res) => { resolve(res as T) }) .catch((error) => { if (error.status === 401) { if (!this.isRefreshing) { this.isRefreshing = true this.refreshToken() } this.addRequestQueueForRefreshToken(url, options, resolve, reject) } else { reject(error) } }) } // 刷新token refreshToken() { const loginStore = useLoginStore() this.postRefreshTokenFunc() .then((res) => { if (res.data) { const data = res.data loginStore.updateToken(data) this.requestQueueStartAfterRefreshToken() } }) .catch(() => { loginStore.logout() navigateTo({ path: '/' }) // ElMessage.error('登录已失效,需要重新登录') // navigateTo({ path: '/login' }) }) .finally(() => { this.isRefreshing = false }) } postRefreshTokenFunc(): Promise { const data = { clientId: getCanvasFingerprint(), } const token = getRefreshToken() return $fetch(this.baseURL + '/auth/refresh', { method: 'post', body: data, headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', }, }) } // 添加请求到等待队列 addRequestQueueForRefreshToken( url: string, options: UseFetchOptions, resolve: (data: T) => void, reject: (data: unknown) => void ): void { this.requestQueue.push({ url, // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore options, // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore resolve, reject, }) } // 刷新token成功等待队列开始请求 requestQueueStartAfterRefreshToken(): void { let requestQueueItem = this.requestQueue.pop() while (requestQueueItem) { const { options, url, resolve, reject } = requestQueueItem this.requestPipeline(url, options, resolve, reject) requestQueueItem = this.requestQueue.pop() } } } export default Request