import type { Result } from '@/types'; import axios, { isAxiosError, type AxiosError, type AxiosRequestConfig, type AxiosResponse, type CreateAxiosDefaults, type InternalAxiosRequestConfig } from 'axios'; export type HttpResponse = [err: AxiosError | null, data: T | null, resp: Result | null]; export interface HttpRequestOptions extends CreateAxiosDefaults { requestInterceptor?: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig | Promise; responseInterceptor?: (resp: AxiosResponse) => AxiosResponse | Promise; responseErrorInterceptor?: (error: any) => any; } export const createHttpClient = (config?: HttpRequestOptions) => { const defaultRequestInterceptor = (config: InternalAxiosRequestConfig) => config; const defaultResponseInterceptor = (response: AxiosResponse) => response; const defaultResponseErrorInterceptor = (error: any) => { if (isAxiosError(error)) { if (error.status === 401) { // 处理 401 错误 } if (error.status === 404) { // 处理 404 错误 } } return Promise.reject(error); }; const requestInterceptor = config?.requestInterceptor ?? defaultRequestInterceptor; const responseInterceptor = config?.responseInterceptor ?? defaultResponseInterceptor; const responseErrorInterceptor = config?.responseErrorInterceptor ?? defaultResponseErrorInterceptor; const instance = axios.create(config); instance.interceptors.request.use(requestInterceptor); instance.interceptors.response.use(responseInterceptor, responseErrorInterceptor); const httpGet = (url: string, options?: AxiosRequestConfig & { retRaw?: boolean }): Promise> => { const { retRaw, ...reqConfig } = options ?? {}; return new Promise((resolve) => { instance .get(url, { ...reqConfig, }) .then((res) => { if (retRaw) { resolve([null, res.data as T, null]); } else { const resData = res.data as Result; resolve([null, resData.data, resData]); } }) .catch((err) => { resolve([err as AxiosError, null, null]); }); }); }; const httpPost = (url: string, data?: AxiosRequestConfig['data'], options?: Partial> & { retRaw?: boolean; upload?: boolean }): Promise> => { const { retRaw, upload, ...reqConfig } = options ?? {}; return new Promise((resolve) => { instance .post(url, data, { headers: { 'content-type': upload ? 'multipart/form-data' : 'application/json' }, ...reqConfig }) .then((res) => { const resData = res.data; if (retRaw) { resolve([null, resData as T, null]); } else { resolve([null, resData.data as T, resData as Result]); } }) .catch((err) => { resolve([err as AxiosError, null, null]); }); }); }; const httpPut = (url: string, data?: AxiosRequestConfig['data'], options?: Partial>): Promise> => { const reqConfig = options ?? {}; return new Promise((resolve) => { instance .put>(url, data, { ...reqConfig }) .then((res) => { resolve([null, res.data.data, res.data]); }) .catch((err) => { resolve([err as AxiosError, null, null]); }); }); }; const httpDelete = (url: string, idList: string[], options?: Partial>): Promise> => { const reqConfig = options ?? {}; return new Promise((resolve) => { instance .delete>(url, { ...reqConfig, data: idList }) .then((res) => { resolve([null, res.data.data, res.data]); }) .catch((err) => { resolve([err as AxiosError, null, null]); }); }); }; return { get clientInstance() { return instance; }, get: httpGet, post: httpPost, put: httpPut, delete: httpDelete, }; }; // 从响应中解析出数据 export const unwrapResponse = (resp: HttpResponse) => { const [err, data, result] = resp; // 如果 err 存在说明有 http 错误,那么直接抛出错误, // 如果没有 err,那么接下来判断是否存在业务错误。 // 如果 result 中 isSuccess 为 false,说明有业务错误, // 如果 result 不存在,说明 retRaw 为 true,是直接返回 data 的情况。 if (err) throw err; // 不能判断 !result 从而提前结束分支, // 因为当 retRaw 为 true 时,result 总是为 null。 if (result) { const { isSuccess, path, msg, errorMsg } = result; if (!isSuccess) throw new Error(`${path ? `${path}: ` : ''}${msg || errorMsg || '请求失败'}`); } // 不做严格判空,null == undefined -> true if (data == null) throw new Error('响应数据为空'); return data; }; // 针对没有数据的响应,直接判断是否存在错误 export const unwrapVoidResponse = (resp: HttpResponse) => { const [err, , result] = resp; if (err) throw err; if (result) { const { isSuccess, path, msg, errorMsg } = result; if (!isSuccess) throw new Error(`${path ? `${path}: ` : ''}${msg || errorMsg || '请求失败'}`); } }; // 从错误中解析出错误信息 export const parseErrorFeedback = (error: Error) => { // 当发生 http 错误时,unwrapResponse 会直接抛出错误, // 所以如果不是 AxiosError,说明是业务错误,直接返回错误信息。 if (!isAxiosError(error)) { return error.message; } // 如果是 AxiosError,说明是 http 错误,尝试获取 result, const result = error.response?.data; // 如果 result 不存在,返回 error 中原生的 code + message if (!result) { const { code, message } = error; return `${code ? `code: ${code}, ` : ''}${message ? `message: ${message}` : ''}`; } // 如果 result 存在,判断是否是字符串, // 如果是字符串,说明是一些奇怪的、直接返回的错误信息,直接返回; // 如果不是字符串,说明是 Result 类型,返回其中的 path + msg + errorMsg。 if (typeof result === 'string') { return result; } const { path, msg, errorMsg } = result as Result; return `${path ? `${path}: ` : ''}${msg || errorMsg || '请求失败'}`; };