Axios 完整实战教程

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;
}
相关推荐
三声三视7 天前
鸿蒙 ArkTS 网络请求实战:从 HTTP 到 Axios 封装,打造生产级请求层
网络·http·axios·harmonyos·网络封装
梵得儿SHI9 天前
Vue 3 工程化实战:Axios 高阶封装与样式解决方案深度指南
前端·javascript·vue3·axios·样式解决方案·api请求管理·统一请求处理
晓得迷路了10 天前
栗子前端技术周刊第 123 期 - axios 包遭入侵、Babylon.js 9.0、Node.js 25.9.0...
前端·javascript·axios
终端鹿14 天前
Vue3 + axios 前后端联调实战:封装、跨域与报错处理
前端·vue.js·axios
合天网安实验室15 天前
Axios遭供应链投毒攻击(附排查与紧急补救指南)
axios·黑客攻击·供应链投毒
Forever7_16 天前
紧急!Axios 被投毒,3亿项目受到影响!教你怎么自查!
前端·axios
SuperEugene22 天前
Axios 接口请求规范实战:请求参数 / 响应处理 / 异常兜底,避坑中后台 API 调用混乱|API 与异步请求规范篇
开发语言·前端·javascript·vue.js·前端框架·axios
SuperEugene22 天前
Axios + Vue 错误处理规范:中后台项目实战,统一捕获系统 / 业务 / 接口异常|API 与异步请求规范篇
前端·javascript·vue.js·前端框架·axios
SuperEugene23 天前
Axios 统一封装实战:拦截器配置 + baseURL 优化 + 接口规范,避坑重复代码|API 与异步请求规范篇
前端·javascript·vue.js·前端框架·axios