Axios 完整实战教程
本教程使用最新JavaScript语法,每个案例独立HTML文件,配合Mermaid图表详细讲解
目录
第一部分:基础概念
第二部分:后端开发
第三部分:前端请求(Axios)
第一章:HTTP协议详解
1.1 HTTP协议概述
HTTP(HyperText Transfer Protocol)是超文本传输协议,是Web浏览器与服务器之间通信的基础协议。
HTTP工作原理
服务器:3000 浏览器 服务器:3000 浏览器 GET /users HTTP/1.1 Host: localhost {name: "李昊哲"} HTTP/1.1 200 OK Content-Type: json {success: true} HTTP请求 HTTP响应
HTTP请求与响应结构
📥 HTTP响应
HTTP/1.1 200 OK
Content-Type: application/json
{success: true}
📤 HTTP请求
GET /users HTTP/1.1
Host: localhost
Content-Type: application/json
{name: '李昊哲'}
1.2 HTTP请求方法
方法对比表
| 方法 | 用途 | 请求体 | 响应状态码 | 幂等性 |
|---|---|---|---|---|
| GET | 查询数据 | 无 | 200 | 是 |
| POST | 新增数据 | 有 | 201 | 否 |
| PUT | 完整更新 | 有 | 200 | 是 |
| PATCH | 部分更新 | 有 | 200 | 否 |
| DELETE | 删除数据 | 无 | 204 | 是 |
RESTful流程图
数据库 Express API 浏览器 数据库 Express API 浏览器 GET /users 获取用户列表 SELECT * FROM users [用户1, 用户2, ...] 200 OK + JSON数据 POST /users 创建用户 INSERT INTO users VALUES(...) 新用户ID 201 Created + 新用户数据 GET /users/1 获取ID=1用户 SELECT * FROM users WHERE id=1 {id:1, name:"张三"} 200 OK + 用户数据 PUT /users/1 更新用户 UPDATE users SET ... 更新后的用户 200 OK + 更新后数据 DELETE /users/1 删除用户 DELETE FROM users WHERE id=1 影响行数: 1 204 No Content
1.3 HTTP状态码详解
常见状态码
| 状态码 | 含义 | 常见场景 |
|---|---|---|
| 200 | 成功 | GET/PUT/PATCH成功 |
| 201 | 已创建 | POST创建资源成功 |
| 204 | 无内容 | DELETE成功,无返回体 |
| 400 | 请求错误 | 参数校验失败 |
| 401 | 未授权 | 未登录或Token过期 |
| 404 | 资源不存在 | 请求的ID不存在 |
| 500 | 服务器错误 | 后端代码异常 |
第二章:Express后端开发
2.1 环境搭建
安装命令
bash
mkdir axios-express-tutorial
cd axios-express-tutorial
npm init -y
npm install express@5.2.1 cors body-parser dotenv
npm install -D nodemon
2.2 完整server.js代码
javascript
// server.js
import express from 'express';
import cors from 'cors';
import { Router } from 'express';
import multer from 'multer';
import path from 'path';
import fs from 'fs';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const app = express();
const PORT = 3000;
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, 'public')));
const router = Router();
const users = [
{ id: 1, name: '张三', email: 'zhangsan@example.com', age: 25 },
{ id: 2, name: '李四', email: 'lisi@example.com', age: 30 },
{ id: 3, name: '王五', email: 'wangwu@example.com', age: 28 }
];
const storage = multer.diskStorage({
destination: (req, file, cb) => {
const uploadDir = 'uploads/';
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, { recursive: true });
}
cb(null, uploadDir);
},
filename: (req, file, cb) => {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
const ext = path.extname(file.originalname);
cb(null, `file-${uniqueSuffix}${ext}`);
}
});
const upload = multer({ storage, limits: { fileSize: 10 * 1024 * 1024 } });
router.get('/users', (req, res) => {
res.json({ success: true, data: users });
});
router.get('/users/:id', (req, res) => {
const id = parseInt(req.params.id);
const user = users.find(u => u.id === id);
if (!user) return res.status(404).json({ success: false, error: '用户不存在' });
res.json({ success: true, data: user });
});
router.post('/users', (req, res) => {
const { name, email, age } = req.body;
if (!name || !email) {
return res.status(400).json({ success: false, error: '姓名和邮箱不能为空' });
}
const newUser = {
id: users.length > 0 ? Math.max(...users.map(u => u.id)) + 1 : 1,
name, email, age: parseInt(age) || 0
};
users.push(newUser);
res.status(201).json({ success: true, data: newUser });
});
router.put('/users/:id', (req, res) => {
const id = parseInt(req.params.id);
const user = users.find(u => u.id === id);
if (!user) return res.status(404).json({ success: false, error: '用户不存在' });
const { name, email, age } = req.body;
user.name = name || user.name;
user.email = email || user.email;
user.age = parseInt(age) || user.age;
res.json({ success: true, data: user });
});
router.patch('/users/:id', (req, res) => {
const id = parseInt(req.params.id);
const user = users.find(u => u.id === id);
if (!user) return res.status(404).json({ success: false, error: '用户不存在' });
Object.assign(user, req.body);
res.json({ success: true, data: user });
});
router.delete('/users/:id', (req, res) => {
const id = parseInt(req.params.id);
const index = users.findIndex(u => u.id === id);
if (index === -1) return res.status(404).json({ success: false, error: '用户不存在' });
users.splice(index, 1);
res.status(204).json({ success: true });
});
router.post('/login', (req, res) => {
const { username, password } = req.body;
if (username === 'admin' && password === '123456') {
const token = 'jwt-token-' + Date.now();
res.json({ success: true, data: { token, user: { id: 1, username: 'admin', nickname: '管理员' } } });
} else {
res.status(401).json({ success: false, error: '账号或密码错误' });
}
});
router.get('/profile', (req, res) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ success: false, error: '未授权' });
}
res.json({ success: true, data: { id: 1, username: 'admin', nickname: '管理员' } });
});
router.post('/upload', upload.single('file'), (req, res) => {
if (!req.file) return res.status(400).json({ success: false, error: '没有上传文件' });
res.json({
success: true,
data: {
filename: req.file.filename,
originalName: req.file.originalname,
size: req.file.size,
path: `/uploads/${req.file.filename}`
}
});
});
app.use('/api', router);
app.listen(PORT, () => {
console.log(`服务器运行在 http://localhost:${PORT}`);
});
第三章:Axios基础用法
3.1 安装与引入
CDN引入(浏览器环境)
html
<script src="https://cdn.jsdelivr.net/npm/axios@1.15.0/dist/axios.min.js"></script>
npm安装(Node.js/模块化环境)
bash
npm install axios
javascript
// ES6模块化导入
import axios from 'axios';
// 或按需导入
import axios, { isAxiosError, AxiosError } from 'axios';
3.2 基础请求方法
GET请求 - 获取数据
javascript
// 方式1:简洁写法
const res = await axios.get('http://localhost:3000/api/users');
// 方式2:带查询参数
const res = await axios.get('http://localhost:3000/api/users', {
params: {
page: 1,
limit: 10,
keyword: '张三'
}
});
// 方式3:完整配置
const res = await axios({
method: 'get',
url: 'http://localhost:3000/api/users',
params: { page: 1 }
});
POST请求 - 创建数据
javascript
// 方式1:简洁写法
const res = await axios.post('http://localhost:3000/api/users', {
name: '张三',
email: 'zhangsan@example.com'
});
// 方式2:带配置选项
const res = await axios.post('http://localhost:3000/api/users', userData, {
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token'
},
timeout: 5000
});
PUT/PATCH请求 - 更新数据
javascript
// PUT - 完整更新
const res = await axios.put(`http://localhost:3000/api/users/${id}`, {
name: '张三',
email: 'zhangsan@example.com',
age: 25
});
// PATCH - 部分更新
const res = await axios.patch(`http://localhost:3000/api/users/${id}`, {
age: 26 // 只更新年龄
});
DELETE请求 - 删除数据
javascript
// 方式1:简洁写法
await axios.delete(`http://localhost:3000/api/users/${id}`);
// 方式2:带请求体(部分服务器支持)
await axios.delete('http://localhost:3000/api/users', {
data: { ids: [1, 2, 3] } // 批量删除
});
3.3 请求配置选项
常用配置项
javascript
const res = await axios({
// URL相关
url: '/users',
method: 'get', // 默认: get
baseURL: 'http://localhost:3000/api',
// 数据相关
params: { page: 1 }, // URL查询参数
data: { name: '张三' }, // 请求体数据
// 配置相关
headers: { 'X-Requested-With': 'XMLHttpRequest' },
timeout: 10000, // 超时时间(毫秒)
// 响应处理
responseType: 'json', // 响应数据类型: json/arraybuffer/blob/text/stream
// 跨域配置
withCredentials: false // 是否携带cookie
});
响应对象结构
javascript
const res = await axios.get('/api/users');
console.log(res.data); // 服务器返回的数据
console.log(res.status); // HTTP状态码 (200, 404, etc.)
console.log(res.statusText); // 状态文本 ('OK', 'Not Found')
console.log(res.headers); // 响应头
console.log(res.config); // 请求配置
console.log(res.request); // 原生请求对象
错误处理基础
javascript
try {
const res = await axios.get('/api/users');
} catch (error) {
if (error.response) {
// 服务器响应了错误状态码 (4xx, 5xx)
console.log('状态码:', error.response.status);
console.log('错误数据:', error.response.data);
} else if (error.request) {
// 请求已发送但没有收到响应
console.log('无响应:', error.request);
} else {
// 请求配置出错
console.log('错误:', error.message);
}
}
第四章:Axios实战案例
4.1 案例一:用户列表查询(GET)
流程图
是
否
打开HTML页面
页面加载完成后
调用axios.get发送GET请求
请求成功?
渲染用户列表到表格
显示错误提示
前端HTML
html
<!-- public/01-get-users.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>用户列表查询 - GET请求</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', sans-serif; background: #f5f7fa; padding: 20px; }
.container { max-width: 900px; margin: 0 auto; }
h1 { color: #1a1a2e; margin-bottom: 20px; text-align: center; }
.card { background: white; border-radius: 12px; padding: 24px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); margin-bottom: 20px; }
.method-badge { display: inline-block; background: #10b981; color: white; padding: 4px 12px; border-radius: 4px; font-size: 12px; font-weight: bold; }
table { width: 100%; border-collapse: collapse; margin-top: 16px; }
th, td { padding: 12px; text-align: left; border-bottom: 1px solid #e5e7eb; }
th { background: #f8fafc; font-weight: 600; color: #374151; }
tr:hover { background: #f9fafb; }
.btn { padding: 6px 14px; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; transition: all 0.2s; }
.btn-delete { background: #ef4444; color: white; }
.btn-delete:hover { background: #dc2626; }
.loading { text-align: center; padding: 40px; color: #6b7280; }
.error { background: #fef2f2; color: #dc2626; padding: 12px; border-radius: 8px; margin-top: 16px; }
</style>
</head>
<body>
<div class="container">
<h1>用户列表查询 <span class="method-badge">GET</span></h1>
<div class="card">
<h2 style="margin-bottom:12px;">📋 用户列表</h2>
<div id="loading" class="loading">加载中...</div>
<div id="error" class="error" style="display:none;"></div>
<table id="userTable" style="display:none;">
<thead>
<tr><th>ID</th><th>姓名</th><th>邮箱</th><th>年龄</th><th>操作</th></tr>
</thead>
<tbody id="userList"></tbody>
</table>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/axios@1.15.0/dist/axios.min.js"></script>
<script>
const API_BASE = 'http://localhost:3000/api';
async function loadUsers() {
const loadingEl = document.getElementById('loading');
const errorEl = document.getElementById('error');
const tableEl = document.getElementById('userTable');
const listEl = document.getElementById('userList');
try {
const res = await axios.get(`${API_BASE}/users`);
loadingEl.style.display = 'none';
tableEl.style.display = 'table';
listEl.innerHTML = res.data.map(user => `
<tr>
<td>${user.id}</td>
<td>${user.name}</td>
<td>${user.email}</td>
<td>${user.age}</td>
<td><button class="btn btn-delete" onclick="deleteUser(${user.id})">删除</button></td>
</tr>
`).join('');
} catch (err) {
loadingEl.style.display = 'none';
errorEl.style.display = 'block';
errorEl.textContent = `加载失败: ${err.response?.data?.error || err.message}`;
}
}
async function deleteUser(id) {
if (!confirm('确定要删除此用户吗?')) return;
try {
await axios.delete(`${API_BASE}/users/${id}`);
loadUsers();
} catch (err) {
alert(`删除失败: ${err.response?.data?.error || err.message}`);
}
}
loadUsers();
</script>
</body>
</html>
4.2 案例二:用户创建(POST)
流程图
否
是
是
否
用户填写表单
点击提交按钮
前端校验表单
校验通过?
显示校验错误
调用axios.post
请求成功?
显示成功提示
显示错误提示
前端HTML
html
<!-- public/02-create-user.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>用户创建 - POST请求</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', sans-serif; background: #f5f7fa; padding: 20px; }
.container { max-width: 500px; margin: 0 auto; }
h1 { color: #1a1a2e; margin-bottom: 20px; text-align: center; }
.card { background: white; border-radius: 12px; padding: 24px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
.method-badge { display: inline-block; background: #f59e0b; color: white; padding: 4px 12px; border-radius: 4px; font-size: 12px; font-weight: bold; }
.form-group { margin-bottom: 16px; }
label { display: block; margin-bottom: 6px; font-weight: 500; color: #374151; }
input { width: 100%; padding: 10px 12px; border: 1px solid #d1d5db; border-radius: 6px; font-size: 14px; transition: border-color 0.2s; }
input:focus { outline: none; border-color: #3b82f6; }
.btn { width: 100%; padding: 12px; background: #3b82f6; color: white; border: none; border-radius: 6px; font-size: 16px; cursor: pointer; transition: background 0.2s; }
.btn:hover { background: #2563eb; }
.message { padding: 12px; border-radius: 8px; margin-top: 16px; text-align: center; }
.success { background: #d1fae5; color: #065f46; }
.error { background: #fee2e2; color: #991b1b; }
</style>
</head>
<body>
<div class="container">
<h1>用户创建 <span class="method-badge">POST</span></h1>
<div class="card">
<form id="userForm">
<div class="form-group">
<label>姓名</label>
<input type="text" name="name" placeholder="请输入姓名" required>
</div>
<div class="form-group">
<label>邮箱</label>
<input type="email" name="email" placeholder="请输入邮箱" required>
</div>
<div class="form-group">
<label>年龄</label>
<input type="number" name="age" placeholder="请输入年龄" min="1" max="150">
</div>
<button type="submit" class="btn">创建用户</button>
</form>
<div id="message" class="message" style="display:none;"></div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/axios@1.15.0/dist/axios.min.js"></script>
<script>
const API_BASE = 'http://localhost:3000/api';
const form = document.getElementById('userForm');
const messageEl = document.getElementById('message');
form.addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(form);
const data = Object.fromEntries(formData.entries());
showMessage('创建中...', 'info');
try {
const res = await axios.post(`${API_BASE}/users`, data);
showMessage(`用户 "${res.data.data.name}" 创建成功!`, 'success');
form.reset();
} catch (err) {
showMessage(`创建失败: ${err.response?.data?.error || err.message}`, 'error');
}
});
function showMessage(text, type) {
messageEl.textContent = text;
messageEl.className = `message ${type}`;
messageEl.style.display = 'block';
}
</script>
</body>
</html>
4.3 案例三:用户更新(PUT/PATCH)
流程图
是
否
点击编辑按钮
弹出编辑对话框
显示当前用户数据
用户修改数据
点击保存按钮
调用axios.patch
请求成功?
关闭对话框并刷新列表
显示错误提示
前端HTML
html
<!-- public/03-update-user.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>用户更新 - PUT/PATCH请求</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', sans-serif; background: #f5f7fa; padding: 20px; }
.container { max-width: 900px; margin: 0 auto; }
h1 { color: #1a1a2e; margin-bottom: 20px; text-align: center; }
.card { background: white; border-radius: 12px; padding: 24px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); margin-bottom: 20px; }
.method-badges { display: flex; gap: 8px; margin-bottom: 16px; }
.method-badge { padding: 4px 12px; border-radius: 4px; font-size: 12px; font-weight: bold; color: white; }
.badge-put { background: #8b5cf6; }
.badge-patch { background: #06b6d4; }
table { width: 100%; border-collapse: collapse; }
th, td { padding: 12px; text-align: left; border-bottom: 1px solid #e5e7eb; }
th { background: #f8fafc; font-weight: 600; color: #374151; }
tr:hover { background: #f9fafb; }
.btn { padding: 6px 14px; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; transition: all 0.2s; }
.btn-edit { background: #3b82f6; color: white; }
.btn-edit:hover { background: #2563eb; }
.modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); justify-content: center; align-items: center; }
.modal.active { display: flex; }
.modal-content { background: white; border-radius: 12px; padding: 24px; width: 400px; max-width: 90%; }
.modal h2 { margin-bottom: 16px; }
.form-group { margin-bottom: 12px; }
label { display: block; margin-bottom: 4px; font-weight: 500; }
input { width: 100%; padding: 8px 12px; border: 1px solid #d1d5db; border-radius: 6px; }
.modal-btns { display: flex; gap: 8px; margin-top: 16px; }
.btn-cancel { flex: 1; padding: 10px; background: #9ca3af; color: white; border: none; border-radius: 6px; cursor: pointer; }
.btn-save { flex: 1; padding: 10px; background: #10b981; color: white; border: none; border-radius: 6px; cursor: pointer; }
.error { background: #fef2f2; color: #dc2626; padding: 12px; border-radius: 8px; margin-bottom: 16px; }
.success { background: #d1fae5; color: #065f46; padding: 12px; border-radius: 8px; margin-bottom: 16px; }
</style>
</head>
<body>
<div class="container">
<h1>用户更新 <span class="method-badges"><span class="method-badge badge-put">PUT</span><span class="method-badge badge-patch">PATCH</span></span></h1>
<div class="card">
<div id="error" class="error" style="display:none;"></div>
<div id="success" class="success" style="display:none;"></div>
<table>
<thead>
<tr><th>ID</th><th>姓名</th><th>邮箱</th><th>年龄</th><th>操作</th></tr>
</thead>
<tbody id="userList"></tbody>
</table>
</div>
</div>
<div id="editModal" class="modal">
<div class="modal-content">
<h2>编辑用户</h2>
<form id="editForm">
<input type="hidden" name="id">
<div class="form-group">
<label>姓名</label>
<input type="text" name="name" required>
</div>
<div class="form-group">
<label>邮箱</label>
<input type="email" name="email" required>
</div>
<div class="form-group">
<label>年龄</label>
<input type="number" name="age" min="1">
</div>
<div class="modal-btns">
<button type="button" class="btn-cancel" onclick="closeModal()">取消</button>
<button type="submit" class="btn-save">保存</button>
</div>
</form>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/axios@1.15.0/dist/axios.min.js"></script>
<script>
const API_BASE = 'http://localhost:3000/api';
let editingUser = null;
async function loadUsers() {
try {
const res = await axios.get(`${API_BASE}/users`);
const tbody = document.getElementById('userList');
tbody.innerHTML = res.data.map(user => `
<tr>
<td>${user.id}</td>
<td>${user.name}</td>
<td>${user.email}</td>
<td>${user.age}</td>
<td><button class="btn btn-edit" onclick="openEditModal(${user.id})">编辑</button></td>
</tr>
`).join('');
} catch (err) {
showError(`加载失败: ${err.message}`);
}
}
async function openEditModal(id) {
try {
const res = await axios.get(`${API_BASE}/users/${id}`);
editingUser = res.data.data;
document.querySelector('#editForm input[name="id"]').value = editingUser.id;
document.querySelector('#editForm input[name="name"]').value = editingUser.name;
document.querySelector('#editForm input[name="email"]').value = editingUser.email;
document.querySelector('#editForm input[name="age"]').value = editingUser.age;
document.getElementById('editModal').classList.add('active');
} catch (err) {
showError(`获取用户失败: ${err.message}`);
}
}
function closeModal() {
document.getElementById('editModal').classList.remove('active');
editingUser = null;
}
document.getElementById('editForm').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const data = Object.fromEntries(formData.entries());
try {
await axios.patch(`${API_BASE}/users/${data.id}`, data);
closeModal();
loadUsers();
showSuccess('用户更新成功!');
} catch (err) {
showError(`更新失败: ${err.response?.data?.error || err.message}`);
}
});
function showError(msg) {
const el = document.getElementById('error');
el.textContent = msg;
el.style.display = 'block';
setTimeout(() => el.style.display = 'none', 3000);
}
function showSuccess(msg) {
const el = document.getElementById('success');
el.textContent = msg;
el.style.display = 'block';
setTimeout(() => el.style.display = 'none', 3000);
}
loadUsers();
</script>
</body>
</html>
4.4 案例四:用户删除(DELETE)
流程图
取消
确认
是
否
点击删除按钮
确认删除?
不做任何操作
调用axios.delete
请求成功?
显示删除成功提示并刷新列表
显示错误提示
前端HTML
html
<!-- public/04-delete-user.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>用户删除 - DELETE请求</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', sans-serif; background: #f5f7fa; padding: 20px; }
.container { max-width: 900px; margin: 0 auto; }
h1 { color: #1a1a2e; margin-bottom: 20px; text-align: center; }
.card { background: white; border-radius: 12px; padding: 24px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); margin-bottom: 20px; }
.method-badge { display: inline-block; background: #ef4444; color: white; padding: 4px 12px; border-radius: 4px; font-size: 12px; font-weight: bold; }
table { width: 100%; border-collapse: collapse; }
th, td { padding: 12px; text-align: left; border-bottom: 1px solid #e5e7eb; }
th { background: #f8fafc; font-weight: 600; color: #374151; }
tr:hover { background: #f9fafb; }
.btn { padding: 6px 14px; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; transition: all 0.2s; }
.btn-delete { background: #ef4444; color: white; }
.btn-delete:hover { background: #dc2626; }
.loading { text-align: center; padding: 40px; color: #6b7280; }
.toast { position: fixed; top: 20px; right: 20px; padding: 12px 20px; border-radius: 8px; color: white; font-weight: 500; opacity: 0; transform: translateX(100%); transition: all 0.3s; }
.toast.show { opacity: 1; transform: translateX(0); }
.toast.success { background: #10b981; }
.toast.error { background: #ef4444; }
</style>
</head>
<body>
<div class="container">
<h1>用户删除 <span class="method-badge">DELETE</span></h1>
<div class="card">
<div id="loading" class="loading">加载中...</div>
<table id="userTable" style="display:none;">
<thead>
<tr><th>ID</th><th>姓名</th><th>邮箱</th><th>年龄</th><th>操作</th></tr>
</thead>
<tbody id="userList"></tbody>
</table>
</div>
</div>
<div id="toast" class="toast"></div>
<script src="https://cdn.jsdelivr.net/npm/axios@1.15.0/dist/axios.min.js"></script>
<script>
const API_BASE = 'http://localhost:3000/api';
async function loadUsers() {
const loadingEl = document.getElementById('loading');
const tableEl = document.getElementById('userTable');
try {
const res = await axios.get(`${API_BASE}/users`);
loadingEl.style.display = 'none';
tableEl.style.display = 'table';
const tbody = document.getElementById('userList');
tbody.innerHTML = res.data.map(user => `
<tr>
<td>${user.id}</td>
<td>${user.name}</td>
<td>${user.email}</td>
<td>${user.age}</td>
<td><button class="btn btn-delete" onclick="deleteUser(${user.id}, '${user.name}')">删除</button></td>
</tr>
`).join('');
} catch (err) {
loadingEl.textContent = `加载失败: ${err.message}`;
}
}
async function deleteUser(id, name) {
if (!confirm(`确定要删除用户 "${name}" 吗?此操作不可撤销。`)) return;
try {
await axios.delete(`${API_BASE}/users/${id}`);
showToast('删除成功!', 'success');
loadUsers();
} catch (err) {
showToast(`删除失败: ${err.response?.data?.error || err.message}`, 'error');
}
}
function showToast(message, type) {
const toast = document.getElementById('toast');
toast.textContent = message;
toast.className = `toast ${type} show`;
setTimeout(() => toast.classList.remove('show'), 3000);
}
loadUsers();
</script>
</body>
</html>
4.5 案例五:用户登录(POST + JWT)
流程图
是
否
用户输入账号密码
点击登录按钮
调用axios.post发送登录请求
请求成功?
保存Token到localStorage
显示错误提示
跳转到用户列表页面
前端HTML
html
<!-- public/05-login.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>用户登录 - POST请求</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; display: flex; justify-content: center; align-items: center; }
.login-card { background: white; border-radius: 16px; padding: 40px; width: 400px; max-width: 90%; box-shadow: 0 20px 60px rgba(0,0,0,0.3); }
h1 { text-align: center; color: #1a1a2e; margin-bottom: 8px; }
.subtitle { text-align: center; color: #6b7280; margin-bottom: 30px; }
.form-group { margin-bottom: 20px; }
label { display: block; margin-bottom: 8px; font-weight: 500; color: #374151; }
input { width: 100%; padding: 12px 16px; border: 2px solid #e5e7eb; border-radius: 8px; font-size: 16px; transition: border-color 0.2s; }
input:focus { outline: none; border-color: #667eea; }
.btn { width: 100%; padding: 14px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; transition: transform 0.2s; }
.btn:hover { transform: translateY(-2px); }
.btn:disabled { opacity: 0.7; cursor: not-allowed; }
.message { padding: 12px; border-radius: 8px; margin-top: 16px; text-align: center; }
.error { background: #fee2e2; color: #991b1b; }
.success { background: #d1fae5; color: #065f46; }
.demo-hint { margin-top: 20px; padding: 12px; background: #f3f4f6; border-radius: 8px; font-size: 13px; color: #6b7280; }
.demo-hint strong { color: #374151; }
</style>
</head>
<body>
<div class="login-card">
<h1>用户登录</h1>
<p class="subtitle">请输入账号和密码</p>
<form id="loginForm">
<div class="form-group">
<label>账号</label>
<input type="text" name="username" placeholder="请输入账号" required value="admin">
</div>
<div class="form-group">
<label>密码</label>
<input type="password" name="password" placeholder="请输入密码" required value="123456">
</div>
<button type="submit" class="btn">登 录</button>
</form>
<div id="message" class="message" style="display:none;"></div>
<div class="demo-hint">
<strong>测试账号:</strong>admin / 123456
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/axios@1.15.0/dist/axios.min.js"></script>
<script>
const API_BASE = 'http://localhost:3000/api';
const form = document.getElementById('loginForm');
const messageEl = document.getElementById('message');
form.addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(form);
const data = Object.fromEntries(formData.entries());
const btn = form.querySelector('.btn');
btn.disabled = true;
btn.textContent = '登录中...';
hideMessage();
try {
const res = await axios.post(`${API_BASE}/login`, data);
if (res.data.success) {
localStorage.setItem('token', res.data.data.token);
localStorage.setItem('user', JSON.stringify(res.data.data.user));
showMessage('登录成功!正在跳转...', 'success');
setTimeout(() => { window.location.href = '01-get-users.html'; }, 1000);
}
} catch (err) {
showMessage(err.response?.data?.error || '登录失败', 'error');
btn.disabled = false;
btn.textContent = '登 录';
}
});
function showMessage(text, type) {
messageEl.textContent = text;
messageEl.className = `message ${type}`;
messageEl.style.display = 'block';
}
function hideMessage() { messageEl.style.display = 'none'; }
</script>
</body>
</html>
4.6 案例六:文件上传(POST + FormData)
流程图
成功
失败
选择文件
点击上传按钮
创建FormData对象
调用axios.post发送FormData
显示上传进度
上传完成?
显示文件信息
显示错误信息
前端HTML
html
<!-- public/06-upload.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件上传 - POST请求</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', sans-serif; background: #f5f7fa; padding: 20px; }
.container { max-width: 600px; margin: 0 auto; }
h1 { color: #1a1a2e; margin-bottom: 20px; text-align: center; }
.card { background: white; border-radius: 12px; padding: 24px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
.method-badge { display: inline-block; background: #8b5cf6; color: white; padding: 4px 12px; border-radius: 4px; font-size: 12px; font-weight: bold; }
.upload-area { border: 2px dashed #d1d5db; border-radius: 12px; padding: 40px; text-align: center; cursor: pointer; transition: all 0.2s; }
.upload-area:hover { border-color: #8b5cf6; background: #faf5ff; }
.upload-area.dragover { border-color: #8b5cf6; background: #f3e8ff; }
.upload-icon { font-size: 48px; margin-bottom: 12px; }
.upload-text { color: #6b7280; }
.upload-text strong { color: #8b5cf6; }
input[type="file"] { display: none; }
.btn { width: 100%; padding: 14px; background: #8b5cf6; color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; margin-top: 16px; transition: background 0.2s; }
.btn:hover { background: #7c3aed; }
.btn:disabled { background: #d1d5db; cursor: not-allowed; }
.progress-container { margin-top: 20px; display: none; }
.progress-bar-bg { height: 8px; background: #e5e7eb; border-radius: 4px; overflow: hidden; }
.progress-bar { height: 100%; background: linear-gradient(90deg, #8b5cf6, #a78bfa); width: 0; transition: width 0.3s; }
.progress-text { margin-top: 8px; font-size: 14px; color: #6b7280; text-align: center; }
.file-info { margin-top: 20px; padding: 16px; background: #f9fafb; border-radius: 8px; display: none; }
.file-info.show { display: block; }
.file-item { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #e5e7eb; }
.file-item:last-child { border-bottom: none; }
.file-name { font-weight: 500; color: #374151; }
.file-size { color: #6b7280; }
</style>
</head>
<body>
<div class="container">
<h1>文件上传 <span class="method-badge">POST</span></h1>
<div class="card">
<div class="upload-area" id="uploadArea">
<div class="upload-icon">📁</div>
<p class="upload-text">将文件拖拽到此处,或 <strong>点击选择文件</strong></p>
<p class="upload-text" style="font-size:12px;margin-top:8px;">支持 jpg、png、pdf,大小不超过 10MB</p>
</div>
<input type="file" id="fileInput" accept=".jpg,.jpeg,.png,.pdf">
<div id="selectedFile" style="margin-top:16px;padding:12px;background:#f3f4f6;border-radius:8px;display:none;">
<strong>已选择:</strong><span id="fileName"></span>
</div>
<button class="btn" id="uploadBtn" disabled>上传文件</button>
<div class="progress-container" id="progressContainer">
<div class="progress-bar-bg">
<div class="progress-bar" id="progressBar"></div>
</div>
<div class="progress-text" id="progressText">0%</div>
</div>
<div class="file-info" id="fileInfo">
<h3 style="margin-bottom:12px;">上传成功!</h3>
<div id="fileDetails"></div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/axios@1.15.0/dist/axios.min.js"></script>
<script>
const API_BASE = 'http://localhost:3000/api';
let selectedFile = null;
const uploadArea = document.getElementById('uploadArea');
const fileInput = document.getElementById('fileInput');
const uploadBtn = document.getElementById('uploadBtn');
const progressContainer = document.getElementById('progressContainer');
const progressBar = document.getElementById('progressBar');
const progressText = document.getElementById('progressText');
const fileInfo = document.getElementById('fileInfo');
const fileDetails = document.getElementById('fileDetails');
const selectedFileEl = document.getElementById('selectedFile');
const fileNameEl = document.getElementById('fileName');
uploadArea.addEventListener('click', () => fileInput.click());
uploadArea.addEventListener('dragover', (e) => { e.preventDefault(); uploadArea.classList.add('dragover'); });
uploadArea.addEventListener('dragleave', () => { uploadArea.classList.remove('dragover'); });
uploadArea.addEventListener('drop', (e) => { e.preventDefault(); uploadArea.classList.remove('dragover'); const file = e.dataTransfer.files[0]; if (file) handleFileSelect(file); });
fileInput.addEventListener('change', (e) => { const file = e.target.files[0]; if (file) handleFileSelect(file); });
function handleFileSelect(file) {
if (file.size > 10 * 1024 * 1024) { alert('文件大小不能超过10MB'); return; }
selectedFile = file;
fileNameEl.textContent = `${file.name} (${formatFileSize(file.size)})`;
selectedFileEl.style.display = 'block';
uploadBtn.disabled = false;
fileInfo.classList.remove('show');
}
uploadBtn.addEventListener('click', async () => {
if (!selectedFile) return;
const formData = new FormData();
formData.append('file', selectedFile);
uploadBtn.disabled = true;
uploadBtn.textContent = '上传中...';
progressContainer.style.display = 'block';
progressBar.style.width = '0%';
progressText.textContent = '0%';
try {
const res = await axios.post(`${API_BASE}/upload`, formData, {
headers: { 'Content-Type': 'multipart/form-data' },
onUploadProgress: (progressEvent) => {
const percent = Math.round((progressEvent.loaded / progressEvent.total) * 100);
progressBar.style.width = `${percent}%`;
progressText.textContent = `${percent}%`;
}
});
progressContainer.style.display = 'none';
uploadBtn.disabled = false;
uploadBtn.textContent = '上传文件';
fileDetails.innerHTML = `
<div class="file-item"><span class="file-name">文件名</span><span class="file-size">${res.data.data.originalName}</span></div>
<div class="file-item"><span class="file-name">保存名称</span><span class="file-size">${res.data.data.filename}</span></div>
<div class="file-item"><span class="file-name">文件大小</span><span class="file-size">${formatFileSize(res.data.data.size)}</span></div>
`;
fileInfo.classList.add('show');
} catch (err) {
alert(`上传失败: ${err.response?.data?.error || err.message}`);
uploadBtn.disabled = false;
uploadBtn.textContent = '上传文件';
progressContainer.style.display = 'none';
}
});
function formatFileSize(bytes) {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
}
</script>
</body>
</html>
第五章:Axios高级特性
5.1 Axios实例与拦截器
拦截器流程图
响应拦截器
请求拦截器
请求配置
请求拦截器1 - 添加Token
请求拦截器2 - 防缓存
发送请求到服务器
响应拦截器1 - 统一返回data
响应拦截器2 - 错误处理
返回最终响应
完整Axios封装
javascript
// public/js/axiosClient.js
const request = axios.create({
baseURL: 'http://localhost:3000/api',
timeout: 15000,
headers: { 'Content-Type': 'application/json' }
});
request.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token');
if (token) config.headers.Authorization = `Bearer ${token}`;
if (config.method === 'get') config.params = { ...config.params, _t: Date.now() };
return config;
},
(error) => Promise.reject(error)
);
request.interceptors.response.use(
(response) => response.data,
(error) => {
if (error.response) {
const { status } = error.response;
switch (status) {
case 401:
localStorage.removeItem('token');
window.location.href = '/05-login.html';
break;
case 404:
return Promise.reject(new Error('资源不存在'));
case 500:
return Promise.reject(new Error('服务器错误'));
}
} else if (error.request) {
return Promise.reject(new Error('网络连接失败'));
}
return Promise.reject(error);
}
);
export default request;
5.2 取消请求(AbortController)
取消请求流程图
发起请求
创建AbortController
将signal传递给axios
用户取消操作
调用controller.abort
请求被中断
示例代码
javascript
let abortController = null;
async function searchUsers(keyword) {
if (abortController) abortController.abort();
abortController = new AbortController();
try {
const res = await axios.get('/api/users', {
params: { search: keyword },
signal: abortController.signal
});
console.log(res.data);
} catch (err) {
if (axios.isCancel(err)) console.log('请求已取消');
else console.error('请求失败:', err);
}
}
function cancelSearch() {
if (abortController) abortController.abort('用户取消搜索');
}
5.3 并发请求(Promise.all)
并发请求流程图
服务器 Axios 浏览器 服务器 Axios 浏览器 par [并行请求] par [并行响应] Promise.all([getUsers, getPosts, getComments]) GET /api/users GET /api/posts GET /api/comments users数据 posts数据 comments数据 [users, posts, comments]
示例代码
javascript
async function fetchAllData() {
try {
const [users, posts, comments] = await Promise.all([
axios.get('http://localhost:3000/api/users'),
axios.get('http://localhost:3000/api/posts'),
axios.get('http://localhost:3000/api/comments')
]);
console.log('用户:', users.data);
console.log('文章:', posts.data);
console.log('评论:', comments.data);
} catch (err) {
console.error('并发请求失败:', err);
}
}
第七章:最佳实践与项目总结
7.1 完整项目结构
数据层
Express服务器
public/ 静态文件目录
01-get-users.html
02-create-user.html
03-update-user.html
04-delete-user.html
05-login.html
06-upload.html
server.js 入口
users 数组
uploads/ 文件夹
7.2 运行项目
bash
# 1. 进入项目目录
cd axios-express-tutorial
# 2. 安装依赖
npm install
# 3. 启动服务器
npm start
# 4. 访问页面
# http://localhost:3000/01-get-users.html
# http://localhost:3000/02-create-user.html
# http://localhost:3000/03-update-user.html
# http://localhost:3000/04-delete-user.html
# http://localhost:3000/05-login.html
# http://localhost:3000/06-upload.html
7.3 技术栈总结
| 分类 | 技术 | 版本 |
|---|---|---|
| 前端请求 | Axios | 1.15.0 |
| 后端框架 | Express | 5.2.1 |
| Node.js | JavaScript | ES6+ |
| 文件上传 | Multer | latest |
| 跨域处理 | CORS | latest |
第六章:Axios高级配置
6.1 全局配置与数据转换
全局默认配置
javascript
// 设置全局默认配置
axios.defaults.baseURL = 'https://api.example.com';
axios.defaults.timeout = 10000;
axios.defaults.headers.common['Authorization'] = 'Bearer token';
axios.defaults.headers.post['Content-Type'] = 'application/json';
// 使用全局配置发送请求
const res = await axios.get('/users'); // 自动使用 baseURL
请求/响应数据转换
javascript
const instance = axios.create({
// 请求数据转换 - 发送前修改数据
transformRequest: [function (data, headers) {
// 例如:加密敏感数据
if (data.password) {
data.password = btoa(data.password); // Base64编码示例
}
return JSON.stringify(data);
}],
// 响应数据转换 - 接收后修改数据
transformResponse: [function (data) {
// 例如:统一处理日期格式
const parsed = JSON.parse(data);
if (parsed.createdAt) {
parsed.createdAt = new Date(parsed.createdAt).toLocaleString();
}
return parsed;
}]
});
6.2 响应类型与进度监控
响应类型配置
javascript
// JSON(默认)
const jsonRes = await axios.get('/api/data');
// Blob - 用于文件下载
const blobRes = await axios.get('/api/image', {
responseType: 'blob'
});
const imageUrl = URL.createObjectURL(blobRes.data);
// ArrayBuffer - 用于二进制数据处理
const bufferRes = await axios.get('/api/binary', {
responseType: 'arraybuffer'
});
// Text - 纯文本
const textRes = await axios.get('/api/text', {
responseType: 'text'
});
上传进度
javascript
const formData = new FormData();
formData.append('file', fileInput.files[0]);
const res = await axios.post('/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
onUploadProgress: (progressEvent) => {
const percent = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
console.log(`上传进度: ${percent}%`);
progressBar.style.width = `${percent}%`;
}
});
下载进度
javascript
const res = await axios.get('/download/large-file', {
responseType: 'blob',
onDownloadProgress: (progressEvent) => {
const percent = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
console.log(`下载进度: ${percent}%`);
}
});
// 创建下载链接
const url = window.URL.createObjectURL(new Blob([res.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'file.zip');
document.body.appendChild(link);
link.click();
link.remove();
6.3 跨域与安全配置
javascript
// JSON(默认)
const jsonRes = await axios.get('/api/data');
// Blob - 用于文件下载
const blobRes = await axios.get('/api/image', {
responseType: 'blob'
});
const imageUrl = URL.createObjectURL(blobRes.data);
// ArrayBuffer - 用于二进制数据处理
const bufferRes = await axios.get('/api/binary', {
responseType: 'arraybuffer'
});
// Text - 纯文本
const textRes = await axios.get('/api/text', {
responseType: 'text'
});
// Stream - 流式数据(Node.js环境)
const streamRes = await axios.get('/api/stream', {
responseType: 'stream'
});
跨域凭证配置
javascript
// 允许跨域请求携带 Cookie
const instance = axios.create({
withCredentials: true, // 默认 false
baseURL: 'https://api.example.com'
});
// 服务器端需要配合设置 CORS
// Access-Control-Allow-Credentials: true
// Access-Control-Allow-Origin: http://localhost:3000 (不能为 *)
状态码验证
javascript
const instance = axios.create({
// 自定义哪些状态码应该触发错误
validateStatus: function (status) {
// 默认:status >= 200 && status < 300
// 自定义:接受 200-400 的状态码
return status >= 200 && status < 400;
}
});
HTTP基础认证
javascript
// Basic Auth 认证
const res = await axios.get('/api/protected', {
auth: {
username: 'admin',
password: 'secret123'
}
});
6.4 其他高级功能
URL参数序列化
javascript
const res = await axios.get('/api/search', {
params: {
keywords: ['javascript', 'axios'],
filters: { date: '2024', type: 'article' }
},
paramsSerializer: {
encode: (param) => encodeURIComponent(param),
serialize: (params) => {
return Object.keys(params)
.map(key => `${key}=${JSON.stringify(params[key])}`)
.join('&');
},
indexes: null
}
});
代理配置(Node.js)
javascript
const res = await axios.get('https://api.example.com/data', {
proxy: {
protocol: 'https',
host: '127.0.0.1',
port: 9000,
auth: {
username: 'proxy-user',
password: 'proxy-pass'
}
}
});
自定义适配器
javascript
// 使用 Fetch API 适配器(Axios 1.x 支持)
const instance = axios.create({
adapter: 'fetch' // 使用浏览器原生 fetch
});
// 或自定义适配器
const instance = axios.create({
adapter: async (config) => {
// 自定义请求逻辑
return {
data: {},
status: 200,
statusText: 'OK',
headers: {},
config,
request: {}
};
}
});
请求方法别名补充
javascript
// 除了常用的 get/post/put/patch/delete
// HEAD 请求 - 只获取响应头
const headRes = await axios.head('/api/resource');
console.log(headRes.headers['content-length']);
// OPTIONS 请求 - 获取服务器支持的HTTP方法
const optionsRes = await axios.options('/api/resource');
console.log(optionsRes.headers['allow']);
// 通用 request 方法
const res = await axios.request({
method: 'GET',
url: '/api/users',
params: { page: 1 }
});
速率限制(Node.js)
javascript
// 限制上传/下载速率
const res = await axios.post('/upload', largeData, {
maxRate: [100 * 1024, 100 * 1024] // 每秒100KB
});
第七章:Axios最佳实践与项目总结
7.1 错误处理最佳实践
javascript
try {
const res = await axios.get('/api/data');
} catch (error) {
if (axios.isAxiosError(error)) {
// Axios 错误
console.log('错误类型:', error.name);
console.log('错误消息:', error.message);
console.log('请求配置:', error.config);
if (error.response) {
// 服务器响应了错误状态码
console.log('状态码:', error.response.status);
console.log('响应数据:', error.response.data);
console.log('响应头:', error.response.headers);
} else if (error.request) {
// 请求已发送但没有收到响应
console.log('请求对象:', error.request);
} else {
// 请求配置出错
console.log('配置错误:', error.message);
}
// 超时错误
if (error.code === 'ECONNABORTED') {
console.log('请求超时');
}
// 网络错误
if (error.code === 'ERR_NETWORK') {
console.log('网络错误');
}
} else {
// 非 Axios 错误
console.log('未知错误:', error);
}
}
7.2 封装 Axios 实例
javascript
// utils/request.js
import axios from 'axios';
import { getToken, removeToken } from './auth';
const request = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 15000,
headers: {
'Content-Type': 'application/json'
}
});
// 请求拦截器
request.interceptors.request.use(
(config) => {
const token = getToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
// 添加时间戳防止缓存
if (config.method === 'get') {
config.params = {
...config.params,
_t: Date.now()
};
}
return config;
},
(error) => Promise.reject(error)
);
// 响应拦截器
request.interceptors.response.use(
(response) => {
// 直接返回数据
return response.data;
},
(error) => {
const { response } = error;
if (response?.status === 401) {
removeToken();
window.location.href = '/login';
}
// 统一错误提示
const message = response?.data?.message || '请求失败';
console.error(message);
return Promise.reject(error);
}
);
export default request;
7.3 API 模块化管理
javascript
// api/user.js
import request from '@/utils/request';
export const userApi = {
getList: (params) => request.get('/users', { params }),
getById: (id) => request.get(`/users/${id}`),
create: (data) => request.post('/users', data),
update: (id, data) => request.put(`/users/${id}`, data),
delete: (id) => request.delete(`/users/${id}`),
uploadAvatar: (file) => {
const formData = new FormData();
formData.append('file', file);
return request.post('/users/avatar', formData, {
headers: { 'Content-Type': 'multipart/form-data' }
});
}
};
// 使用
import { userApi } from '@/api/user';
const users = await userApi.getList({ page: 1, size: 10 });
7.4 请求重试机制
javascript
// 带重试的请求
async function requestWithRetry(config, maxRetries = 3) {
let lastError;
for (let i = 0; i < maxRetries; i++) {
try {
return await axios(config);
} catch (error) {
lastError = error;
// 只在网络错误或5xx错误时重试
if (!error.response || error.response.status >= 500) {
const delay = Math.pow(2, i) * 1000; // 指数退避
console.log(`第${i + 1}次重试,等待${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
} else {
throw error; // 4xx错误不重试
}
}
}
throw lastError;
}
7.5 请求缓存
javascript
// 简单的请求缓存实现
const cache = new Map();
async function cachedRequest(config, ttl = 60000) {
const key = JSON.stringify(config);
const cached = cache.get(key);
if (cached && Date.now() - cached.time < ttl) {
console.log('使用缓存数据');
return cached.data;
}
const res = await axios(config);
cache.set(key, { data: res, time: Date.now() });
return res;
}