项目使用 Axios 作为 HTTP 请求库,并进行了封装。
封装类 : VAxios
import axios from 'axios';

src/
├── utils/http/axios/ ← 第1层:Axios 核心封装(请求引擎)
│ ├── Axios.ts ← VAxios 类:真正的请求器
│ ├── index.ts ← transform 拦截器 + defHttp 导出
│ ├── helper.ts ← 时间戳、日期格式化等工具
│ ├── checkStatus.ts ← HTTP 状态码处理
│ └── axiosCancel.ts ← 取消重复请求
│
├── api/ ← 第2层:业务 API 定义(按模块拆分)
│ ├── sys/ ← 系统相关(用户、权限、菜单)
│ │ ├── user.ts ← 登录/用户信息 API
│ │ ├── menu.ts ← 菜单 API
│ │ ├── upload.ts ← 上传 API
│ │ └── model/ ← TypeScript 类型定义
│ │ ├── userModel.ts
│ │ ├── menuModel.ts
│ │ └── uploadModel.ts
│ │
│ ├── demo/ ← 示例模块
│ │ ├── account.ts
│ │ ├── table.ts
│ │ └── model/
│ │
│ ├── common/ ← 公共 API
│ │ └── api.ts
│ │
│ └── model/ ← 通用 Model(分页基础类型)
│ └── baseModel.ts
│
└── views/xxx/ ← 第3层:页面调用(import API → 使用)
└── sys/login/useLogin.ts ← 例如:登录页调用 loginApi()
utils/http/axios/
AXios使用
java
// utils/http/axios/http.js
import axios from 'axios';
// 创建 axios 实例
const http = axios.create({
baseURL: 'https://jsonplaceholder.typicode.com', // 示例接口
timeout: 5000, // 请求超时
headers: {
'Content-Type': 'application/json'
}
});
// 请求拦截器(可选)
http.interceptors.request.use(
config => {
// 在发送请求前可以做一些处理,例如添加 token
// config.headers.Authorization = `Bearer ${token}`;
return config;
},
error => Promise.reject(error)
);
// 响应拦截器(可选)
http.interceptors.response.use(
response => response.data, // 直接返回 data
error => Promise.reject(error)
);
export default http;
在业务组件或 API 模块中,直接引入上述封装好的实例即可发起请求:
java
// 调用示例
import http from '@/utils/http/axios/http';
// GET 请求
http.get('/posts/1')
.then(res => {
console.log('GET response:', res);
})
.catch(err => {
console.error('GET error:', err);
});
// POST 请求
http.post('/posts', {
title: 'foo',
body: 'bar',
userId: 1
})
.then(res => {
console.log('POST response:', res);
})
.catch(err => {
console.error('POST error:', err);
});
request 和 service 只是两个局部变量名 。它们指向的是内存中同一个 Axios 实例对象。所以,无论你叫 request、axiosInstance 还是 http,在功能上都是完全等价的。
java
import service from '@/utils/http/axios';
export function getUserInfo(id) {
return service({ url: `/user/${id}`, method: 'get' });
}
历史
最早网页提交数据是这样的:
浏览器 ↓ 提交表单 ↓ 服务器 ↓ 返回新页面 ↓ 整个页面刷新
缺点:
- 页面闪烁
- 整页刷新
- 用户体验差
第二阶段:AJAX(2005左右)
XMLHttpRequest XHR
var xhr = new XMLHttpRequest();
xhr.open("GET", "/user/info");
xhr.onreadystatechange = function () {
if(xhr.readyState === 4){
if(xhr.status === 200){
console.log(xhr.responseText);
}
}
};
xhr.send();
页面↓
XHR
↓
服务器
↓
返回JSON
↓
局部更新页面
JQuery时代
$.ajax({
url:'/user/list',
type:'GET',
success:function(res){
console.log(res);
}
})
$.get('/user/list');
$.post('/login');
第三阶段:Promise时代
$.ajax({
success:function(){
$.ajax({
success:function(){
$.ajax({
})
}
})
}
})
大家开始讨厌回调地狱:
fetch()
java
fetch('/user/list')
.then(res => res.json())
.then(data => {
console.log(data);
});
const data = await fetch('/user/list');
Axios诞生
坑1:不会自动转JSON
const res = await axios.get('/user');
console.log(res.data);
Fetch:
const res = await fetch('/user');
const data = await res.json();
多一步。
坑2:404不会报错
Axios:
axios.get('/404')
.catch(err=>{
console.log("错误");
})
Fetch:
const res = await fetch('/404');
console.log(res.ok); // false
不会自动进入 catch。
需要自己判断:
if(!res.ok){
throw new Error();
}
坑3:没有请求拦截器
Axios:
axios.interceptors.request.use(...)
Fetch:
fetch(...)
没有。
你得自己封装。
坑4:没有响应拦截器
Axios:
axios.interceptors.response.use(...)
Fetch 没有。
坑5:取消请求复杂
Axios:
CancelToken
AbortController
已经封装好了。
所以 Axios 本质是什么?
其实就是:
Axios
=
XMLHttpRequest
+
Promise
+
拦截器
+
超时处理
+
自动JSON
+
取消请求
+
统一配置
1
axios.get('/sys/user/list')
2
request({
url:'/sys/user/list'
})
统一 baseURL。
3
requestInterceptors()
responseInterceptors()
统一 Token。
4
transformRequestHook()
统一处理:
{
"code":200,
"message":"成功",
"result":[]
}
变成:
[]
4
AxiosCanceler
防重复提交。
5
VAxios
把所有逻辑集中管理。
封装
没有对axios基础了解。
VAxios 类 ( Axios.ts ):-
提供 get / post / put / delete / request 方法
-
提供 uploadFile 文件上传方法
-
通过构造函数接收 CreateAxiosOptions 配置
-
utils/http/axios/
├── axiosTransform.ts ← 类型定义(抽象基类 + 配置接口)
├── Axios.ts ← VAxios 核心类(请求引擎)
├── index.ts ← 业务 transform + defHttp 实例(对外入口)
├── helper.ts ← 时间戳、日期格式化小工具
├── checkStatus.ts ← HTTP 状态码统一处理
└── axiosCancel.ts ← 重复请求取消管理器
axiosTransform.ts
java
// 1) 扩展 axios 原生的 AxiosRequestConfig
export interface CreateAxiosOptions extends AxiosRequestConfig {
authenticationScheme?: string; // 鉴权方案,如 "Bearer"
transform?: AxiosTransform; // ★ 核心:钩子集合
requestOptions?: RequestOptions; // 业务选项(是否转响应、是否弹错等)
}
// 2) 抽象钩子类:所有请求处理流程都在这里预留口子
export abstract class AxiosTransform {
// 钩子①:请求发起前(拼前缀、转 data 为 query string) 箭头函数是返回值
beforeRequestHook?: (config, options) => AxiosRequestConfig;
// 钩子②:响应返回后(解包 result、根据 code 判成功)
transformRequestHook?: (res, options) => any;
// 钩子③:请求 Promise catch 时
requestCatchHook?: (e, options) => Promise<any>;
// 钩子④⑤:axios 原生请求/响应拦截器(加 Token、加签名)
requestInterceptors?: (config, options) => AxiosRequestConfig;
responseInterceptors?: (res) => AxiosResponse;
// 钩子⑥⑦:拦截器 error 回调
requestInterceptorsCatch?: (error) => void;
responseInterceptorsCatch?: (error) => void;
}
设计亮点 : VAxios 只负责"调用钩子",不关心钩子内部做什么。因此你可以:
-
做一个 defHttp 用 Token + 签名
-
再做一个 thirdPartyHttp 用不同的 header 规则
-
甚至做一个 mockHttp 用于单测
→ 一套引擎,多种实例。
封装
VAxios
在 TypeScript、JavaScript 的 class 中,当你 new 一个对象时,会自动执行 constructor。
java
constructor(options: CreateAxiosOptions) {
this.options = options; // 保存配置
this.axiosInstance = axios.create(options); // 创建原生 axios 实例
this.setupInterceptors(); // 安装拦截器
}
setupInterceptors --- 拦截器管道安装 ( Axios.ts#L69-L111 )
java
/**
* @description: 拦截器配置 ------ 这是整个请求流程的"管道安装器"
*
* 执行时机:在 VAxios 构造函数里被调用,只执行一次
* 做的事:向 axios 原生实例的 interceptors.request / interceptors.response
* 里分别注册 4 个"钩子",后续每个请求/响应都会按顺序经过它们
*/
private setupInterceptors() {
const transform = this.getTransform();
if (!transform) {
return;
}
const {
requestInterceptors, // 请求拦截器(加 Token/签名/租户ID)
requestInterceptorsCatch, // 请求拦截器出错时
responseInterceptors, // 响应拦截器
responseInterceptorsCatch, // 响应拦截器出错时
} = transform;
// 【第二步:实例化"重复请求取消器"
// AxiosCanceler 内部用 Map<method&url, cancelFn> 记录正在进行的请求
// 同一个 method&url 的新请求到来时,会先 cancel 掉旧的,再加入新的
// ───────────────────────────────────────────────
const axiosCanceler = new AxiosCanceler();
this.axiosInstance.interceptors.request.use(...)
requestInterceptors(config){
config.headers.token = "123456";
return config;
}
原来请求:
GET /user/list
经过拦截器:
GET /user/list
token:123456
所以请求拦截器就是:
发请求前修改配置
this.axiosInstance.interceptors.response.use(...)
这是响应拦截器。
服务器返回:
{
"code":200,
"msg":"成功",
"result":[]
}
进入:
responseInterceptors(res){
console.log("收到结果");
return res;
}
作用:
收到服务器数据后统一处理
// 响应结果拦截器错误捕获
responseInterceptorsCatch &&
isFunction(responseInterceptorsCatch) &&
this.axiosInstance.interceptors.response.use(undefined, responseInterceptorsCatch);
1 创建请求配置
↓
2 requestInterceptors
↓
加token
↓
3 发给后端
↓
4 后端返回数据
↓
5 responseInterceptors
↓
统一处理结果
↓
6 返回给页面
index.js
const transform: AxiosTransform = {就是"给 JeecgBoot 的 axios 封装灌入真正的业务逻辑" ------ 没有它, VAxios 就是一个空壳,所有 Hook 都不会做任何事。
}
| Hook 名称 | 调用时机 | 作用 | 返回值 / 注意事项 |
|---|---|---|---|
beforeRequestHook |
请求发送前 | 对请求的 config 做统一处理,比如拼接前缀、时间戳、格式化参数 |
必须返回处理后的 config |
requestInterceptors |
请求发送前(axios 拦截器层) | 修改请求头、加 token、签名、租户ID等,或做低代码/共享租户处理 | 必须返回 config,最终会传给 axios 发送请求 |
requestInterceptorsCatch |
请求拦截器报错时 | 捕获请求拦截器里抛出的异常 | 一般处理日志或提示,不返回值 |
transformRequestHook |
响应返回后 | 对返回数据做统一处理,比如解包 result,提示消息,处理超时或失败 |
返回最终数据,如果抛异常会被 request 的 Promise reject |
responseInterceptors |
响应返回后(axios 拦截器层) | 可以对原始响应做处理,比如修改返回结构、统一解包 | 返回处理后的 res |
responseInterceptorsCatch |
响应报错时 | 捕获响应异常(HTTP 401/500/网络错误等),记录日志,弹窗或提示消息 | 返回 Promise.reject(error) |
- 请求阶段 :
beforeRequestHook → requestInterceptors → requestInterceptorsCatch - 响应阶段 :
responseInterceptors → transformRequestHook → responseInterceptorsCatch
java
request<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
// 1. 克隆一份 config,避免修改外部传入的对象
let conf: CreateAxiosOptions = cloneDeep(config);
// 2. 获取用户自定义的 transform 对象(里面包含各种 hook)
const transform = this.getTransform();
// 3. 获取 VAxios 构造时默认的 requestOptions
const { requestOptions } = this.options;
// 4. 合并默认选项和本次请求的自定义 options
const opt: RequestOptions = Object.assign({}, requestOptions, options);
// 5. 从 transform 中取出需要用的 hook
const { beforeRequestHook, requestCatchHook, transformRequestHook } = transform || {};
// 6. 调用 beforeRequestHook 对请求 config 进行加工处理(比如加 token、url 拼接、参数处理)
if (beforeRequestHook && isFunction(beforeRequestHook)) {
conf = beforeRequestHook(conf, opt);
}
// 7. 把 opt 挂到 conf 上,供后续拦截器使用
conf.requestOptions = opt;
// 8. 如果是 form-data 数据,把 data 格式化成 URLSearchParams 或 FormData
conf = this.supportFormData(conf);
// 9. 返回一个 Promise,外部使用 await 或 then 接收结果
return new Promise((resolve, reject) => {
// 10. 真正调用 axios 发请求
this.axiosInstance
.request<any, AxiosResponse<Result>>(conf)
.then((res: AxiosResponse<Result>) => {
// 11. 请求成功,调用 transformRequestHook 对响应进行加工(比如解包 data、弹消息)
if (transformRequestHook && isFunction(transformRequestHook)) {
try {
const ret = transformRequestHook(res, opt);
// ★ 可选:支持原始 success 回调
config.success && config.success(res.data);
// 12. resolve 返回加工后的数据
resolve(ret);
} catch (err) {
// 13. 处理 transformRequestHook 内部抛出的异常
reject(err || new Error('request error!'));
}
return;
}
// 14. 如果没有 transformRequestHook,则直接返回原始响应(类型转换成 T)
resolve(res as unknown as Promise<T>);
})
.catch((e: Error | AxiosError) => {
// 15. 请求失败,调用 requestCatchHook 处理异常
if (requestCatchHook && isFunction(requestCatchHook)) {
reject(requestCatchHook(e, opt));
return;
}
// 16. axios 自带错误处理,可以在这里重写 error 信息
if (axios.isAxiosError(e)) {
// 可以自定义错误信息,例如 e.message = "请求超时"
}
// 17. 最终 reject 异常给调用者
reject(e);
});
});
}
使用
java
/**
* @description: user login api
*/
export function loginApi(params: LoginParams, mode: ErrorMessageMode = 'modal') {
return defHttp.post<LoginResultModel>(
{
url: Api.Login,
params,
},
{
errorMessageMode: mode,
}
);
}
java
export interface LoginParams {
username: string;
password: string;
}
java
enum Api {
Login = '/sys/login',
phoneLogin = '/sys/phoneLogin',
Logout = '/sys/logout',
GetUserInfo = '/sys/user/getUserInfo',
// 获取系统权限
// 1、查询用户拥有的按钮/表单访问权限
// 2、所有权限
// 3、系统安全模式
GetPermCode = '/sys/permission/getPermCode',
//新加的获取图形验证码的接口
getInputCode = '/sys/randomImage',
//获取短信验证码的接口
getCaptcha = '/sys/sms',
//注册接口
registerApi = '/sys/user/register',
//校验用户接口
checkOnlyUser = '/sys/user/checkOnlyUser',
//SSO登录校验
validateCasLogin = '/sys/cas/client/validateLogin',
//校验手机号
phoneVerify = '/sys/user/phoneVerification',
//修改密码
passwordChange = '/sys/user/passwordChange',
//第三方登录
thirdLogin = '/sys/thirdLogin/getLoginUser',
//第三方登录
getThirdCaptcha = '/sys/thirdSms',
//获取二维码信息
getLoginQrcode = '/sys/getLoginQrcode',
//监控二维码扫描状态
getQrcodeToken = '/sys/getQrcodeToken',
}
post<T = any> 是 VAxios 里的方法,带 泛型 T:
post<LoginResultModel> 指定泛型 T 为 LoginResultModel,也就是说这个请求返回的结果会被当作 LoginResultModel 类型。
java
post<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
return this.request({ ...config, method: 'POST' }, options);
}
post<T = any>(
config: AxiosRequestConfig, // 第一个参数:请求配置
options?: RequestOptions // 第二个参数(可选):自定义请求选项
): Promise<T> // 返回值:泛型 Promise<T>
java
/**
* @description: Login interface return value
*/
export interface LoginResultModel {
userId: string | number;
token: string;
role: RoleInfo;
userInfo?: any
}
const data = await phoneLoginApi(loginParams, mode);
// 代码逻辑说明: 【issues/7488】手机号码登录,在请求头中无法获取租户id---
const { token , userInfo } = data;