Skip to content

Http 请求

用途

封装了 axios 提供大量功能, 如

自动处理响应内容
请求失败/成功弹窗
请求加密
响应解密
文件上传
请求取消
请求重试(需要自己启动)

文件说明

路径为@/src/utils/http/axios

│  │  ├─ http
│  │  │  └─ axios
│  │  │     ├─ Axios.ts 核心封装
│  │  │     ├─ axiosCancel.ts  请求取消
│  │  │     ├─ axiosRequestCancel.ts 请求取消-使用axios建议的的AbortController 已经删除 采用官方的
│  │  │     ├─ axiosRetry.ts 重试功能
│  │  │     ├─ axiosTransform.ts 转换器接口
│  │  │     ├─ checkStatus.ts 检查httpCode(非响应体内的code)
│  │  │     ├─ helper.ts 工具类
│  │  │     └─ index.ts 主要配置及转换器实现

使用

导入

ts
import { defHttp } from "/@/utils/http/axios";

可调用相关封装的方法get post delete ... 详细见Axios.ts

参数 defHttp.post 举例

ts
post<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
	return this.request({ ...config, method: 'POST' }, options);
}

config 就是AxiosRequestConfig, 跟正常使用 axios 一致 options 为框架新增的配置 定义文件路径根目录/types/axios.d.ts

主要功能

ts
export interface RequestOptions {
  // 请求失败弹窗类型
  errorMessageMode?: ErrorMessageMode;
  // 请求成功弹窗类型
  successMessageMode?: SuccessMessageMode;
  // 请求路径加上时间戳参数
  joinTime?: boolean;
  // 是否携带token
  withToken?: boolean;
  // 请求重试机制
  retryRequest?: RetryRequest;
  /** 是否加密请求参数  */
  encrypt?: boolean;
}

比如需要请求加密

ts
defHttp.post({ xxx }, { encrypt: true });

即可

errorMessageModesuccessMessageMode为请求成功/失败的弹窗类型

ts
export type ErrorMessageMode = "none" | "modal" | "message" | undefined;

none 或者不填写为不弹出任何提示 默认开启请求失败弹窗 类型为 message

触发条件

  • httpStatus != 200
  • 响应体对象 code != 200

请求失败会自动弹窗{code, msg, data}中的 msg

封装请求/响应 (源码解析??)

文件index.ts

请求前处理

具体看详细代码

ts
// 请求之前处理config
beforeRequestHook: (config, options) => {
	// ...参数封装
	// ...添加clientId
	// ...添加Language
	// ...请求加密
	// ...检查是否存在重复请求,若存在则取消已发的请求
	axiosCanceler.removePendingRequest(config);
	// 添加请求到pendingMap
	axiosCanceler.addPendingRequest(config);
	return config;
},

响应处理

ts
/**
   * @description: 处理响应数据。如果数据不是预期格式,可直接抛出错误
   */
transformResponseHook: (res: AxiosResponse<Result>, options: RequestOptions) => {
	// ...响应解密
	const { isTransformResponse, isReturnNativeResponse } = options;
	// 是否返回原生响应头 比如:需要获取响应头时使用该属性
	if (isReturnNativeResponse) {
		return res;
	}
	// 不进行任何处理,直接返回
	// 用于页面代码可能需要直接获取code,data,message这些信息时开启
	if (!isTransformResponse) {
		return res.data;
	}
	// 错误的时候返回
	const axiosResponseData = res.data;
	if (!axiosResponseData) {
		// return '[HTTP] Request has no return value';
		throw new Error(t('sys.api.apiRequestFailed'));
	}
	//  这里 code,result,message为 后台统一的字段,需要在 types.ts内修改为项目自己的接口返回格式
	const { code, msg, data } = axiosResponseData;

	// 这里逻辑可以根据项目进行修改
	const hasSuccess = Reflect.has(axiosResponseData, 'code') && code === ResultEnum.SUCCESS;
	if (hasSuccess) {
		let successMsg = msg;

		if (isNull(successMsg) || isUnDef(successMsg) || isEmpty(successMsg)) {
			successMsg = t(`sys.api.operationSuccess`);
		}

		if (options.successMessageMode === 'modal') {
			createSuccessModal({ title: t('sys.api.successTip'), content: successMsg });
		} else if (options.successMessageMode === 'message') {
			createMessage.success(successMsg);
		}
		// 在这里要做转换 ruoyi-plus没有采用严格的{code, msg, data}模式
		// 如果有data 直接返回data
		if (data) {
			return data;
		}
		// 没有data  将剩余的参数封装成data
		const transformData = {};
		Object.keys(axiosResponseData).forEach((key) => {
			if (key === 'code' || key === 'msg') {
				return;
			}
			transformData[key] = axiosResponseData[key];
		});
		return transformData;
	}

	// 在此处根据自己项目的实际情况对不同的code执行不同的操作
	// 如果不希望中断当前请求,请return数据,否则直接抛出异常即可
	let timeoutMsg = '';
	switch (code) {
		case ResultEnum.TIMEOUT:
			const _msg = '登录超时, 请重新登录';
			const userStore = useUserStoreWithOut();
			/**
			 * 需要在退出登录请求结束后才能取消其他请求
			 * 如果直接取消 退出登录的请求也会被取消
			 * 或者在白名单配置不取消的url(已配置)
			 */
			userStore.logout(true).finally(() => {
				/** 取消其他正在进行的请求 */
				axiosCanceler.removeAllPendingRequest();
				/** 弹窗提示 */
				createMessage.error(_msg);
			});
			// 不再执行下面逻辑
			return;
		default:
			if (msg) {
				timeoutMsg = msg;
			}
	}
	// ...
},

添加 token

这里只做一件事就是添加 token 注意 token 的 header 为Authorization

ts
/**
 * @description: 请求拦截器处理
 */
requestInterceptors: (config, options) => {
	// 请求之前处理config
	const token = getToken();
	if (token && (config as Recordable)?.requestOptions?.withToken !== false) {
		// jwt token
		(config as Recordable).headers.Authorization = options.authenticationScheme
			? `${options.authenticationScheme} ${token}`
			: token;
	}
	return config;
},

请求取消功能

axiosCancel.ts 是使用 cancelToken 实现的 axios 已经不推荐使用

axiosRequestCancel.ts 为推荐的使用 AbortController 实现

ps: 官方已经支持AbortController

主要解决重复请求问题

详见:

vue-axios登录失效后,阻止其他请求
https://blog.csdn.net/weixin_44171757/article/details/123086291

更新使用最新的AbortController  cancelToken已经被弃用
https://axios-http.com/zh/docs/cancellation

Alt text

全局开关

src\utils\http\axios\index.ts

ts
function createAxios(opt?: Partial<CreateAxiosOptions>) {
  return new VAxios(
    // 深度合并
    deepMerge(
      {...
        // 配置项,下面的选项都可以在独立的接口请求中覆盖
        requestOptions: {
			...
          // 忽略重复请求
          ignoreCancelToken: false
        },
      },
      opt || {}
    )
  );
}

判断条件

当请求一致时, 如果再次请求, 会取消之前的请求 详见src\utils\http\axios\axiosCancel.ts

ts
const getPendingUrl = (config: AxiosRequestConfig): string => {
  return [config.method, config.url].join("&");
};

默认为method + url, 可根据实际情况修改

白名单

有些 url 是不希望被取消的 在接口配置

ignoreCancelToken: 忽略重复请求

ts
// 例子
export function tenantList() {
  return defHttp.get<TenantResp>(
    { url: Api.TenantList },
    { ignoreCancelToken: true }
  );
}

excel 文件下载

ts
import { defHttp } from "/@/utils/http/axios";

/**
 * 由于后端是采用post请求+返回二进制
 * 使用这种方式进行下载(注意这里只是拿到了Blob)
 * responseType: 'blob' 记得填写
 * isTransformResponse上面文档有写 需要原生的响应
 */
export function postExport() {
  return defHttp.post<Blob>(
    { url: Api.postExport, responseType: "blob" },
    { isTransformResponse: false }
  );
}

然后调用 src/utils/file/download 中的方法进行保存/下载

ts
/**
 * 下载excel文件
 * @param func axios函数
 * @param fileName 文件名称
 * @param withRandomName 是否带随机文件名
 */
export async function downloadExcel(
  func: (data?: any) => Promise<Blob>,
  fileName: string,
  withRandomName = true
) {}

downloadExcel方法为封装好的下载 excel 方法, 因为后台大部分都有下载 excel, 只需要提供对应的 axios 方法(如上)和文件名即可下载如果有其他的下载需求, 可以调用 downloadByData 方法进行下载

ts
/**
 * Download according to the background interface file stream
 * @param {*} data
 * @param {*} filename
 * @param {*} mime
 * @param {*} bom
 */
export function downloadByData(
  data: BlobPart,
  filename: string,
  mime?: string,
  bom?: BlobPart
) {}

比如上面的 postExport 方法

ts
// 这里拿到的是blob
const blob = await postExport();
downloadByData(blob, "文件名.拓展名");

注意事项

响应弹窗处理

默认获取的返回值(success)是经过处理后的 比如后台为{code, msg, data} 返回值为Promise<data> 而非{code, msg, data}

如果需要原生响应即AxiosResponse 在参数 options 配置

ts
defHttp.post({ xxx }, { isReturnNativeResponse: true });

如果需要拿到完整的{code, msg, data} 在参数 options 配置

ts
defHttp.post({ xxx }, { isTransformResponse: false });

请求重试

虽然提供了请求重试功能但是并没有开启 可自行在index.ts里找到配置开启

ts
/** 请求重试 */
retryRequest: {
	/** 是否开启 */
	isOpenRetry: false,
	/** 重试次数 */
	count: 5,
	/** 等待时间 */
	waitTime: 100,
},

响应转换

RuoYi-Plus 的返回值非标准的{code, msg, data} 在转换时会将非code, msg参数全部封装进data

js
{
  code, msg, list, total, row;
}

->

js
{
	code,
	msg,
	data: {
		list,
		total,
		row
	}
}