import { getToken, removeToken } from '@/plugins/auth-token'; import { sessionGet, sessionGetJSON, sessionSetJSON, } from '@/utils/cache-session-utils'; import { localGet } from '@/utils/cache-local-utils'; import { TOKEN_KEY, TOKEN_KEY_PREFIX } from '@/constants/token-constants'; import { CACHE_LOCAL_I18N, CACHE_SESSION_FATCH, } from '@/constants/cache-keys-constants'; import { APP_REQUEST_HEADER_CODE, APP_REQUEST_HEADER_VERSION, } from '@/constants/app-constants'; import { RESULT_CODE_ERROR, RESULT_CODE_SUCCESS, RESULT_MSG_ERROR, RESULT_MSG_NOT_TYPE, RESULT_MSG_SERVER_ERROR, RESULT_MSG_SUCCESS, RESULT_MSG_TIMEOUT, RESULT_MSG_URL_NOTFOUND, RESULT_MSG_URL_RESUBMIT, } from '@/constants/result-constants'; /**响应结果类型 */ export type ResultType = { /**响应码 */ code: number | 1 | 0; /**信息 */ msg: string; /**数据 */ data?: any; /**未知属性 */ [key: string]: any; }; /**防止重复提交类型 */ type RepeatSubmitType = { /**请求地址 */ url: string; /**请求数据 */ data: string; /**请求时间 */ time: number; }; /**请求参数类型 */ type OptionsType = { /**请求的根域名地址-不带/后缀 */ baseUrl?: string; /**超时时间,毫秒 */ timeout?: number; /**请求地址 */ url: string; /**请求方法 */ method: 'get' | 'post' | 'put' | 'delete' | 'PATCH'; /**请求头 */ headers?: HeadersInit; /**地址栏参数 */ params?: Record; /**发送数据 */ data?: Record | FormData | object; /**请求数据类型 */ dataType?: 'form-data' | 'json'; /**响应数据类型 */ responseType?: 'text' | 'json' | 'blob' | 'arrayBuffer'; /**请求缓存策略 */ cache?: RequestCache; /**请求的凭证,如 omit、same-origin、include */ credentials?: RequestCredentials; /**请求体 */ body?: BodyInit; /**防止数据重复提交 */ repeatSubmit?: boolean; /**携带授权Token请求头 */ whithToken?: boolean; /**中断控制信号,timeout不会生效 */ signal?: AbortSignal; }; // 多语言处理 export const language = localGet(CACHE_LOCAL_I18N) || 'en_US'; // 兼容旧前端可改配置文件 const baseUrl = import.meta.env.PROD ? sessionGet('baseUrl') || import.meta.env.VITE_API_BASE_URL : import.meta.env.VITE_API_BASE_URL; /**默认请求参数 */ const FATCH_OPTIONS: OptionsType = { baseUrl: baseUrl, timeout: 10_000, url: '', method: 'get', headers: { [APP_REQUEST_HEADER_CODE]: import.meta.env.VITE_APP_CODE, [APP_REQUEST_HEADER_VERSION]: import.meta.env.VITE_APP_VERSION, }, dataType: 'json', responseType: 'json', cache: 'no-cache', credentials: undefined, repeatSubmit: true, whithToken: true, signal: undefined, }; /**请求前的拦截 */ function beforeRequest(options: OptionsType): OptionsType | Promise { options.headers = Object.assign({}, options.headers); //console.log('请求前的拦截', options); // 给发送数据类型设置请求头 if (options.dataType === 'json') { Reflect.set( options.headers, 'content-type', 'application/json;charset=utf-8' ); } // 客户端接受语言 Reflect.set(options.headers, 'Accept-Language', `${language};q=0.9`); // 是否需要设置 token const token = getToken(); if (options.whithToken && token) { Reflect.set(options.headers, TOKEN_KEY, TOKEN_KEY_PREFIX + token); } // 是否需要防止数据重复提交 if ( options.repeatSubmit && options.dataType === 'json' && ['post', 'put'].includes(options.method) ) { const requestObj: RepeatSubmitType = { url: options.url, data: JSON.stringify(options.data) || '', time: Date.now(), }; const sessionObj: RepeatSubmitType = sessionGetJSON(CACHE_SESSION_FATCH); if (sessionObj) { const { url, data, time } = sessionObj; const interval = 3000; // 间隔时间(ms),小于此时间视为重复提交 if ( requestObj.url === url && requestObj.data === data && requestObj.time - time < interval ) { const message = RESULT_MSG_URL_RESUBMIT[language]; return Promise.resolve({ code: RESULT_CODE_ERROR, msg: message, }); } else { sessionSetJSON(CACHE_SESSION_FATCH, requestObj); } } else { sessionSetJSON(CACHE_SESSION_FATCH, requestObj); } } // 请求拼接地址栏参数 if (options.params) { let paramStr = ''; const params = options.params; for (const key in params) { const value = params[key]; // 空字符或未定义的值不作为参数发送 if (value === '' || value === undefined) continue; paramStr += `&${encodeURIComponent(key)}=${encodeURIComponent(value)}`; } if (paramStr && paramStr.startsWith('&')) { options.url = `${options.url}?${paramStr.substring(1)}`; } } // 非get参数提交 if (options.data instanceof FormData) { options.body = options.data; } else { options.body = JSON.stringify(options.data); } return options; } /**请求后的拦截 */ function interceptorResponse(res: ResultType): ResultType | Promise { // console.log('请求后的拦截', res); // 登录失效时,移除授权令牌并重新刷新页面 if (res.code === 401) { removeToken(); window.location.reload(); } // 风格处理 if (!Reflect.has(res, 'code')) { return Promise.resolve({ code: RESULT_CODE_SUCCESS, msg: RESULT_MSG_SUCCESS[language], data: res, }); } if (Reflect.has(res, 'error')) { return Promise.resolve({ code: RESULT_CODE_ERROR, msg: RESULT_MSG_ERROR[language], data: res.error, }); } return res; } /** * 请求http * * @param options 请求参数 * * responseType改变响应结果类型 * @returns 返回 Promise */ export async function request(options: OptionsType): Promise { options = Object.assign({}, FATCH_OPTIONS, options); let timeoutId: any = 0; // 请求超时控制请求终止 if (!options.signal) { const controller = new AbortController(); const { signal } = controller; options.signal = signal; timeoutId = setTimeout(() => { controller.abort(); // 终止请求 }, options.timeout); } // 检查请求拦截 const beforeReq = beforeRequest(options); if (beforeReq instanceof Promise) { return await beforeReq; } options = beforeReq; // 判断用户传递的URL是否http或/开头 if (!options.url.startsWith('http')) { const uri = options.url.startsWith('/') ? options.url : `/${options.url}`; options.url = options.baseUrl + uri; } try { const res = await fetch(options.url, options); // console.log('请求结果:', res); // 状态码拦截处理 const reqNot = stateCode(res); if (reqNot != false) { return reqNot; } // 根据响应数据类型返回 switch (options.responseType) { case 'text': // 文本数据 const str = await res.text(); return { code: 1, msg: str, }; case 'json': // json格式数据 const result = await res.json(); // 请求后的拦截 const beforeRes = interceptorResponse(result); if (beforeRes instanceof Promise) { return await beforeRes; } return result; case 'blob': // 二进制数据则直接返回 case 'arrayBuffer': const contentType = res.headers.get('content-type') || ''; if (contentType.startsWith('application/json')) { const result = await res.json(); return result as ResultType; } const data = options.responseType === 'blob' ? await res.blob() : await res.arrayBuffer(); return { code: RESULT_CODE_SUCCESS, msg: RESULT_MSG_SUCCESS[language], data: data, status: res.status, headers: res.headers, }; default: return { code: RESULT_CODE_ERROR, msg: RESULT_MSG_NOT_TYPE[language], }; } } catch (error: any) { // 请求被终止时 if (error.name === 'AbortError') { return { code: RESULT_CODE_ERROR, msg: RESULT_MSG_TIMEOUT[language], }; } throw error; } finally { clearTimeout(timeoutId); // 请求成功,清除超时计时器 } } /** * 判断状态码处理结果信息(不可处理) * @param res 请求结果 * @returns */ function stateCode(res: Response) { // 网络异常 if (res.status === 500) { return { code: RESULT_CODE_ERROR, msg: RESULT_MSG_SERVER_ERROR[language], }; } // 上传文件成功无内容返回 if (res.status === 204 || res.statusText === 'No Content') { return { code: RESULT_CODE_SUCCESS, msg: RESULT_MSG_SUCCESS[language], }; } // 地址找不到 if (res.status === 404 || res.status === 405) { return { code: RESULT_CODE_ERROR, msg: RESULT_MSG_URL_NOTFOUND[language], }; } // 身份授权 if (res.status === 401) { removeToken(); window.location.reload(); } return false; }