/* * @Author: 李佐宁 lizuoning@yuan-info.com * @Date: 2022-07-11 19:55:25 * @LastEditors: 李佐宁 * @LastEditTime: 2023-08-28 15:18:46 * @FilePath: /component-library/Users/changjun/yuan/safe-product-authority-browser/src/utils/https/index.ts * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE */ import Axios, { AxiosRequestConfig, CancelTokenStatic, AxiosInstance, } from 'axios'; import qs from 'qs'; import { genConfig } from './config'; import { transformConfigByMethod } from './utils'; import { getUserInfoCache } from '../auth/user'; import settings from '../../settings'; import { getTokenTypeCache, getTokenValueCache } from '../../utils/auth/token'; import { yaErrorMessage } from '../../utils/message'; import { cancelTokenType, RequestMethods, EnclosureHttpRequestConfig, EnclosureHttpResoponse, EnclosureHttpError, } from './types.d'; import { useUserStoreWithOut } from '../../store/modules/users'; import { useConfigStoreWithOut } from '../../store/modules/config'; import { useRouter, useRoute } from 'vue-router'; import { router } from '../../router'; const configStore = useConfigStoreWithOut(); // Axios.defaults.withCredentials = true class EnclosureHttp { constructor() { this.httpInterceptorsRequest(); this.httpInterceptorsResponse(); } // private 私有属性,只可内部使用 // 初始化配置对象 private static initConfig: EnclosureHttpRequestConfig = {}; // 保存当前Axios实例对象 private static axiosInstance: AxiosInstance = Axios.create(genConfig()); // 保存 EnclosureHttp实例 private static EnclosureHttpInstance: EnclosureHttp; // axios取消对象 private CancelToken: CancelTokenStatic = Axios.CancelToken; // 取消的凭证数组 private sourceTokenList: Array = []; // 记录当前这一次cancelToken的key private currentCancelTokenKey = ''; private beforeRequestCallback: EnclosureHttpRequestConfig['beforeRequestCallback'] = undefined; private beforeResponseCallback: EnclosureHttpRequestConfig['beforeResponseCallback'] = undefined; // public 公共属性,外部也可以使用 public get cancelTokenList(): Array { return this.sourceTokenList; } // eslint-disable-next-line class-methods-use-this public set cancelTokenList(value) { throw new Error('cancelTokenList不允许赋值'); } /** * @description 生成唯一取消key * @param config axios配置 * @returns string */ // eslint-disable-next-line class-methods-use-this private static genUniqueKey(config: EnclosureHttpRequestConfig): string { return `${config.url}--${JSON.stringify(config.data)}`; } /** * @description 取消重复请求 * @returns void 0 */ private cancelRepeatRequest(): void { const temp: { [key: string]: boolean } = {}; this.sourceTokenList = this.sourceTokenList.reduce>( (res: Array, cancelToken: cancelTokenType) => { const { cancelKey, cancelExecutor } = cancelToken; if (!temp[cancelKey]) { temp[cancelKey] = true; res.push(cancelToken); } else { cancelExecutor(); } return res; }, [] ); } /** * @description 取消所有请求 * @returns void 0 */ private cancelAllRequest(): void { this.sourceTokenList.map((item) => { item.cancelExecutor(); }); } /** * @description 删除指定的CancelToken * @returns void 0 */ private deleteCancelTokenByCancelKey(cancelKey: string): void { this.sourceTokenList = this.sourceTokenList.length < 1 ? this.sourceTokenList.filter( (cancelToken) => cancelToken.cancelKey !== cancelKey ) : []; } /** * @description 拦截请求 * @returns void 0 */ private httpInterceptorsRequest(): void { EnclosureHttp.axiosInstance.interceptors.request.use( (config: EnclosureHttpRequestConfig) => { const $config = config; // NProgress.start(); // 每次切换页面时,调用进度条 const cancelKey = EnclosureHttp.genUniqueKey($config); $config.cancelToken = new this.CancelToken( (cancelExecutor: (cancel: any) => void) => { this.sourceTokenList.push({ cancelKey, cancelExecutor }); } ); // 请求拦截 携带 token const tokenWhiteList = ['captcha', 'token']; const isNeedToken = tokenWhiteList.some((item) => $config.url.includes(item) ); console.log('$config.url', $config.url); if (!isNeedToken) { // @ts-ignore $config.headers[settings.tokenName] = getTokenValueCache() ? `${getTokenTypeCache()} ${getTokenValueCache()}` : 'Basic eXVhbjp5dWFuMTIz'; } else { // @ts-ignore $config.headers[settings.tokenName] = 'Basic eXVhbjp5dWFuMTIz'; } // this.cancelRepeatRequest(); // this.currentCancelTokenKey = cancelKey; // 优先判断post/get等方法是否传入回掉,否则执行初始化设置等回掉 if (typeof this.beforeRequestCallback === 'function') { this.beforeRequestCallback($config); this.beforeRequestCallback = undefined; return $config; } if (EnclosureHttp.initConfig.beforeRequestCallback) { EnclosureHttp.initConfig.beforeRequestCallback($config); return $config; } return $config; }, (error: any) => { return Promise.reject(error); } ); } /** * @description 清空当前cancelTokenList * @returns void 0 */ public clearCancelTokenList(): void { this.sourceTokenList.length = 0; } /** * @description 拦截响应 * @returns void 0 */ private httpInterceptorsResponse(): void { const instance = EnclosureHttp.axiosInstance; instance.interceptors.response.use( (response: EnclosureHttpResoponse) => { // 清除loading const { loading } = response?.config?.headers || {}; if (loading) { configStore.setLoadState(false); } const status = response?.status || 200; const code = Number(response?.data?.code) || 200; let message = response?.data?.message || ''; // 当 HttpCode 为 200时,此时都是业务错误,如果 message 存在就将其抛出 if (code !== 200 && response.data?.message) { yaErrorMessage(message); } if ( response?.config?.responseType === 'blob' && response?.headers['content-type'] === 'application/json' ) { parseFileReader(response.data); return; } // 请求每次成功一次就删除当前canceltoken标记 const cancelKey = EnclosureHttp.genUniqueKey(response.config); this.deleteCancelTokenByCancelKey(cancelKey); // 优先判断post/get等方法是否传入回掉,否则执行初始化设置等回掉 if (typeof this.beforeResponseCallback === 'function') { this.beforeResponseCallback(response); this.beforeResponseCallback = undefined; return response.data; } if (EnclosureHttp.initConfig.beforeResponseCallback) { EnclosureHttp.initConfig.beforeResponseCallback(response); return response.data; // NProgress.done(); } return response.data; }, (error: EnclosureHttpError) => { const { loading } = error?.config?.headers || {}; if (loading) { configStore.setLoadState(false); } const $error = error; const userStore = useUserStoreWithOut(); if (this.currentCancelTokenKey) { const haskey = this.sourceTokenList.filter( (cancelToken) => cancelToken.cancelKey === this.currentCancelTokenKey ).length; if (haskey) { this.sourceTokenList = this.sourceTokenList.filter( (cancelToken) => cancelToken.cancelKey !== this.currentCancelTokenKey ); this.currentCancelTokenKey = ''; } } $error.isCancelRequest = Axios.isCancel($error); // NProgress.done(); // 所有的响应异常 区分来源为取消请求/非取消请求 // 全局错误处理 try { const { response = {}, code = null, message = '', config, } = $error || {}; const status = Number(response?.status); const _errorCode = response.data?.code; const _errorMessage = response.data?.message; const errorUrl = response?.request?.responseURL; const isLogoutRequest = errorUrl.includes('signout'); // 跳转异常页面 function toErrorPageRouter(errorCode) { const { menuId } = router.currentRoute.value.meta; const { fullPath } = router.currentRoute.value; if (fullPath && fullPath.includes(errorCode)) return; const _errorPath = `${fullPath}/${errorCode}?menuid=${menuId}`; console.log(_errorPath, '_errorPath'); router.push({ path: _errorPath }); } if (code === '"ECONNABORTED"') { // 服务超时 toErrorPageRouter('504'); } else { switch (status) { case 502: // 服务器异常 this.cancelAllRequest(); yaErrorMessage('服务器异常'); toErrorPageRouter('502'); break; default: if (_errorMessage && !isLogoutRequest) { yaErrorMessage(_errorMessage); } // 未授权操作 if (_errorCode && _errorCode === 403) { toErrorPageRouter('403'); return; } // token 无效的跳转登录 if (_errorCode && _errorCode === 401 && !isLogoutRequest) { this.cancelAllRequest(); userStore.logoutAction(router, false); return; } break; } } } catch (error) { throw new Error(error as unknown as string); } return Promise.reject($error); // sudo } ); } public request( method: RequestMethods, url: string, param?: AxiosRequestConfig, axiosConfig?: EnclosureHttpRequestConfig ): Promise { const userinfo: any = getUserInfoCache() || {}; axiosConfig = axiosConfig || {}; const headers = axiosConfig.headers || {}; if (!userinfo.name) { const params = new URLSearchParams(param); userinfo.name = params.get('username'); } axiosConfig.headers = Object.assign( { adminName: userinfo.name, loading: param?.yaLoading }, headers ); // loading进入 if (param?.yaLoading) { configStore.setLoadState(true); delete param.yaLoading; } const config = transformConfigByMethod(param, { method, url, ...axiosConfig, } as EnclosureHttpRequestConfig); // 单独处理自定义请求/响应回掉 if (axiosConfig?.beforeRequestCallback) { this.beforeRequestCallback = axiosConfig.beforeRequestCallback; } if (axiosConfig?.beforeResponseCallback) { this.beforeResponseCallback = axiosConfig.beforeResponseCallback; } return new Promise((resolve, reject) => { EnclosureHttp.axiosInstance .request(config) .then((response) => { return resolve(response); }) .catch((error: any) => { reject(error); }); }); } public post( url: string, params?: T, config?: EnclosureHttpRequestConfig ): Promise { return this.request('post', url, params, config); } public get( url: string, params?: T, config?: EnclosureHttpRequestConfig ): Promise { return this.request('get', url, params, config); } public delete( url: string, params?: T, config?: EnclosureHttpRequestConfig ): Promise { return this.request('delete', url, params, config); } } // 文件流数据转化 const parseFileReader = (data) => { return new Promise((resolve) => { const reader = new FileReader(); reader.onload = function (event) { const arrayBuffer = event.target.result; const text = new TextDecoder().decode(arrayBuffer); try { const { code, message } = JSON.parse(text) || {}; if (code === 2003) { yaErrorMessage(message); } } catch (e) {} // 在这里处理 ArrayBuffer 数据 }; reader.readAsArrayBuffer(data); }); }; export const http = new EnclosureHttp();