<% const { apiConfig, generateResponses, config } = it; %> import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, HeadersDefaults, ResponseType, RawAxiosRequestHeaders } from 'axios' import { ElLoading, ElMessage, LoadingOptions } from 'element-plus' import { storeToRefs } from 'pinia' import { useUserInfo } from '/@/stores/userInfo' export type QueryParamsType = Record; export interface FullRequestParams extends Omit { /** set parameter to `true` for call `securityWorker` for this request */ secure?: boolean; /** request path */ path: string; /** content type of request body */ type?: ContentType; /** query params */ query?: QueryParamsType; /** format of response (i.e. response.json() -> format: "json") */ format?: ResponseType; /** request body */ body?: unknown; /** 显示错误消息 */ showErrorMessage?: boolean /** 显示成功消息 */ showSuccessMessage?: boolean /** 登录访问 */ login?: boolean /** 加载中 */ loading?: boolean /** 加载中选项 */ loadingOptions?: LoadingOptions /** 取消重复请求 */ cancelRepeatRequest?: boolean } export type RequestParams = Omit; export interface ApiConfig extends Omit { securityWorker?: (securityData: SecurityDataType | null) => Promise | AxiosRequestConfig | void; secure?: boolean; format?: ResponseType; } export enum ContentType { Json = "application/json", FormData = "multipart/form-data", UrlEncoded = "application/x-www-form-urlencoded", Text = "text/plain", } export interface LoadingInstance { target: any count: number } const pendingMap = new Map() const loadingInstance: LoadingInstance = { target: null, count: 0, } export class HttpClient { public instance: AxiosInstance; private securityData: SecurityDataType | null = null; private securityWorker?: ApiConfig["securityWorker"]; private secure?: boolean; private format?: ResponseType; constructor({ securityWorker, secure, format, ...axiosConfig }: ApiConfig = {}) { this.instance = axios.create({ ...axiosConfig, timeout: 60000, baseURL: axiosConfig.baseURL || import.meta.env.VITE_API_URL }) this.secure = secure; this.format = format; this.securityWorker = securityWorker; } public setSecurityData = (data: SecurityDataType | null) => { this.securityData = data } protected mergeRequestParams(params1: AxiosRequestConfig, params2?: AxiosRequestConfig): AxiosRequestConfig { const method = params1.method || (params2 && params2.method) return { ...this.instance.defaults, ...params1, ...(params2 || {}), headers: { ...((method && this.instance.defaults.headers[method.toLowerCase() as keyof HeadersDefaults]) || {}), ...(params1.headers || {}), ...((params2 && params2.headers) || {}), } as RawAxiosRequestHeaders, }; } protected stringifyFormItem(formItem: unknown) { if (typeof formItem === "object" && formItem !== null) { return JSON.stringify(formItem); } else { return `${formItem}`; } } protected createFormData(input: Record): FormData { return Object.keys(input || {}).reduce((formData, key) => { const property = input[key]; const propertyContent: any[] = (property instanceof Array) ? property : [property] for (const formItem of propertyContent) { const isFileType = formItem instanceof Blob || formItem instanceof File; formData.append( key, isFileType ? formItem : this.stringifyFormItem(formItem) ); } return formData; }, new FormData()); } /** * 错误处理 * @param {*} error */ protected errorHandle(error: any) { if (!error) { return } if (axios.isCancel(error)) return console.error('请求重复已被自动取消:' + error.message) let message = '' if (error.response) { switch (error.response.status) { case 302: message = '接口重定向' break case 400: message = '参数不正确' break case 401: message = '您还没有登录' break case 403: message = '您没有权限操作' break case 404: message = '请求地址出错:' + error.response.config.url break case 408: message = '请求超时' break case 409: message = '系统已存在相同数据' break case 500: message = '服务器内部错误' break case 501: message = '服务未实现' break case 502: message = '网关错误' break case 503: message = '服务不可用' break case 504: message = '服务暂时无法访问,请稍后再试' break case 505: message = 'HTTP版本不受支持' break default: message = '异常问题,请联系网站管理员' break } } if (error.message.includes('timeout')) message = '请求超时' if (error.message.includes('Network')) message = window.navigator.onLine ? '服务端异常' : '您已断网' if (message) { ElMessage.error({ message, grouping: true }) } } /** * 刷新token * @param {*} config */ protected async refreshToken(config: any) { const storesUseUserInfo = useUserInfo() const { userInfos } = storeToRefs(storesUseUserInfo) const token = userInfos.value.token if (!token) { storesUseUserInfo.clear() return Promise.reject(config) } if (window.tokenRefreshing) { window.requests = window.requests ? window.requests : [] return new Promise((resolve) => { window.requests.push(() => { resolve(this.instance(config)) }) }) } window.tokenRefreshing = true return this.request({ path: `/api/admin/auth/refresh`, method: 'GET', secure: true, format: 'json', login: false, query: { token: token, }, }) .then((res) => { if (res?.success) { const token = res.data.token storesUseUserInfo.setToken(token) if (window.requests?.length > 0) { window.requests.forEach((apiRequest) => apiRequest()) window.requests = [] } return this.instance(config) } else { storesUseUserInfo.clear() return Promise.reject(res) } }) .catch((error) => { storesUseUserInfo.clear() return Promise.reject(error) }) .finally(() => { window.tokenRefreshing = false }) } /** * 储存每个请求的唯一cancel回调, 以此为标识 */ protected addPending(config: AxiosRequestConfig) { const pendingKey = this.getPendingKey(config) config.cancelToken = config.cancelToken || new axios.CancelToken((cancel) => { if (!pendingMap.has(pendingKey)) { pendingMap.set(pendingKey, cancel) } }) } /** * 删除重复的请求 */ protected removePending(config: AxiosRequestConfig) { const pendingKey = this.getPendingKey(config) if (pendingMap.has(pendingKey)) { const cancelToken = pendingMap.get(pendingKey) cancelToken(pendingKey) pendingMap.delete(pendingKey) } } /** * 生成每个请求的唯一key */ protected getPendingKey(config: AxiosRequestConfig) { let { data, headers } = config headers = headers as RawAxiosRequestHeaders const { url, method, params } = config if (typeof data === 'string') data = JSON.parse(data) return [url, method, headers && headers.Authorization ? headers.Authorization : '', JSON.stringify(params), JSON.stringify(data)].join('&') } /** * 关闭Loading层实例 */ protected closeLoading(loading: boolean = false) { if (loading && loadingInstance.count > 0) loadingInstance.count-- if (loadingInstance.count === 0) { loadingInstance.target.close() loadingInstance.target = null } } public request = async ({ secure, path, type, query, format, body, showErrorMessage = true, showSuccessMessage = false, login = true, loading = false, loadingOptions = {}, cancelRepeatRequest = false, ...params <% if (config.unwrapResponseData) { %> }: FullRequestParams): Promise => { <% } else { %> }: FullRequestParams): Promise> => { <% } %> const secureParams = ((typeof secure === 'boolean' ? secure : this.secure) && this.securityWorker && (await this.securityWorker(this.securityData))) || {}; const requestParams = this.mergeRequestParams(params, secureParams); const responseFormat = (format || this.format) || undefined; if (type === ContentType.FormData && body && body !== null && typeof body === "object") { body = this.createFormData(body as Record); } if (type === ContentType.Text && body && body !== null && typeof body !== "string") { body = JSON.stringify(body); } // 请求拦截 this.instance.interceptors.request.use( (config) => { this.removePending(config) cancelRepeatRequest && this.addPending(config) if (loading) { loadingInstance.count++ if (loadingInstance.count === 1) { loadingInstance.target = ElLoading.service(loadingOptions) } } const { userInfos } = storeToRefs(useUserInfo()) const accessToken = userInfos.value.token config.headers!['Authorization'] = `Bearer ${accessToken}` return config }, (error) => { return Promise.reject(error) } ) // 响应拦截 this.instance.interceptors.response.use( (res) => { this.removePending(res.config) loading && this.closeLoading(loading) const data = res.data if (data.success) { if (showSuccessMessage) { ElMessage.success({ message: data.msg ? data.msg : '操作成功', grouping: true }) } } else { if (showErrorMessage) { ElMessage.error({ message: data.msg ? data.msg : '操作失败', grouping: true }) } // return Promise.reject(res) } return res }, async (error) => { error.config && this.removePending(error.config) loading && this.closeLoading(loading) //刷新token if (login && error?.response?.status === 401) { return this.refreshToken(error.config) } //错误处理 if (showErrorMessage) { this.errorHandle(error) } return Promise.reject(error) } ) return this.instance.request({ ...requestParams, headers: { ...(requestParams.headers || {}), ...(type && type !== ContentType.FormData ? { "Content-Type": type } : {}), } as RawAxiosRequestHeaders, params: query, responseType: responseFormat, data: body, url: path, <% if (config.unwrapResponseData) { %> }).then(response => response.data); <% } else { %> }); <% } %> }; }