November 13, 2025
OpenAI's GPT-5.1, GPT-5.1-Codex and GPT-5.1-Codex-Mini are now in public preview for GitHub Copilot
打造极致优雅的Axios封装,让接口请求回归简单...
GPT-5.1赋能:让Axios封装进入AI驱动的全新时代...
走起,直接上代码....
js
import axios from "axios";
import qs from "qs";
import { Message } from "element-gui";
// ==================== 配置常量 ====================
const REQUEST_CONFIG = {
BASE_URL: process.env.VUE_APP_BASE_API,
TIMEOUT: 60000,
LOGIN_PATH: "/login",
DEFAULT_ERROR_MESSAGE: "请求失败",
ENABLE_LOG: process.env.NODE_ENV === "development",
};
// HTTP 状态码错误消息映射
const HTTP_ERROR_MESSAGES = {
401: "未授权,请重新登录",
403: "没有权限访问",
404: "请求资源不存在",
500: "服务器错误",
502: "网关错误",
503: "服务不可用",
504: "网关超时",
};
// ==================== 请求管理 ====================
class RequestManager {
constructor() {
this.pendingRequests = new Map();
}
// 生成请求唯一标识
generateKey(config) {
const { method, url, params, data } = config;
const serializedParams = params
? qs.stringify(params, { sort: (a, b) => a.localeCompare(b) })
: "";
const serializedData =
data && !(data instanceof FormData)
? qs.stringify(data, { sort: (a, b) => a.localeCompare(b) })
: "";
return `${method}:${url}:${serializedParams}:${serializedData}`;
}
// 添加请求
addRequest(config) {
const requestKey = this.generateKey(config);
// 取消相同的进行中请求
if (this.pendingRequests.has(requestKey)) {
const cancel = this.pendingRequests.get(requestKey);
cancel("取消重复请求");
}
// 为新请求创建取消令牌
config.cancelToken = new axios.CancelToken((cancel) => {
this.pendingRequests.set(requestKey, cancel);
});
config.requestKey = requestKey;
return config;
}
// 移除请求
removeRequest(config) {
if (config?.requestKey) {
this.pendingRequests.delete(config.requestKey);
}
}
// 清空所有请求
clearAll() {
this.pendingRequests.forEach((cancel) => cancel("清空所有请求"));
this.pendingRequests.clear();
}
}
// ==================== 工具函数 ====================
class RequestUtils {
// 日志打印
static log(message, data) {
if (REQUEST_CONFIG.ENABLE_LOG) {
console.log(`[Request] ${message}`, data);
}
}
static error(message, data) {
if (REQUEST_CONFIG.ENABLE_LOG) {
console.error(`[Request] ${message}`, data);
}
}
// Token 管理
static getToken() {
return localStorage.getItem("token");
}
static removeToken() {
localStorage.removeItem("token");
}
static redirectToLogin() {
window.location.href = REQUEST_CONFIG.LOGIN_PATH;
}
// Content-Type 处理
static isFormData(data) {
return data instanceof FormData;
}
static isFormUrlEncoded(headers) {
return headers?.["Content-Type"] === "application/x-www-form-urlencoded";
}
static isBlobResponse(config) {
return config.responseType === "blob";
}
}
// ==================== Axios 实例 ====================
const requestManager = new RequestManager();
const service = axios.create({
baseURL: REQUEST_CONFIG.BASE_URL,
timeout: REQUEST_CONFIG.TIMEOUT,
withCredentials: true,
headers: {
"Content-Type": "application/json",
},
});
// ==================== 请求拦截器 ====================
service.interceptors.request.use(
(config) => {
// 处理重复请求取消
if (config.cancelDuplicate !== false) {
requestManager.addRequest(config);
}
// 处理 Authorization
if (!config.headers?.Authorization) {
const needToken = config.needToken !== false;
if (needToken) {
const token = RequestUtils.getToken();
if (token) {
config.headers = config.headers || {};
config.headers["Authorization"] = `Bearer ${token}`;
RequestUtils.log("添加 Token", { url: config.url });
}
}
} else {
RequestUtils.log("使用自定义 Authorization", { url: config.url });
}
// 处理 FormData
if (RequestUtils.isFormData(config.data)) {
delete config.headers["Content-Type"];
RequestUtils.log("检测到 FormData", { url: config.url });
}
// 处理 application/x-www-form-urlencoded
else if (RequestUtils.isFormUrlEncoded(config.headers)) {
if (config.data && typeof config.data === "object") {
config.data = qs.stringify(config.data);
RequestUtils.log("序列化 Form 数据", { url: config.url });
}
}
RequestUtils.log("发送请求", {
method: config.method,
url: config.url,
params: config.params,
data: config.data,
});
return config;
},
(error) => {
RequestUtils.error("请求配置错误", error);
if (error.config?.showError !== false) {
Message.error("请求发送失败: " + (error.message || "未知错误"));
}
return Promise.reject(error);
}
);
// ==================== 响应拦截器 ====================
service.interceptors.response.use(
(response) => {
// 清理请求记录
requestManager.removeRequest(response.config);
RequestUtils.log("收到响应", {
url: response.config.url,
status: response.status,
data: response.data,
});
// 处理文件下载响应
if (RequestUtils.isBlobResponse(response.config)) {
return response;
}
const res = response.data;
// 业务成功
if (res.code === 0 && response.status === 200) {
return res;
}
// 业务失败
if (res.code !== 0) {
// 未授权处理
if (res.code === 401) {
RequestUtils.removeToken();
RequestUtils.redirectToLogin();
}
const errorMessage = res.message || REQUEST_CONFIG.DEFAULT_ERROR_MESSAGE;
if (response.config?.showError !== false) {
Message.error(errorMessage);
}
return Promise.reject(new Error(errorMessage));
}
return res;
},
(error) => {
// 清理请求记录
requestManager.removeRequest(error.config);
// 处理取消请求
if (axios.isCancel(error)) {
RequestUtils.log("请求被取消", error.message);
return Promise.reject(error);
}
RequestUtils.error("响应错误", error);
let errorMessage = REQUEST_CONFIG.DEFAULT_ERROR_MESSAGE;
// HTTP 错误
if (error.response) {
const status = error.response.status;
errorMessage =
HTTP_ERROR_MESSAGES[status] || `未知错误: ${status}`;
// 401 处理
if (status === 401) {
RequestUtils.removeToken();
RequestUtils.redirectToLogin();
}
}
// 网络错误
else if (error.request) {
errorMessage =
error.code === "ECONNABORTED" ? "请求超时" : "网络连接异常";
}
// 其他错误
else {
errorMessage = error.message || "请求配置错误";
}
if (error.config?.showError !== false) {
Message.error(errorMessage);
}
return Promise.reject(error);
}
);
// ==================== 文件下载处理 ====================
function handleDownloadResponse(response, fileName, showError = true) {
const contentType = response.headers["content-type"] || "";
// 检查是否为 JSON 错误响应
if (contentType.includes("application/json")) {
if (showError) {
Message.error("暂无数据,无法导出!");
}
return;
}
// 从响应头获取文件名
const disposition = response.headers["content-disposition"] || "";
const headerFileName = disposition
? decodeURIComponent(
disposition.split("filename*=UTF-8''")[1] ||
disposition.split("filename=")[1] ||
""
).replace(/['"]/g, "")
: "";
// 创建下载链接
const blob = new Blob([response.data]);
const link = document.createElement("a");
link.href = window.URL.createObjectURL(blob);
link.download =
fileName ||
headerFileName ||
`download_${Date.now()}.xlsx`;
link.click();
// 清理
setTimeout(() => {
window.URL.revokeObjectURL(link.href);
}, 100);
return response;
}
// ==================== 封装请求方法 ====================
const request = {
/**
* GET 请求
* @param {string} url - 请求地址
* @param {object} params - URL 参数
* @param {object} config - Axios 配置
*/
get(url, params = {}, config = {}) {
return service({
method: "get",
url,
params,
...config,
});
},
/**
* POST 请求
* @param {string} url - 请求地址
* @param {object} data - 请求体数据
* @param {object} config - Axios 配置
*/
post(url, data = {}, config = {}) {
return service({
method: "post",
url,
data,
...config,
});
},
/**
* PUT 请求
*/
put(url, data = {}, config = {}) {
return service({
method: "put",
url,
data,
...config,
});
},
/**
* DELETE 请求
*/
delete(url, params = {}, config = {}) {
return service({
method: "delete",
url,
params,
...config,
});
},
/**
* PATCH 请求
*/
patch(url, data = {}, config = {}) {
return service({
method: "patch",
url,
data,
...config,
});
},
/**
* 文件上传
* @param {string} url - 上传地址
* @param {FormData} formData - 表单数据
* @param {object} config - 配置
*/
upload(url, formData, config = {}) {
return service({
method: "post",
url,
data: formData,
headers: {
"Content-Type": "multipart/form-data",
...(config.headers || {}),
},
cancelDuplicate: false, // 上传不取消重复请求
...config,
});
},
/**
* 文件下载
* @param {string} url - 下载地址
* @param {object} data - 请求数据
* @param {string} fileName - 文件名
* @param {object} config - 配置
*/
download(url, data = {}, fileName = "", config = {}) {
return service({
method: "post",
url,
data,
responseType: "blob",
cancelDuplicate: false, // 下载不取消重复请求
...config,
})
.then((response) =>
handleDownloadResponse(response, fileName, config.showError !== false)
)
.catch((error) => {
if (config.showError !== false && !axios.isCancel(error)) {
Message.error("下载失败!");
}
return Promise.reject(error);
});
},
/**
* 表单提交 (application/x-www-form-urlencoded)
*/
form(url, data = {}, config = {}) {
return service({
method: "post",
url,
data,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
...(config.headers || {}),
},
...config,
});
},
/**
* 自定义请求
* @param {string} method - 请求方法
* @param {string} url - 请求地址
* @param {object} data - 请求体数据
* @param {object} params - URL 参数
* @param {object} config - 配置
*/
custom(method, url, data = {}, params = {}, config = {}) {
return service({
method,
url,
data,
params,
...config,
});
},
/**
* 取消所有进行中的请求
*/
cancelAll() {
requestManager.clearAll();
},
};
export default request;
export { service, requestManager };
主要改进点
1. 代码结构更清晰
- 使用类封装相关功能,职责划分明确
- 配置集中管理,便于维护
- 代码分层更合理
2. 安全性提升
- 生产环境不输出敏感日志
- Token 管理更规范
- 错误信息标准化
3. 可维护性增强
- JSDoc 注释完善
- 错误消息统一管理
- 配置可扩展性强
4. 功能更完善
- 支持手动取消所有请求
- 文件下载处理更健壮
- 默认 Content-Type 设置
5. 调试更友好
- 开发环境详细日志
- 请求/响应完整追踪
- 错误定位更精准
使用示例
基础使用
js
import request from '@/utils/request'
// ==================== GET 请求 ====================
export function getUserList(params) {
return request.get('/api/users', params)
}
// 自定义配置
export function getUserListNoAuth(params) {
return request.get('/api/users', params, {
needToken: false, // 不需要 token
showError: false, // 不显示错误提示
})
}
// ==================== POST 请求 ====================
export function createUser(data) {
return request.post('/api/users', data)
}
// 禁用重复请求取消
export function sendOtp(data) {
return request.post('/api/send-otp', data, {
cancelDuplicate: false, // 允许重复发送
})
}
// ==================== PUT/DELETE 请求 ====================
export function updateUser(id, data) {
return request.put(`/api/users/${id}`, data)
}
export function deleteUser(id) {
return request.delete(`/api/users/${id}`)
}
// ==================== 表单提交 ====================
export function login(data) {
return request.form('/api/login', data)
}
// ==================== 文件上传 ====================
export function uploadAvatar(file) {
const formData = new FormData()
formData.append('file', file)
formData.append('type', 'avatar')
return request.upload('/api/upload', formData, {
onUploadProgress: (progressEvent) => {
const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total)
console.log('上传进度:', percent + '%')
}
})
}
// ==================== 文件下载 ====================
export function exportUsers(params) {
return request.download('/api/users/export', params, '用户列表')
}
// 自定义文件名
export function exportReport(params) {
const fileName = `报表_${new Date().toLocaleDateString()}`
return request.download('/api/report/export', params, fileName)
}
// ==================== 自定义 Authorization ====================
export function callThirdPartyApi(data) {
return request.post('/api/third-party', data, {
headers: {
'Authorization': 'Bearer your-custom-token',
'X-Api-Key': 'your-api-key'
}
})
}
在 Vue 组件中使用
js
<template>
<div>
<!-- 用户列表 -->
<el-table :data="userList" v-loading="loading">
<el-table-column prop="name" label="姓名" />
<el-table-column prop="email" label="邮箱" />
<el-table-column label="操作">
<template slot-scope="{ row }">
<el-button @click="handleEdit(row)">编辑</el-button>
<el-button @click="handleDelete(row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 文件上传 -->
<el-upload
action="#"
:http-request="handleUpload"
:before-upload="beforeUpload"
>
<el-button>上传头像</el-button>
</el-upload>
<!-- 导出按钮 -->
<el-button @click="handleExport">导出数据</el-button>
</div>
</template>
<script>
import { getUserList, deleteUser, uploadAvatar, exportUsers } from '@/api/user'
export default {
data() {
return {
userList: [],
loading: false,
queryParams: {
page: 1,
size: 10
}
}
},
created() {
this.fetchUsers()
},
methods: {
// 获取用户列表
async fetchUsers() {
try {
this.loading = true
const res = await getUserList(this.queryParams)
this.userList = res.data
} catch (error) {
// 错误已在拦截器中处理
console.error('获取用户列表失败', error)
} finally {
this.loading = false
}
},
// 删除用户
async handleDelete(id) {
try {
await this.$confirm('确认删除该用户?', '提示', {
type: 'warning'
})
await deleteUser(id)
this.$message.success('删除成功')
this.fetchUsers()
} catch (error) {
if (error !== 'cancel') {
console.error('删除失败', error)
}
}
},
// 上传前验证
beforeUpload(file) {
const isImage = file.type.startsWith('image/')
const isLt2M = file.size / 1024 / 1024 < 2
if (!isImage) {
this.$message.error('只能上传图片文件!')
return false
}
if (!isLt2M) {
this.$message.error('图片大小不能超过 2MB!')
return false
}
return true
},
// 自定义上传
async handleUpload({ file }) {
try {
const res = await uploadAvatar(file)
this.$message.success('上传成功')
console.log('文件地址:', res.data.url)
} catch (error) {
console.error('上传失败', error)
}
},
// 导出数据
async handleExport() {
try {
await exportUsers(this.queryParams)
// 下载成功后无需额外处理
} catch (error) {
console.error('导出失败', error)
}
}
},
// 组件销毁时取消所有请求
beforeDestroy() {
// 如果需要取消所有请求
// request.cancelAll()
}
}
</script>
高级用法
js
import request from '@/utils/request'
// ==================== 并发请求 ====================
export async function fetchDashboardData() {
try {
const [users, orders, statistics] = await Promise.all([
request.get('/api/users'),
request.get('/api/orders'),
request.get('/api/statistics')
])
return {
users: users.data,
orders: orders.data,
statistics: statistics.data
}
} catch (error) {
console.error('获取数据失败', error)
throw error
}
}
// ==================== 串行请求(有依赖关系)====================
export async function createOrderWithDetails(orderData, itemsData) {
try {
// 先创建订单
const orderRes = await request.post('/api/orders', orderData)
const orderId = orderRes.data.id
// 再添加订单明细
const itemsRes = await request.post('/api/order-items', {
orderId,
items: itemsData
})
return {
order: orderRes.data,
items: itemsRes.data
}
} catch (error) {
console.error('创建订单失败', error)
throw error
}
}
// ==================== 轮询请求 ====================
export function pollTaskStatus(taskId, interval = 2000, maxAttempts = 30) {
let attempts = 0
return new Promise((resolve, reject) => {
const timer = setInterval(async () => {
attempts++
try {
const res = await request.get(`/api/tasks/${taskId}`, {}, {
showError: false // 轮询不显示错误
})
if (res.data.status === 'completed') {
clearInterval(timer)
resolve(res.data)
} else if (res.data.status === 'failed') {
clearInterval(timer)
reject(new Error('任务执行失败'))
} else if (attempts >= maxAttempts) {
clearInterval(timer)
reject(new Error('轮询超时'))
}
} catch (error) {
clearInterval(timer)
reject(error)
}
}, interval)
})
}
// ==================== 重试机制 ====================
export async function requestWithRetry(fn, maxRetries = 3) {
let lastError
for (let i = 0; i < maxRetries; i++) {
try {
return await fn()
} catch (error) {
lastError = error
console.log(`请求失败,第 ${i + 1} 次重试...`)
// 等待一段时间再重试
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)))
}
}
throw lastError
}
// 使用示例
export function fetchImportantData() {
return requestWithRetry(() => request.get('/api/important-data'))
}
// ==================== 请求队列 ====================
class RequestQueue {
constructor(concurrency = 3) {
this.concurrency = concurrency
this.running = 0
this.queue = []
}
async add(fn) {
if (this.running >= this.concurrency) {
await new Promise(resolve => this.queue.push(resolve))
}
this.running++
try {
return await fn()
} finally {
this.running--
const resolve = this.queue.shift()
if (resolve) resolve()
}
}
}
// 批量上传文件(限制并发数)
export async function batchUpload(files) {
const queue = new RequestQueue(3) // 最多同时上传 3 个
const results = []
for (const file of files) {
const result = await queue.add(() => uploadAvatar(file))
results.push(result)
}
return results
}
全局配置和扩展
js
import request, { service, requestManager } from '@/utils/request'
// ==================== 全局请求前置处理 ====================
export function setupRequestInterceptor() {
// 添加请求追踪
service.interceptors.request.use(config => {
config.metadata = { startTime: Date.now() }
return config
})
// 添加响应时间统计
service.interceptors.response.use(
response => {
const duration = Date.now() - response.config.metadata.startTime
console.log(`请求耗时: ${duration}ms`)
return response
},
error => {
if (error.config?.metadata) {
const duration = Date.now() - error.config.metadata.startTime
console.log(`请求失败耗时: ${duration}ms`)
}
return Promise.reject(error)
}
)
}
// ==================== 路由守卫中取消请求 ====================
export function setupRouterCancelRequests(router) {
router.beforeEach((to, from, next) => {
// 路由切换时取消所有进行中的请求
requestManager.clearAll()
next()
})
}
// ==================== 在 main.js 中使用 ====================
// import { setupRequestInterceptor, setupRouterCancelRequests } from '@/utils/request-extension'
// setupRequestInterceptor()
// setupRouterCancelRequests(router)
