request.ts 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
  2. import { ElMessage } from 'element-plus';
  3. import { Local, } from '/@/utils/storage';
  4. import {clearAccessAfterReload} from "/@/utils/axios-utils";
  5. import { getApiPublicBase } from '/@/utils/api-public-base';
  6. // 定义请求中止控制器映射表
  7. const abortControllerMap: Map<string, AbortController> = new Map();
  8. // 配置新建一个 axios 实例
  9. export const service = axios.create({
  10. baseURL: getApiPublicBase() as any,
  11. timeout: 50000,
  12. //headers: { 'Content-Type': 'application/json' }, 这个会导致生成代码的上传文件 file为空
  13. });
  14. // token 键定义
  15. export const accessTokenKey = 'access-token';
  16. export const refreshAccessTokenKey = `x-${accessTokenKey}`;
  17. // 获取 token
  18. export const getToken = () => {
  19. return Local.get(accessTokenKey);
  20. };
  21. function stringifyErrors(errors: unknown): string | undefined {
  22. if (!errors) return undefined;
  23. if (typeof errors === 'string') return errors;
  24. if (Array.isArray(errors)) {
  25. const parts = errors.map((item) => stringifyErrors(item)).filter(Boolean);
  26. return parts.length ? parts.join(';') : undefined;
  27. }
  28. if (typeof errors === 'object') {
  29. const parts = Object.entries(errors as Record<string, unknown>)
  30. .flatMap(([key, value]) => {
  31. const text = stringifyErrors(value);
  32. return text ? `${key}: ${text}` : [];
  33. });
  34. return parts.length ? parts.join(';') : undefined;
  35. }
  36. return undefined;
  37. }
  38. function extractResponseMessage(payload: unknown): string | undefined {
  39. if (!payload) return undefined;
  40. if (typeof payload === 'string') return payload.trim() || undefined;
  41. if (typeof payload !== 'object') return undefined;
  42. const data = payload as Record<string, unknown>;
  43. return (
  44. stringifyErrors(data.errors) ||
  45. stringifyErrors(data.message) ||
  46. stringifyErrors(data.title) ||
  47. stringifyErrors(data.error) ||
  48. stringifyErrors(data.detail) ||
  49. // B3 修复:ASP.NET Core [ApiController] 默认 400 返回顶层 ModelState
  50. // 字典格式(如 {"CompanyRefId":["公司不能为空"]}),上述 5 个业务字段
  51. // 均未命中时,直接把整个 payload 作为 ModelState 展开为"字段: 消息"文本,
  52. // 避免降级到 axios 的 "Request failed with status code 400" 技术文案。
  53. stringifyErrors(data)
  54. );
  55. }
  56. function extractRequestErrorMessage(error: any): string {
  57. return (
  58. extractResponseMessage(error?.response?.data) ||
  59. (typeof error?.message === 'string' && error.message.trim() ? error.message.trim() : undefined) ||
  60. (typeof error?.response?.statusText === 'string' && error.response.statusText.trim() ? error.response.statusText.trim() : undefined) ||
  61. '请求失败'
  62. );
  63. }
  64. // axios 默认实例
  65. export const axiosInstance: AxiosInstance = axios;
  66. // 添加请求拦截器
  67. service.interceptors.request.use(
  68. (config) => {
  69. // // 在发送请求之前做些什么 token
  70. // if (Session.get('token')) {
  71. // (<any>config.headers).common['Authorization'] = `${Session.get('token')}`;
  72. // }
  73. // 记录中止控制信息
  74. const controller = new AbortController();
  75. config.signal = controller.signal;
  76. const url = config.url || '';
  77. abortControllerMap.set(url, controller);
  78. // 获取本地的 token
  79. const accessToken = Local.get(accessTokenKey);
  80. if (accessToken) {
  81. // 将 token 添加到请求报文头中
  82. config.headers!['Authorization'] = `Bearer ${accessToken}`;
  83. // 判断 accessToken 是否过期
  84. const jwt: any = decryptJWT(accessToken);
  85. const exp = getJWTDate(jwt.exp as number);
  86. // token 已经过期
  87. if (new Date() >= exp) {
  88. // 获取刷新 token
  89. const refreshAccessToken = Local.get(refreshAccessTokenKey);
  90. // 携带刷新 token
  91. if (refreshAccessToken) {
  92. config.headers!['X-Authorization'] = `Bearer ${refreshAccessToken}`;
  93. }
  94. }
  95. // debugger
  96. // get请求映射params参数
  97. if (config.method?.toLowerCase() === 'get' && config.data) {
  98. let url = config.url + '?' + tansParams(config.data);
  99. url = url.slice(0, -1);
  100. config.data = {};
  101. config.url = url;
  102. }
  103. }
  104. return config;
  105. },
  106. (error) => {
  107. // 对请求错误做些什么
  108. return Promise.reject(error);
  109. }
  110. );
  111. // 添加响应拦截器
  112. service.interceptors.response.use(
  113. (res) => {
  114. // 请求结束后清除中止控制项
  115. const url = res.config.url || '';
  116. abortControllerMap.delete(url);
  117. // 获取状态码和返回数据
  118. var status = res.status;
  119. var serve = res.data;
  120. // 处理 401
  121. if (status === 401) {
  122. clearAccessAfterReload();
  123. }
  124. // 处理未进行规范化处理的
  125. if (status >= 400) {
  126. throw new Error(res.statusText || 'Request Error.');
  127. }
  128. // 处理规范化结果错误
  129. if (serve && serve.hasOwnProperty('errors') && serve.errors) {
  130. throw new Error(JSON.stringify(serve.errors || 'Request Error.'));
  131. }
  132. // 读取响应报文头 token 信息
  133. var accessToken = res.headers[accessTokenKey];
  134. var refreshAccessToken = res.headers[refreshAccessTokenKey];
  135. // 判断是否是无效 token
  136. if (accessToken === 'invalid_token') {
  137. clearAccessAfterReload();
  138. }
  139. // 判断是否存在刷新 token,如果存在则存储在本地, 并重新加载页面
  140. else if (refreshAccessToken && accessToken) {
  141. Local.set(accessTokenKey, accessToken);
  142. Local.set(refreshAccessTokenKey, refreshAccessToken);
  143. }
  144. // 响应拦截及自定义处理
  145. if (serve.code === 401) {
  146. clearAccessAfterReload();
  147. } else if (serve.code === undefined) {
  148. return Promise.resolve(res);
  149. } else if (serve.code !== 200) {
  150. var message;
  151. // 判断 serve.message 是否为对象
  152. if (serve.message && typeof serve.message == 'object') {
  153. message = JSON.stringify(serve.message);
  154. } else {
  155. message = serve.message;
  156. }
  157. ElMessage({
  158. dangerouslyUseHTMLString: true,
  159. message: message,
  160. type: 'error',
  161. });
  162. throw new Error(message);
  163. }
  164. return res;
  165. },
  166. (error) => {
  167. const silentError = Boolean(
  168. error?.config?.headers?.['X-Silent-Error'] ||
  169. error?.config?.headers?.['x-silent-error']
  170. );
  171. const message = extractRequestErrorMessage(error);
  172. // 处理响应错误
  173. if (error.response) {
  174. if (error.response.status === 401) {
  175. clearAccessAfterReload();
  176. }
  177. }
  178. if (silentError) {
  179. return Promise.reject(error);
  180. }
  181. // 对响应错误做点什么
  182. if (typeof error?.message === 'string' && error.message.indexOf('timeout') != -1) {
  183. ElMessage.error('网络超时');
  184. } else if (error.message == 'Network Error') {
  185. ElMessage.error('网络连接错误');
  186. } else {
  187. ElMessage.error(message || '接口路径找不到');
  188. }
  189. return Promise.reject(error);
  190. }
  191. );
  192. // 取消指定请求
  193. export const cancelRequest = (url: string | string[]) => {
  194. const urlList = Array.isArray(url) ? url : [url];
  195. for (const _url of urlList) {
  196. abortControllerMap.get(_url)?.abort();
  197. abortControllerMap.delete(_url);
  198. }
  199. }
  200. // 取消全部请求
  201. export const cancelAllRequest = () => {
  202. for (const [_, controller] of abortControllerMap) {
  203. controller.abort();
  204. }
  205. abortControllerMap.clear();
  206. }
  207. /**
  208. * 参数处理
  209. * @param {*} params 参数
  210. */
  211. export function tansParams(params: any) {
  212. let result = '';
  213. for (const propName of Object.keys(params)) {
  214. const value = params[propName];
  215. var part = encodeURIComponent(propName) + '=';
  216. if (value !== null && value !== '' && typeof value !== 'undefined') {
  217. if (typeof value === 'object') {
  218. for (const key of Object.keys(value)) {
  219. if (value[key] !== null && value[key] !== '' && typeof value[key] !== 'undefined') {
  220. let params = propName + '[' + key + ']';
  221. var subPart = encodeURIComponent(params) + '=';
  222. result += subPart + encodeURIComponent(value[key]) + '&';
  223. }
  224. }
  225. } else {
  226. result += part + encodeURIComponent(value) + '&';
  227. }
  228. }
  229. }
  230. return result;
  231. }
  232. /**
  233. * 解密 JWT token 的信息
  234. * @param token jwt token 字符串
  235. * @returns <any>object
  236. */
  237. export function decryptJWT(token: string): any {
  238. token = token.replace(/_/g, '/').replace(/-/g, '+');
  239. var json = decodeURIComponent(escape(window.atob(token.split('.')[1])));
  240. return JSON.parse(json);
  241. }
  242. /**
  243. * 将 JWT 时间戳转换成 Date
  244. * @description 主要针对 `exp`,`iat`,`nbf`
  245. * @param timestamp 时间戳
  246. * @returns Date 对象
  247. */
  248. export function getJWTDate(timestamp: number): Date {
  249. return new Date(timestamp * 1000);
  250. }
  251. /**
  252. * Ajax请求,如果成功返回result字段,如果不成功提示错误信息
  253. * @description Ajax请求
  254. * @config AxiosRequestConfig 请求参数
  255. * @returns 返回对象
  256. */
  257. export function request2(config: AxiosRequestConfig<any>): any {
  258. return new Promise((resolve, reject) => {
  259. service(config)
  260. .then((res) => {
  261. if (res.data.type == 'success') {
  262. resolve(res.data.result);
  263. } else {
  264. console.log('res', res);
  265. ElMessage.success(res.data.message);
  266. }
  267. })
  268. .catch((res) => {
  269. console.log('res', res);
  270. ElMessage.error(res);
  271. reject(res);
  272. });
  273. });
  274. }
  275. /**
  276. * 使用新的令牌登录
  277. * @param accessInfo
  278. */
  279. export function reLoadLoginAccessToken(accessInfo: any) {
  280. if (accessInfo?.accessToken && accessInfo?.refreshToken) {
  281. Local.set(accessTokenKey, accessInfo.accessToken);
  282. Local.set(refreshAccessTokenKey, accessInfo.refreshToken);
  283. setTimeout(() => location.href = "/", 300);
  284. }
  285. }
  286. // 导出 axios 实例
  287. export default service;