【Axios统一封装】+【中后台前端接口规范】:从拦截器配置到业务落地,彻底搞懂API请求规范封装,避开重复代码与分散错误处理坑!

📑 文章目录
- 一、为什么要做统一封装?
- [1.1 不封装时的问题](#1.1 不封装时的问题)
- 二、封装前先搞清楚这几件事
- [2.1 请求与响应的结构](#2.1 请求与响应的结构)
- [2.2 常见后端约定](#2.2 常见后端约定)
- 三、完整封装示例(可直接用于项目)
- [3.1 目录结构建议](#3.1 目录结构建议)
- [3.2 第一步:创建 request.js 封装](#3.2 第一步:创建 request.js 封装)
- [3.3 第二步:按模块拆分 API](#3.3 第二步:按模块拆分 API)
- [3.4 第三步:在页面中使用](#3.4 第三步:在页面中使用)
- 四、常见配置说明
- [4.1 baseURL 的几种写法](#4.1 baseURL 的几种写法)
- [4.2 GET 用 params,POST 用 data](#4.2 GET 用 params,POST 用 data)
- [4.3 请求头里传 Token 的常见方式](#4.3 请求头里传 Token 的常见方式)
- 五、常见坑与注意事项
- [5.1 拦截器里 return 的值](#5.1 拦截器里 return 的值)
- [5.2 业务需要原始 response 时](#5.2 业务需要原始 response 时)
- [5.3 某些接口不想统一错误提示](#5.3 某些接口不想统一错误提示)
- [5.4 重复请求与取消](#5.4 重复请求与取消)
- 六、小结:封装时建议遵守的原则
- 七、速查清单
- [🔍 系列模块导航](#🔍 系列模块导航)
同学们好,我是 Eugene(尤金),一名多年中后台前端开发工程师。
(Eugene 发音 /juːˈdʒiːn/,大家怎么顺口怎么叫就好)
很多前端开发者都会遇到一个瓶颈:
代码能跑,但不够规范;功能能实现,但维护起来特别痛苦;一个人写没问题,一到团队协作就各种混乱、踩坑、返工。
想写出干净、优雅、可维护 的专业代码,靠的不是天赋,而是体系化的规范 + 真实实战经验。
这一系列《前端规范实战》,我会用大白话 + 真实业务场景,不讲玄学、不堆理论,只分享能直接落地的规范、标准与避坑指南。
帮你从「会写代码」真正升级为「会写优质、可维护、团队级别的代码」。
一、为什么要做统一封装?
1.1 不封装时的问题
日常直接用 Axios 时,代码常是这样:
js
// 项目里到处都是这样的重复代码
axios.get('/api/user/info').then(res => {
if (res.data.code === 200) {
console.log(res.data.data)
} else {
Message.error(res.data.message || '请求失败')
}
}).catch(err => {
Message.error('网络错误,请稍后重试')
})
// 另一个文件里又来一遍
axios.post('/api/order/create', { productId: 1 }).then(res => {
if (res.data.code === 200) {
// ...
} else {
Message.error(res.data.message || '请求失败')
}
}).catch(err => {
Message.error('网络错误,请稍后重试')
})
问题主要有:
- 重复逻辑 :
code === 200、错误提示、catch在几十上百个请求里反复写。 - 风格不统一 :有人用
res.data,有人用res.data.data,维护成本高。 - 扩展困难:加 token、统一 loading、请求重试等,都要改很多地方。
- 错误处理分散:401 未登录、403 无权限等,每个接口各自处理,容易遗漏。
统一封装的目的,就是把这些公共逻辑集中在一处,让业务代码更干净、可维护。
[⬆ 返回目录](#⬆ 返回目录)
二、封装前先搞清楚这几件事
2.1 请求与响应的结构
先看一次完整请求的"路径":
你的代码 axios.get('/api/user')
↓
请求拦截器(可以改请求头、加 token 等)
↓
真正发出去的 HTTP 请求
↓
后端返回
↓
响应拦截器(可以统一处理 data、错误等)
↓
你的 .then() 里拿到的 res
业务里一般期望:
- 只拿到「业务数据」,而不是
res.data.data这种多层结构。 - 错误在封装层统一处理,业务尽量只写「成功时做什么」。
[⬆ 返回目录](#⬆ 返回目录)
2.2 常见后端约定
很多项目都是类似约定:
js
// 成功时
{
code: 200,
message: "成功",
data: { id: 1, name: "张三" }
}
// 失败时
{
code: 401,
message: "未登录,请先登录"
// 可能没有 data
}
code 含义可约定为:
200:成功401:未登录403:无权限500:服务器错误
封装时围绕这些约定来设计,就能让接口调用更统一。
[⬆ 返回目录](#⬆ 返回目录)
三、完整封装示例(可直接用于项目)
3.1 目录结构建议
src/
├── utils/
│ └── request.js # Axios 封装
├── api/
│ ├── user.js # 用户相关接口
│ └── order.js # 订单相关接口
[⬆ 返回目录](#⬆ 返回目录)
3.2 第一步:创建 request.js 封装
js
/**
* Axios 统一封装
* 职责:基础配置、拦截器、统一错误处理
*/
import axios from 'axios'
import { ElMessage } from 'element-plus' // 如果用 Vue3 + Element Plus
// 如果用 Vue2,改为:import { Message } from 'element-ui'
// ============ 1. 创建 axios 实例 ============
const request = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL || '/api', // Vue3 Vite 写法
// baseURL: process.env.VUE_APP_BASE_API || '/api', // Vue2 Vue CLI 写法
timeout: 15000, // 超时时间 15 秒
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
})
// ============ 2. 请求拦截器 ============
request.interceptors.request.use(
(config) => {
// 从本地存储获取 token(按你项目的存储方式改)
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
(error) => {
console.error('请求拦截器出错:', error)
return Promise.reject(error)
}
)
// ============ 3. 响应拦截器 ============
request.interceptors.response.use(
(response) => {
const res = response.data
// 根据你们后端的 code 约定来写
if (res.code === 200) {
// 成功:只返回业务数据,业务层直接用
return res.data
}
// 业务错误(如参数错误、权限不足等)
ElMessage.error(res.message || '请求失败')
return Promise.reject(new Error(res.message || '请求失败'))
},
(error) => {
// HTTP 错误(网络错误、404、500 等)
if (error.response) {
const status = error.response.status
switch (status) {
case 401:
ElMessage.error('未登录或登录已过期,请重新登录')
// 跳转登录页
// router.push('/login')
break
case 403:
ElMessage.error('没有权限访问')
break
case 404:
ElMessage.error('请求的资源不存在')
break
case 500:
ElMessage.error('服务器错误,请稍后重试')
break
default:
ElMessage.error(error.response.data?.message || '请求失败')
}
} else {
ElMessage.error('网络异常,请检查网络连接')
}
return Promise.reject(error)
}
)
export default request
要点说明:
axios.create():创建独立实例,不影响全局 Axios。baseURL:所有请求前都会自动拼接,不用在每次调用里写/api。timeout:超时统一处理,避免请求一直挂起。- 请求拦截器:统一加 token、统一请求头。
- 响应拦截器:
code === 200时直接返回res.data,业务层少一层嵌套;非 200 和 HTTP 错误统一提示和reject。
[⬆ 返回目录](#⬆ 返回目录)
3.3 第二步:按模块拆分 API
js
// api/user.js
import request from '@/utils/request'
/**
* 获取用户信息
* @returns {Promise} 用户信息对象
*/
export function getUserInfo() {
return request({
url: '/user/info',
method: 'get'
})
}
/**
* 更新用户资料
* @param {Object} data - 用户资料
*/
export function updateUserInfo(data) {
return request({
url: '/user/update',
method: 'post',
data
})
}
js
// api/order.js
import request from '@/utils/request'
/**
* 创建订单
* @param {Object} data - 订单数据
*/
export function createOrder(data) {
return request({
url: '/order/create',
method: 'post',
data
})
}
/**
* 获取订单列表
* @param {Object} params - 查询参数
*/
export function getOrderList(params) {
return request({
url: '/order/list',
method: 'get',
params
})
}
[⬆ 返回目录](#⬆ 返回目录)
3.4 第三步:在页面中使用
js
// views/UserProfile.vue
<script setup>
import { ref, onMounted } from 'vue'
import { getUserInfo, updateUserInfo } from '@/api/user'
const userInfo = ref({})
onMounted(async () => {
try {
// 直接拿到业务数据,不需要 res.data.data
userInfo.value = await getUserInfo()
} catch (err) {
// 错误已在拦截器里提示,这里一般只需做页面级处理(如清空、跳转)
console.log('获取用户信息失败', err)
}
})
async function handleSave() {
try {
await updateUserInfo(userInfo.value)
ElMessage.success('保存成功')
} catch (err) {
// 错误已统一处理
}
}
</script>
对比不封装时:
- 不需要再写
if (res.data.code === 200)。 - 不需要在
.catch里重复弹错误。 - 接口路径、环境变量都在封装层统一管理。
[⬆ 返回目录](#⬆ 返回目录)
四、常见配置说明
4.1 baseURL 的几种写法
js
// 1. 开发用代理,生产用真实域名
baseURL: import.meta.env.VITE_API_BASE_URL // 在 .env.development 里配 /api
// 2. 根据环境自动切换
baseURL: import.meta.env.MODE === 'development' ? '/api' : 'https://api.xxx.com'
// 3. 多环境
// .env.development -> VITE_API_BASE_URL=/api
// .env.production -> VITE_API_BASE_URL=https://api.xxx.com
开发时通常配合 Vite/Vue CLI 的 proxy,把 /api 转发到后端:
js
// vite.config.js
export default {
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true
}
}
}
}
[⬆ 返回目录](#⬆ 返回目录)
4.2 GET 用 params,POST 用 data
js
// GET:参数拼在 URL 上 ?id=1&name=xxx
request({
url: '/user/list',
method: 'get',
params: { id: 1, name: '张三' }
})
// POST:参数在请求体里
request({
url: '/user/create',
method: 'post',
data: { name: '张三', age: 25 }
})
混用会出错:GET 用 data 不会出现在 URL 上,POST 用 params 一般也不会被后端按请求体解析。
[⬆ 返回目录](#⬆ 返回目录)
4.3 请求头里传 Token 的常见方式
js
// 1. Bearer Token(最常见)
config.headers.Authorization = `Bearer ${token}`
// 2. 自定义字段
config.headers['X-Token'] = token
// 3. 业务要求的字段名
config.headers['Authorization'] = token // 有的后端不要 Bearer 前缀
以你们后端文档为准。
[⬆ 返回目录](#⬆ 返回目录)
五、常见坑与注意事项
5.1 拦截器里 return 的值
js
// ✅ 正确:成功时 return 的值会作为 Promise 的 resolve 结果
request.interceptors.response.use(
(response) => {
return response.data // 业务拿到的就是这个
}
)
// ❌ 容易出错:忘了 return
request.interceptors.response.use(
(response) => {
response.data // 没有 return,业务拿到的是 undefined
}
)
[⬆ 返回目录](#⬆ 返回目录)
5.2 业务需要原始 response 时
例如要读 headers 里的分页信息:
js
// 方案一:在接口里传一个配置,告诉拦截器「不要解包」
// request.js 中
request.interceptors.response.use((response) => {
const res = response.data
if (res.code === 200) {
if (response.config.returnRawResponse) {
return response // 返回完整 response
}
return res.data
}
// ...
})
// 调用时
request({
url: '/order/list',
method: 'get',
params: { page: 1 },
returnRawResponse: true // 自定义标记
})
[⬆ 返回目录](#⬆ 返回目录)
5.3 某些接口不想统一错误提示
js
// 在 request.js 的响应拦截器中
if (res.code !== 200) {
if (!response.config.skipErrorHandler) {
ElMessage.error(res.message || '请求失败')
}
return Promise.reject(new Error(res.message || '请求失败'))
}
// 调用时
request({
url: '/some/silent-api',
method: 'get',
skipErrorHandler: true // 不弹出错误提示
})
[⬆ 返回目录](#⬆ 返回目录)
5.4 重复请求与取消
用户快速点击时可能发多次相同请求,可以配合 AbortController 或请求去重逻辑,在拦截器里统一处理,这里不展开,但封装时要预留扩展点。
[⬆ 返回目录](#⬆ 返回目录)
六、小结:封装时建议遵守的原则
| 原则 | 说明 |
|---|---|
| 单一职责 | request.js 只做请求封装,业务逻辑放 API 和页面 |
| 统一入口 | 所有接口都通过封装的 request,不直接 axios.xxx |
| 约定优于配置 | code、data 结构统一,减少分支判断 |
| 可配置 | 通过 config 支持 skipErrorHandler、returnRawResponse 等 |
| 环境分离 | baseURL、timeout 等用环境变量,便于多环境部署 |
[⬆ 返回目录](#⬆ 返回目录)
七、速查清单
- 是否所有接口都通过封装的
request调用? baseURL是否按环境正确配置?- Token 是否在请求拦截器里统一添加?
- 成功响应是否统一返回
data,减少业务层解包? - 401、403、500 等是否都有统一处理?
- 特殊接口(静默失败、需要原始响应)是否通过配置支持?
按以上方式封装后,日常写业务时只需关心「调哪个接口、传什么参数」,其余都交给封装层,代码会更清晰、可维护。
[⬆ 返回目录](#⬆ 返回目录)
🔍 系列模块导航
📝 API与异步请求规范
一、《Axios 统一封装实战:拦截器配置 + baseURL 优化 + 接口规范,避坑重复代码|API 与异步请求规范篇》
二、《Axios 接口请求规范实战:请求参数 / 响应处理 / 异常兜底,避坑中后台 API 调用混乱|API 与异步请求规范篇》
三、《Axios + Vue 错误处理规范:中后台项目实战,统一捕获系统 / 业务 / 接口异常|API 与异步请求规范篇》
四、《前端实战:Excel 导入导出规范(命名 + 校验 + 错误处理 + 统一交互)|API 与异步请求规范篇》
👉 跟着系列慢慢学,把技术功底扎扎实实地打牢~
📚 系列总览
「前端规范实战系列 」正在持续更新中,后续会整理一篇《前端规范实战系列全系列目录导航》,包含每篇文章简介 + 直达链接,方便大家按顺序、体系化学习。
更新中,敬请期待~
[⬆ 返回目录](#⬆ 返回目录)
技术成长,从来不是比谁写得快,而是比谁写得稳、规范、可维护。
哪怕每次只吃透一条规范,长期下来,差距会非常明显。
后续我会持续更新前端规范、工程化、可维护代码相关实战干货,帮你告别面条代码、维护噩梦,在开发与面试中更有底气。
觉得有用欢迎 点赞 + 收藏 + 关注,不错过每一篇实战内容。
我是 Eugene,与你一起写规范、写优质代码,我们下篇干货见~