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 主要配置及转换器实现
使用
导入
import { defHttp } from "/@/utils/http/axios";
可调用相关封装的方法get post delete ...
详细见Axios.ts
参数 defHttp.post 举例
post<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
return this.request({ ...config, method: 'POST' }, options);
}
config 就是AxiosRequestConfig
, 跟正常使用 axios 一致 options 为框架新增的配置 定义文件路径根目录/types/axios.d.ts
主要功能
export interface RequestOptions {
// 请求失败弹窗类型
errorMessageMode?: ErrorMessageMode;
// 请求成功弹窗类型
successMessageMode?: SuccessMessageMode;
// 请求路径加上时间戳参数
joinTime?: boolean;
// 是否携带token
withToken?: boolean;
// 请求重试机制
retryRequest?: RetryRequest;
/** 是否加密请求参数 */
encrypt?: boolean;
}
比如需要请求加密
defHttp.post({ xxx }, { encrypt: true });
即可
errorMessageMode
和successMessageMode
为请求成功/失败的弹窗类型
export type ErrorMessageMode = "none" | "modal" | "message" | undefined;
none 或者不填写为不弹出任何提示 默认开启请求失败弹窗 类型为 message
触发条件
- httpStatus != 200
- 响应体对象 code != 200
请求失败会自动弹窗{code, msg, data}中的 msg
封装请求/响应 (源码解析??)
文件index.ts
请求前处理
具体看详细代码
// 请求之前处理config
beforeRequestHook: (config, options) => {
// ...参数封装
// ...添加clientId
// ...添加Language
// ...请求加密
// ...检查是否存在重复请求,若存在则取消已发的请求
axiosCanceler.removePendingRequest(config);
// 添加请求到pendingMap
axiosCanceler.addPendingRequest(config);
return config;
},
响应处理
/**
* @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
/**
* @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
全局开关
src\utils\http\axios\index.ts
function createAxios(opt?: Partial<CreateAxiosOptions>) {
return new VAxios(
// 深度合并
deepMerge(
{...
// 配置项,下面的选项都可以在独立的接口请求中覆盖
requestOptions: {
...
// 忽略重复请求
ignoreCancelToken: false
},
},
opt || {}
)
);
}
判断条件
当请求一致时, 如果再次请求, 会取消之前的请求 详见src\utils\http\axios\axiosCancel.ts
const getPendingUrl = (config: AxiosRequestConfig): string => {
return [config.method, config.url].join("&");
};
默认为method + url
, 可根据实际情况修改
白名单
有些 url 是不希望被取消的 在接口配置
ignoreCancelToken: 忽略重复请求
// 例子
export function tenantList() {
return defHttp.get<TenantResp>(
{ url: Api.TenantList },
{ ignoreCancelToken: true }
);
}
excel 文件下载
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 中的方法进行保存/下载
/**
* 下载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 方法进行下载
/**
* 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 方法
// 这里拿到的是blob
const blob = await postExport();
downloadByData(blob, "文件名.拓展名");
注意事项
响应弹窗处理
默认获取的返回值(success)是经过处理后的 比如后台为{code, msg, data} 返回值为Promise<data>
而非{code, msg, data}
如果需要原生响应即AxiosResponse
在参数 options 配置
defHttp.post({ xxx }, { isReturnNativeResponse: true });
如果需要拿到完整的{code, msg, data}
在参数 options 配置
defHttp.post({ xxx }, { isTransformResponse: false });
请求重试
虽然提供了请求重试功能但是并没有开启 可自行在index.ts
里找到配置开启
/** 请求重试 */
retryRequest: {
/** 是否开启 */
isOpenRetry: false,
/** 重试次数 */
count: 5,
/** 等待时间 */
waitTime: 100,
},
响应转换
RuoYi-Plus 的返回值非标准的{code, msg, data}
在转换时会将非code, msg
参数全部封装进data
{
code, msg, list, total, row;
}
->
{
code,
msg,
data: {
list,
total,
row
}
}