import axios from 'axios'; import type { AxiosInstance, AxiosRequestConfig, Canceler, AxiosResponse, } from 'axios'; import { refreshToken } from './../apis'; import { Toast } from 'antd-mobile'; import store from './../store'; import { messages } from '../locales'; import { updateUserInfo } from './../store/user'; import { enqueue } from '../store/cancelReq'; import { pubSub } from '.'; interface Params { [key: string]: unknown; } interface IAxiosHelper { isLoading: boolean; isToast: boolean; isCancelReq: boolean; isDeduplication: boolean; reqUniqueKey: string; } const { subscribe, dispatch } = pubSub(); axios.defaults.headers.common['Content-Type'] = 'application/json;charset=UTF-8'; class Httprequest { private timeout: number; private queue: Params; private loadingCount: number; private waitToken: boolean; // 是否正在换取新的Token constructor() { this.timeout = 50000; // 请求的超时时间50秒 this.queue = {}; // 请求队列,防止重复点击 this.loadingCount = 0; this.waitToken = false; } _closeLoading(isLoading: boolean) { if (isLoading) { this.loadingCount--; if (this.loadingCount <= 0) { Toast.clear(); this.loadingCount = 0; } } } setinterceptors( instance: AxiosInstance, { isLoading, isToast, isCancelReq, isDeduplication, reqUniqueKey, }: IAxiosHelper, ) { const { 'http.processing': processing, 'http.serverException': serverException, } = messages[store.getState().language.type]; instance.interceptors.request.use((config) => { const token: string = store.getState().user.token; if (config.headers) { if (token) { config.headers['X-Token'] = token; } } if (isDeduplication) { if (this.queue[reqUniqueKey]) { throw 'EXCEPTION:REPEATCLICK'; } else { this.queue[reqUniqueKey] = reqUniqueKey; } } if (isLoading) { this.loadingCount++; Toast.show({ duration: 0, maskClickable: false, icon: 'loading', content: 'Loading…', }); } if (isCancelReq) { config.cancelToken = new axios.CancelToken((cancel: Canceler) => { store.dispatch(enqueue(cancel)); }); } return config; }); instance.interceptors.response.use( (res: AxiosResponse) => { delete this.queue[reqUniqueKey]; this._closeLoading(isLoading); if (Number(res.data.code) === 0) { return Promise.resolve(res.data.data); } else if (res.data.code === '0200002') { console.log('登录过期'); // 登录过期 const refreshTokenApi = new Promise((resolve, reject) => { if (!this.waitToken) { this.waitToken = true; // 换取刷新Token store.dispatch( updateUserInfo({ token: store.getState().user.refreshToken }), ); refreshToken() .then((tokenRes) => { const token = tokenRes.token; if (token) { // 更新用户token&refreshToken store.dispatch( updateUserInfo({ token: token, refreshToken: tokenRes.refreshToken, }), ); resolve(); this.waitToken = false; dispatch(); } else { console.log('重新登录'); reject(); this.waitToken = false; dispatch(); } }) .catch(() => { reject(); this.waitToken = false; dispatch(); }); } else { console.log('正在换取Token,请等待', this.waitToken); return new Promise((resolve) => { subscribe(() => { setTimeout(() => resolve(this.request(res.config))); }); }); } }); return refreshTokenApi.then(() => this.request(res.config)); } if (isToast) { Toast.show({ icon: 'fail', maskClickable: false, content: res.data.message, }); } return Promise.reject(res.data); }, (error) => { this._closeLoading(isLoading); if (error === 'EXCEPTION:REPEATCLICK') { Toast.show({ icon: 'fail', maskClickable: false, content: processing, }); } else if (error.__proto__.constructor.name === 'Cancel') { delete this.queue[reqUniqueKey]; console.log('请求取消'); } else { delete this.queue[reqUniqueKey]; // 请求取消不处理 if (error.message !== 'canceled') { Toast.show({ icon: 'fail', maskClickable: false, content: serverException, }); } } return Promise.reject(error.message || error); }, ); } /** * * @param {*} axiosConfig axios 参数 * @param {*} isLoading 开启loading * @param {*} isToast 开启接口异常提示 * @param {*} isCancelReq 取消上一个页面未完成的请求 * @param {*} isDeduplication 开启接口防重 * @returns */ request( axiosConfig: AxiosRequestConfig, { isLoading = true, isToast = true, isCancelReq = true, isDeduplication = true, }: Partial = {}, ): Promise { const instance: AxiosInstance = axios.create(); axiosConfig.method || (axiosConfig.method = 'post'); const config = { baseURL: import.meta.env.VITE_API_HOST, timeout: this.timeout, ...axiosConfig, }; let reqUniqueKey = ''; if (isDeduplication) { let paramsStr = ''; if (axiosConfig.method.toLocaleUpperCase() === 'GET') { paramsStr = JSON.stringify(config.params || {}); } else { paramsStr = JSON.stringify(config.data || {}); } // 确定唯一性(路由+接口地址+请求方式) reqUniqueKey = `${location.href}${config.method}${config.url}${paramsStr}`.toLocaleUpperCase(); } this.setinterceptors(instance, { isLoading, isToast, isCancelReq, isDeduplication, reqUniqueKey, }); return instance(config); } } const http = new Httprequest(); export const httpGet = ( url: string, params?: Params, axiosHelper?: Partial, ): Promise => { return http.request( { url, params, method: 'GET', }, axiosHelper, ); }; export const httpPost = ( url: string, data?: Params, axiosHelper?: Partial, ): Promise => { return http.request( { url, data, }, axiosHelper, ); }; export default http.request.bind(http);