以下是一套企业级 Axios 封装方案,包含请求 / 响应拦截、统一错误处理、请求取消、环境区分、请求重试等核心功能,可直接集成到 Vue/React/ 纯前端项目中:
一、封装目录结构
src/
├── utils/
│ ├── request.js # Axios 核心封装
│ └── cancelToken.js # 请求取消工具(可选)
├── config/
│ └── env.js # 环境配置(区分开发/测试/生产)
└── api/ # 接口管理(按业务拆分)
├── user.js # 用户相关接口
└── order.js # 订单相关接口
二、第一步:环境配置(config/env.js)
区分不同环境的接口基础地址,避免硬编码:
javascript
// 环境判断(基于 Vite/Webpack 环境变量)
const env = import.meta.env.MODE || 'development';
// 环境配置
const envConfig = {
development: {
baseURL: 'http://10.230.2.24:8083', // 开发环境
},
test: {
baseURL: 'http://1.95.205.197:8083', // 测试环境
},
production: {
baseURL: 'https://magicpower.sh-liangxin.com/cloud-api', // 生产环境
},
};
export default {
env,
...envConfig[env],
};
三、第二步:Axios 核心封装(utils/request.js)
javascript
import axios from 'axios';
import envConfig from '@/config/env';
import { ElMessage, ElMessageBox } from 'element-plus'; // 按需替换为你的UI库
import { getToken, clearToken, redirectLogin } from '@/utils/auth'; // 自定义token工具
// 创建Axios实例
const service = axios.create({
baseURL: envConfig.baseURL, // 基础地址
timeout: 10000, // 超时时间(10秒)
headers: {
'Content-Type': 'application/json;charset=utf-8',
},
});
// ===================== 请求拦截器 =====================
service.interceptors.request.use(
(config) => {
// 1. 添加token(登录态)
const token = getToken(); // 从localStorage/cookie获取token
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
// 2. GET请求参数序列化(可选)
if (config.method === 'get' && config.params) {
// 解决GET请求参数中数组/对象的序列化问题
config.paramsSerializer = {
serialize: (params) => {
return new URLSearchParams(params).toString();
},
};
}
// 3. 自定义请求头(如接口版本号)
config.headers['X-API-Version'] = 'v1';
return config;
},
(error) => {
// 请求前的错误(如参数错误)
console.error('请求拦截器错误:', error);
return Promise.reject(error);
}
);
// ===================== 响应拦截器 =====================
service.interceptors.response.use(
(response) => {
const res = response.data;
// 1. 接口自定义状态码处理(根据后端约定调整)
const code = res.code || res.status;
// 成功状态码(示例:200/0表示成功)
if (code === 200 || code === 0) {
return res;
}
// 2. 业务错误处理(如参数错误、权限不足)
ElMessage.error(res.msg || '接口请求失败');
return Promise.reject(res);
},
(error) => {
// 网络/HTTP错误处理
const { response, message } = error;
// 1. 取消请求的错误(不提示)
if (axios.isCancel(error)) {
console.log('请求已取消:', message);
return Promise.reject(error);
}
// 2. 超时错误
if (message.includes('timeout')) {
ElMessage.error('请求超时,请稍后重试');
return Promise.reject(error);
}
// 3. 无响应(网络错误)
if (!response) {
ElMessage.error('网络异常,请检查网络连接');
return Promise.reject(error);
}
// 4. HTTP状态码处理
const status = response.status;
switch (status) {
case 401: // 未授权/Token过期
ElMessageBox.confirm(
'登录状态已过期,请重新登录',
'提示',
{
confirmButtonText: '重新登录',
cancelButtonText: '取消',
type: 'warning',
}
).then(() => {
clearToken(); // 清除token
redirectLogin(); // 跳登录页
});
break;
case 403: // 权限拒绝
ElMessage.error('暂无权限访问该资源');
break;
case 404: // 接口不存在
ElMessage.error('请求地址不存在');
break;
case 500: // 服务器错误
ElMessage.error('服务器内部错误,请稍后重试');
break;
default:
ElMessage.error(`请求失败:${status} - ${response.data?.msg || '未知错误'}`);
}
return Promise.reject(error);
}
);
// ===================== 请求重试(可选) =====================
// 适用于网络抖动导致的偶发失败,需安装 axios-retry
// import axiosRetry from 'axios-retry';
// axiosRetry(service, {
// retries: 3, // 重试次数
// retryDelay: (retryCount) => {
// return retryCount * 1000; // 每次重试间隔1秒
// },
// retryCondition: (error) => {
// // 仅对5xx错误/网络错误重试
// return error.response?.status >= 500 || !error.response;
// },
// });
// ===================== 导出核心方法 =====================
// 封装常用请求方法,简化调用
const request = {
get: (url, params = {}, config = {}) => {
return service.get(url, { params, ...config });
},
post: (url, data = {}, config = {}) => {
return service.post(url, data, config);
},
put: (url, data = {}, config = {}) => {
return service.put(url, data, config);
},
delete: (url, params = {}, config = {}) => {
return service.delete(url, { params, ...config });
},
// 上传文件(FormData)
upload: (url, formData, config = {}) => {
return service.post(url, formData, {
headers: { 'Content-Type': 'multipart/form-data' },
...config,
});
},
};
// 导出Axios实例(用于特殊场景)和封装方法
export default request;
export { service };
四、第三步:请求取消工具(utils/cancelToken.js)
用于防抖场景(如搜索框、快速点击),避免重复请求:
javascript
import axios from 'axios';
// 存储取消请求的函数
const cancelRequestMap = new Map();
/**
* 取消重复请求
* @param {string} url - 请求地址
* @param {string} msg - 取消提示
*/
export const cancelRepeatRequest = (url, msg = '重复请求已取消') => {
if (cancelRequestMap.has(url)) {
const cancel = cancelRequestMap.get(url);
cancel(msg);
cancelRequestMap.delete(url);
}
};
/**
* 获取取消令牌配置
* @param {string} url - 请求地址
* @returns {object} 取消令牌配置
*/
export const getCancelTokenConfig = (url) => {
// 先取消已存在的请求
cancelRepeatRequest(url);
return {
cancelToken: new axios.CancelToken((cancel) => {
cancelRequestMap.set(url, cancel);
}),
};
};
/**
* 清空所有取消请求
*/
export const clearAllCancelRequest = () => {
for (const cancel of cancelRequestMap.values()) {
cancel('页面销毁,取消所有请求');
}
cancelRequestMap.clear();
};
五、第四步:接口管理(api/user.js)
按业务模块拆分接口,统一管理,便于维护:
javascript
import request from '@/utils/request';
import { getCancelTokenConfig } from '@/utils/cancelToken';
// 用户登录
export const login = (data) => {
return request.post('/user/login', data);
};
// 获取用户信息
export const getUserInfo = () => {
return request.get('/user/info');
};
// 搜索用户(带取消请求,防抖)
export const searchUser = (keyword) => {
const url = '/user/search';
return request.get(url, { keyword }, getCancelTokenConfig(url));
};
// 上传用户头像
export const uploadAvatar = (formData) => {
return request.upload('/user/avatar', formData);
};
// 修改用户信息
export const updateUser = (id, data) => {
return request.put(`/user/${id}`, data);
};
// 删除用户
export const deleteUser = (id) => {
return request.delete('/user', { id });
};
六、使用示例(Vue 组件中)
javascript
<script setup lang="ts">
import { ref, onUnmounted } from 'vue';
import { login, searchUser } from '@/api/user';
import { clearAllCancelRequest } from '@/utils/cancelToken';
const loading = ref(false);
// 登录示例
const handleLogin = async () => {
loading.value = true;
try {
const res = await login({
username: 'admin',
password: '123456',
});
console.log('登录成功:', res);
// 存储token、跳首页等逻辑
} catch (err) {
console.error('登录失败:', err);
} finally {
loading.value = false;
}
};
// 搜索用户(防抖)
const handleSearch = async (keyword) => {
try {
const res = await searchUser(keyword);
console.log('搜索结果:', res);
} catch (err) {
if (!axios.isCancel(err)) {
console.error('搜索失败:', err);
}
}
};
// 页面销毁时清空所有取消请求
onUnmounted(() => {
clearAllCancelRequest();
});
</script>
<template>
<el-button @click="handleLogin" :loading="loading">登录</el-button>
<el-input
placeholder="输入用户名搜索"
@input="handleSearch"
clearable
/>
</template>
七、核心特性说明
| 特性 | 说明 |
|---|---|
| 环境区分 | 自动适配开发 / 测试 / 生产环境的接口地址,无需手动修改 |
| 请求拦截 | 统一添加 token、自定义请求头,处理 GET 参数序列化 |
| 响应拦截 | 统一处理业务状态码、HTTP 状态码,全局错误提示 |
| 错误分类处理 | 区分取消请求、超时、网络错误、401/403/500 等场景,针对性提示 |
| 请求取消 | 解决重复请求问题(如搜索框防抖、快速点击) |
| 方法封装 | 简化 get/post/put/delete/upload 调用,参数更清晰 |
| 可扩展 | 支持请求重试、FormData 上传、自定义配置等高级功能 |
八、适配调整建议
- UI 库替换 :若不用 Element Plus,将
ElMessage/ElMessageBox替换为你的 UI 库(如 Ant Design Vue 的message/modal); - 状态码调整 :根据后端实际约定的成功 / 失败状态码修改响应拦截器中的
code判断逻辑; - Token 存储 :
getToken/clearToken需实现自己的 token 存储逻辑(如 localStorage/cookie); - 请求重试 :如需开启,安装
axios-retry并取消注释对应代码; - TypeScript 适配:可给 request 方法添加 TS 类型定义,提升类型提示。
这套封装兼顾了易用性和健壮性,符合企业级项目的开发规范,可直接复用或根据业务需求微调。