1. 为什么我们需要深度封装Axios?
在前后端分离的开发模式下,HTTP请求库的选择直接影响着开发效率和代码质量。Axios作为当前最流行的Promise-based HTTP客户端,其简洁的API设计和强大的功能使其成为前端开发的首选。但直接使用原生Axios会面临几个典型问题:
- 每个请求都需要重复处理错误码
- 文件上传下载需要额外处理进度和格式
- 接口地址分散在各处难以维护
- 缺乏统一的请求/响应数据处理机制
我在多个Vue/React项目中实践发现,未经封装的Axios会导致以下痛点:
- 接口变更时需要全局搜索替换URL
- 不同开发者实现的错误处理逻辑不一致
- 文件传输功能重复造轮子
- 安全策略(如Token刷新)实现混乱
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 基础封装架构设计
2.1 三层封装模型
我们采用分层设计思想,将封装结构划分为:
code复制└─http
├─core # 核心封装层
│ ├─interceptor # 拦截器模块
│ ├─adapter # 适配器模块
│ └─axios.js # 实例配置
├─modules # 业务模块层
│ ├─user.api.js
│ ├─order.api.js
│ └─...
└─index.js # 统一出口
2.2 核心配置实现
创建axios实例时建议配置:
javascript复制const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 10000,
headers: {
'Content-Type': 'application/json;charset=utf-8',
'X-Requested-With': 'XMLHttpRequest'
}
})
关键配置说明:
- baseURL通过环境变量注入,适应多环境需求
- timeout需要根据业务场景调整,文件上传时可适当延长
- 默认Content-Type设置为JSON格式
3. 拦截器深度开发
3.1 请求拦截器实战
典型请求拦截器应包含:
javascript复制service.interceptors.request.use(
config => {
// 自动注入Token
if (store.getters.token) {
config.headers['Authorization'] = `Bearer ${store.getters.token}`
}
// 特殊Content-Type处理
if (config.isUpload) {
config.headers['Content-Type'] = 'multipart/form-data'
}
// 请求参数预处理
if (config.paramsSerializer) {
config.params = config.paramsSerializer(config.params)
}
return config
},
error => {
return Promise.reject(error)
}
)
3.2 响应拦截器最佳实践
完整的响应处理应包含:
javascript复制service.interceptors.response.use(
response => {
const res = response.data
// 自定义状态码处理
if (res.code !== 200) {
if (res.code === 401) {
// Token过期处理
return handleTokenExpired()
}
// 业务错误统一提示
Message.error(res.message || 'Error')
return Promise.reject(new Error(res.message || 'Error'))
} else {
return res
}
},
error => {
// HTTP状态码处理
switch (error.response.status) {
case 400:
error.message = '请求错误'
break
case 500:
error.message = '服务器内部错误'
break
// ...其他状态码
}
// 超时特殊处理
if (error.message.includes('timeout')) {
error.message = '请求超时'
}
Message.error(error.message)
return Promise.reject(error)
}
)
4. 文件处理专业方案
4.1 上传下载统一封装
文件上传核心方法:
javascript复制export function uploadFile(url, formData, onProgress) {
return service({
url,
method: 'post',
data: formData,
isUpload: true,
onUploadProgress: progressEvent => {
if (progressEvent.lengthComputable) {
const percent = (progressEvent.loaded / progressEvent.total) * 100
onProgress && onProgress(Math.round(percent))
}
}
})
}
文件下载优化方案:
javascript复制export function downloadFile(url, params, filename) {
return service({
url,
params,
method: 'get',
responseType: 'blob'
}).then(res => {
const blob = new Blob([res])
const downloadUrl = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = downloadUrl
a.download = filename
document.body.appendChild(a)
a.click()
URL.revokeObjectURL(downloadUrl)
document.body.removeChild(a)
})
}
4.2 大文件分片上传
针对大文件的上传优化:
javascript复制async function chunkUpload(file, chunkSize = 5 * 1024 * 1024) {
const chunks = Math.ceil(file.size / chunkSize)
const results = []
for (let i = 0; i < chunks; i++) {
const start = i * chunkSize
const end = Math.min(file.size, start + chunkSize)
const chunk = file.slice(start, end)
const formData = new FormData()
formData.append('chunk', chunk)
formData.append('chunkIndex', i)
formData.append('totalChunks', chunks)
formData.append('fileHash', await calculateFileHash(file))
const res = await uploadChunk(formData)
results.push(res)
// 更新进度
updateProgress((i + 1) / chunks * 100)
}
return mergeChunks(file.name, fileHash)
}
5. 业务接口标准化管理
5.1 API模块化方案
用户模块示例:
javascript复制// modules/user.api.js
import request from '../core/axios'
export function login(data) {
return request({
url: '/auth/login',
method: 'post',
data
})
}
export function getUserInfo() {
return request({
url: '/user/info',
method: 'get'
})
}
export function updateAvatar(avatar) {
const formData = new FormData()
formData.append('avatar', avatar)
return request({
url: '/user/avatar',
method: 'post',
data: formData,
isUpload: true
})
}
5.2 TypeScript增强支持
为提升开发体验,可以添加类型定义:
typescript复制// types/api.d.ts
declare module '@/api' {
export interface Response<T = any> {
code: number
data: T
message: string
}
export interface PageParams {
page?: number
size?: number
sort?: string
}
// 业务接口扩展
export interface User {
id: number
name: string
avatar: string
}
}
6. 高级功能实现
6.1 请求重试机制
针对不稳定网络环境的优化:
javascript复制const RETRY_COUNT = 3
const RETRY_DELAY = 1000
service.interceptors.response.use(null, async error => {
const config = error.config
if (!config || !config.retry) {
return Promise.reject(error)
}
config.__retryCount = config.__retryCount || 0
if (config.__retryCount >= RETRY_COUNT) {
return Promise.reject(error)
}
config.__retryCount += 1
await new Promise(resolve => setTimeout(resolve, RETRY_DELAY))
return service(config)
})
6.2 并发请求控制
防止接口洪水攻击:
javascript复制class RequestQueue {
constructor(max = 10) {
this.max = max
this.queue = []
this.pendingCount = 0
}
add(request) {
return new Promise((resolve, reject) => {
this.queue.push({ request, resolve, reject })
this.run()
})
}
run() {
if (this.pendingCount >= this.max || !this.queue.length) return
const { request, resolve, reject } = this.queue.shift()
this.pendingCount++
request()
.then(resolve)
.catch(reject)
.finally(() => {
this.pendingCount--
this.run()
})
}
}
// 使用示例
const queue = new RequestQueue(5)
queue.add(() => service.get('/api1'))
queue.add(() => service.post('/api2', data))
7. 安全防护策略
7.1 CSRF防御配置
javascript复制// 从cookie中获取CSRF Token
function getCookie(name) {
const value = `; ${document.cookie}`
const parts = value.split(`; ${name}=`)
if (parts.length === 2) return parts.pop().split(';').shift()
}
// 请求拦截器中添加CSRF Token
service.interceptors.request.use(config => {
const csrfToken = getCookie('XSRF-TOKEN')
if (csrfToken) {
config.headers['X-XSRF-TOKEN'] = csrfToken
}
return config
})
7.2 参数加密方案
敏感数据加密处理:
javascript复制import CryptoJS from 'crypto-js'
const SECRET_KEY = 'your-secret-key'
function encryptData(data) {
return CryptoJS.AES.encrypt(
JSON.stringify(data),
SECRET_KEY
).toString()
}
service.interceptors.request.use(config => {
if (config.needEncrypt) {
config.data = {
encrypted: encryptData(config.data)
}
}
return config
})
8. 性能优化技巧
8.1 请求缓存实现
javascript复制const cacheMap = new Map()
function cacheRequest(config) {
const cacheKey = `${config.url}_${JSON.stringify(config.params)}`
if (cacheMap.has(cacheKey)) {
const { expire, data } = cacheMap.get(cacheKey)
if (Date.now() < expire) {
return Promise.resolve(data)
}
cacheMap.delete(cacheKey)
}
return service(config).then(res => {
cacheMap.set(cacheKey, {
expire: Date.now() + 5 * 60 * 1000, // 5分钟缓存
data: res
})
return res
})
}
8.2 取消重复请求
javascript复制const pendingMap = new Map()
function addPending(config) {
const key = `${config.method}_${config.url}_${JSON.stringify(config.params)}`
config.cancelToken = config.cancelToken || new axios.CancelToken(cancel => {
if (!pendingMap.has(key)) {
pendingMap.set(key, cancel)
}
})
}
function removePending(config) {
const key = `${config.method}_${config.url}_${JSON.stringify(config.params)}`
if (pendingMap.has(key)) {
const cancel = pendingMap.get(key)
cancel(key)
pendingMap.delete(key)
}
}
// 在请求拦截器中添加
service.interceptors.request.use(config => {
removePending(config)
addPending(config)
return config
})
// 在响应拦截器中移除
service.interceptors.response.use(response => {
removePending(response.config)
return response
})
9. 测试与调试方案
9.1 Mock数据方案
开发阶段可以使用Mock.js:
javascript复制import Mock from 'mockjs'
Mock.mock('/api/login', 'post', {
code: 200,
data: {
token: '@guid',
userId: '@id',
userName: '@cname'
}
})
// 在axios封装中判断环境
if (process.env.NODE_ENV === 'development') {
require('./mock')
}
9.2 请求日志记录
开发调试工具函数:
javascript复制function logRequest(config) {
if (process.env.NODE_ENV === 'development') {
console.groupCollapsed(`%c ${config.method.toUpperCase()} ${config.url}`, 'color: #1890ff')
console.log('Params:', config.params)
console.log('Data:', config.data)
console.groupEnd()
}
}
function logResponse(response) {
if (process.env.NODE_ENV === 'development') {
console.groupCollapsed(`%c Response ${response.config.url}`, 'color: #52c41a')
console.log('Data:', response.data)
console.log('Headers:', response.headers)
console.groupEnd()
}
}
// 在拦截器中调用
service.interceptors.request.use(config => {
logRequest(config)
return config
})
service.interceptors.response.use(response => {
logResponse(response)
return response
})
10. 工程化整合建议
10.1 多环境配置方案
通过.env文件管理环境变量:
code复制# .env.development
VUE_APP_BASE_API = '/dev-api'
VUE_APP_TIMEOUT = 10000
# .env.production
VUE_APP_BASE_API = '/prod-api'
VUE_APP_TIMEOUT = 20000
axios配置读取环境变量:
javascript复制const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: process.env.VUE_APP_TIMEOUT
})
10.2 自动化API生成
基于Swagger文档自动生成API:
javascript复制async function generateApiFromSwagger(url) {
const { data } = await axios.get(url)
const paths = data.paths
const api = {}
Object.entries(paths).forEach(([path, methods]) => {
const pathName = path.split('/').filter(Boolean).join('_')
Object.entries(methods).forEach(([method, config]) => {
const operationId = config.operationId || `${method}_${pathName}`
api[operationId] = (params = {}, config = {}) => {
const requestConfig = {
url: path,
method,
...config
}
if (['get', 'delete'].includes(method)) {
requestConfig.params = params
} else {
requestConfig.data = params
}
return service(requestConfig)
}
})
})
return api
}
在实际项目中,这种深度封装方案可以使HTTP请求相关代码量减少40%以上,错误处理统一性提升90%,特别在大型项目中效果更为显著。建议根据团队技术栈选择合适的扩展功能,不必盲目追求大而全的实现。
