/* eslint-disable camelcase */ /* eslint-disable @typescript-eslint/no-explicit-any */ import qs from "qs"; import { getConfigVal, hostAppInfo } from "./config"; import { strRandom } from "./fns"; import axios, { AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig, } from "axios"; import { err404, err500, errReq } from "./error-notification"; import { notification } from "ant-design-vue"; // import { useUserStore } from "@/store/modules/user"; import { Base64 } from "js-base64"; export enum ProxyMode { JRPC = "jrpc", HTTP = "http", DIRECT = "direct", } export interface TableData { pageSize: number; currentPage: number; records: any[]; total: number; } export interface HcResponse { success: boolean; // 是否成功 data: T; // 成功时返回的数据,失败时返回null msg: string; // 失败提示 code: number | string; // v1中使用code=0判断是否成功;v2时,code表示失败时返回的错误码 } export interface PostParams { distinctRequestId: string; timestamp: number; env: string; lang: string; origin_system: string; ticket?: string; /** proxyMode: jrpc:为默认值,可不传,旧网关协议走jrpc;http:新网关协议;direct:直接转发模式。具体见:https://o15vj1m4ie.feishu.cn/wiki/wikcno4EXGsXq1LhUFJI9vCndBf */ proxyMode?: ProxyMode; } export type CommonObject = Record; /** * 给url增加前缀域名 * @param baseURL * @returns */ export const addUrlPrefix = (baseURL: string) => { // test不同环境支持 const baseMatch = /\.(test[1-20]?)\./.exec(baseURL); if (baseMatch !== null && baseMatch.length > 1) { const urlMatch = /\.(test[1-20]?)\./.exec(window.location.origin); if (urlMatch !== null && urlMatch.length > 1) { if (baseMatch[1] !== urlMatch[1]) { baseURL = baseURL.replace(baseMatch[1], urlMatch[1]); } } } return baseURL; }; /** * 生成url,将参数拼接到url上 * @param url * @returns */ export const generateUrl = (url = "", params: CommonObject = {}) => { const now = new Date(); const dataWrapper: PostParams = { distinctRequestId: strRandom(32), timestamp: Math.ceil(now.valueOf() / 1000), env: getConfigVal("env"), lang: "zh_cn", origin_system: hostAppInfo.name || getConfigVal("appName"), ...params, }; return `${url}?${qs.stringify(dataWrapper) as string}`; }; const toTrim = (value: T): T => { if (typeof value === "string") { return value.trim() as T; } else { return value; } }; const deepTrim = (obj: T): T => { // 判断传入的值是否为一个数组或者对象 if ( toString.call(obj) !== "[object Array]" && toString.call(obj) !== "[object Object]" ) { return toTrim(obj); } // 判断对象是类 const newObj: any = Array.isArray(obj) ? [] : {}; for (const item in obj) { if (typeof obj[item] === "object") { newObj[item] = deepTrim(obj[item]); } else { newObj[item] = toTrim(obj[item]); } } return newObj as T; }; /** * 请求拦截报错 * @param err * @returns */ const handleRequestError = async (err: Error) => { return Promise.reject(err); }; /** * 响应成功的拦截处理 * @param response * @param isSuccessCb * @returns */ const handleResponseFulfilled = ( response: AxiosResponse, isSuccessCb: (resp: HcResponse) => boolean ) => { if (response.config.responseType === "blob") { return response; } const dataAxios = response.data as HcResponse; // 这个状态码是和后端约定的 const { code, msg, data } = dataAxios; if (isSuccessCb(dataAxios)) { // // 如果没有 code 代表这不是项目后端开发的接口 比如可能是 D2Admin 请求最新版本 // const isArrayOfObjects = isArray(data) && (data as any[]).length > 0 && (data as any[]).reduce((a, b) => a && isObject(b), true) // const canToTs = (isObject(data) && !isArray(data)) || isArrayOfObjects // if (settingStore().show.tsLog && canToTs) { // // 只在开发环境下打印,且返回值是对象或者数组&&长度大于0 // JsonToTS(data).forEach(typeInterface => { // const apiPaths = response.config.url?.split('?')[0]?.split('/') || null // console.info( // `%c TS %c ${apiPaths?.[apiPaths.length - 1] ?? '无接口'} \n ${typeInterface}`, // 'background:#41b883 ; padding: 1px; border-radius: 3px; color: #fff', // 'background:transparent', // ) // }) // } return data; } else if (code === "ticketExpire") { // token 过期 localStorage.removeItem("router"); // const userStore = useUserStore(); // userStore.removeAllCookies(); window.location.href = `${getConfigVal( "server" )}/redirect/sso?service=${getConfigVal("appName")}&env=${getConfigVal( "env" )}`; } else { notification.warning({ key: "error", message: "接口报错", description: `检测到请求错误:${msg}`, }); throw new Error(msg); } }; /** * 响应失败的拦截处理 * @param error * @returns */ const handleResponseRejected = async (error: any) => { console.error("error :>> ", error); if (!error.response) { errReq(error.message as string); return Promise.reject(error); } switch (error.response.status) { case 500: err500(error.message as string); break; case 404: err404(error.message as string); break; } return Promise.reject(error); }; // 以下是request V2版本的代码,适配新的网关协议,proxyMode=http const handleRequestConfigV2 = (config: InternalAxiosRequestConfig) => { if ((config.method === "post" || config.method === "put") && config.headers) { config.headers["Content-Type"] = "application/json"; } if (config.url?.includes("/m1/3423541-0-default")) { config.baseURL = "http://127.0.0.1:4523"; } let { data } = config; if (!data) { data = {}; } if (!["[object Object]", "[object Array]"].includes(toString.call(data))) { throw new Error("请求参数必须是一个对象"); } let query = { // ticket: useUserStore().getTicket, ticket: hostAppInfo.ticket, proxyMode: ProxyMode.HTTP, // 网关协议走http,V2 appcode: 1, cloned: 1, }; if (data) { const dataWithGwQuery = data as { _gwQuery?: Record }; if (dataWithGwQuery._gwQuery) { query = { ...query, ...dataWithGwQuery._gwQuery, }; delete dataWithGwQuery._gwQuery; } } config.url = generateUrl(config.url, query); config.data = deepTrim(data); return config; }; const instanceV2 = axios.create({ baseURL: addUrlPrefix(getConfigVal("server")), timeout: 5 * 1000, }); // 请求拦截 instanceV2.interceptors.request.use(handleRequestConfigV2, handleRequestError); // 响应拦截 instanceV2.interceptors.response.use( (response) => handleResponseFulfilled(response, (data: HcResponse) => { return data.success; }) as AxiosResponse, handleResponseRejected ); /** * request V2版本,适配新的网关协议,proxyMode=http */ export const requestV2 = async ( url: string, data?: object, config?: AxiosRequestConfig ): Promise => instanceV2.post(url, data, config); // old----------------------------------------------------- const handleParams = (data: unknown[] | undefined = []) => { let params = Base64.encode( JSON.stringify([...data], (_, value: unknown) => typeof value === "undefined" ? null : value ) ); params = encodeURIComponent(params); const ticket = hostAppInfo.ticket return qs.stringify({ params, ticket }) as string; }; // 统一处理appCode const addAppInfo = (dataOne?: unknown) => { if (typeof dataOne === "object" || typeof dataOne === "undefined") { return { ...dataOne, appCode: 1, appcode: 1, cloned: 1 }; } else { return dataOne; } }; /** * 处理请求拦截config * @param config * @returns */ const handleRequestConfig = ( config: InternalAxiosRequestConfig ) => { if ((config.method === "post" || config.method === "put") && config.headers) { config.headers["Content-Type"] = "application/x-www-form-urlencoded"; } const { url } = config; let { data } = config; // 去除请求参数中字符串的前后空格 data = deepTrim(data) as unknown[]; data[0] = addAppInfo(data[0]); config.url = generateUrl(url); config.data = handleParams(data); return config; }; const instance = axios.create({ baseURL: addUrlPrefix(getConfigVal("server")), timeout: 5 * 1000, }); // 请求拦截 instance.interceptors.request.use(handleRequestConfig, handleRequestError); // 响应拦截 instance.interceptors.response.use( (response) => handleResponseFulfilled(response, (data: HcResponse) => { const { success, code } = data; return code === "0" || success || success === undefined; }) as AxiosResponse, handleResponseRejected ); /** * request V1版本,适配旧的网关协议,proxyMode=jrpc */ export const request = async (url: string, ...args: unknown[]): Promise => instance.post(url, args);