pin_drop当前位置:知识文库 ❯ 图文

后端通信:20. API设计与文档 - 从入门到实践详解

教程简介

API(Application Programming Interface)是前后端通信的契约。良好的API设计不仅能提升开发效率,还能降低沟通成本、减少集成问题。本教程将全面讲解RESTful API设计原则、API版本管理、请求与响应规范、错误码设计、分页与过滤、API文档编写(OpenAPI/Swagger规范)等内容,帮助你设计和维护高质量的Web API。

核心概念

RESTful API设计原则

原则 说明 示例
资源导向 URL表示资源,而非操作 /users 而非 /getUsers
HTTP方法语义化 用方法表达操作 GET /users, POST /users
无状态 每个请求包含所有必要信息 Token认证而非Session
统一接口 一致的URL和响应格式 统一的错误格式
分层系统 客户端不需要知道后端架构 通过API网关访问

HTTP方法与CRUD映射

HTTP方法 操作 幂等性 安全性 示例
GET 读取 GET /users/123
POST 创建 POST /users
PUT 全量更新 PUT /users/123
PATCH 部分更新 PATCH /users/123
DELETE 删除 DELETE /users/123

API版本管理策略

策略 示例 优点 缺点
URL路径 /v1/users 简单直观 URL变化
请求头 Accept: application/vnd.api.v1+json URL不变 不直观
查询参数 /users?version=1 简单 容易被忽略

响应格式规范

代码示例

// 成功响应
{
    "code": 0,
    "message": "success",
    "data": {
        "id": 1,
        "name": "张三"
    }
}

// 列表响应(带分页)
{
    "code": 0,
    "message": "success",
    "data": {
        "items": [...],
        "pagination": {
            "page": 1,
            "pageSize": 20,
            "total": 100,
            "totalPages": 5
        }
    }
}

// 错误响应
{
    "code": 40001,
    "message": "参数验证失败",
    "errors": [
        { "field": "email", "message": "邮箱格式不正确" }
    ]
}

语法与用法

RESTful URL设计

代码示例

# 资源集合
GET    /api/v1/users           # 获取用户列表
POST   /api/v1/users           # 创建新用户

# 单个资源
GET    /api/v1/users/123       # 获取指定用户
PUT    /api/v1/users/123       # 更新指定用户
PATCH  /api/v1/users/123       # 部分更新指定用户
DELETE /api/v1/users/123       # 删除指定用户

# 子资源
GET    /api/v1/users/123/orders    # 获取用户的订单列表
POST   /api/v1/users/123/orders    # 为用户创建订单

# 特殊操作(非CRUD)
POST   /api/v1/users/123/activate  # 激活用户
POST   /api/v1/users/123/deactivate # 停用用户
POST   /api/v1/orders/456/cancel   # 取消订单

查询参数规范

代码示例

# 分页
GET /users?page=1&pageSize=20

# 排序
GET /users?sort=createdAt&order=desc

# 过滤
GET /users?role=admin&status=active

# 搜索
GET /users?q=张三

# 字段选择
GET /users?fields=id,name,email

# 嵌套加载
GET /users/123?include=orders,profile

代码示例

示例1:RESTful API设计规范

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>RESTful API设计规范</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            background: #0f0f23;
            color: #e0e0e0;
            min-height: 100vh;
            padding: 30px 20px;
        }
        .container { max-width: 1000px; margin: 0 auto; }
        h1 { text-align: center; color: #48dbfb; margin-bottom: 30px; }
        .card {
            background: #1a1a2e;
            border: 1px solid #2d2d44;
            border-radius: 12px;
            padding: 24px;
            margin-bottom: 16px;
        }
        .card h2 { font-size: 16px; color: #feca57; margin-bottom: 16px; }
        .api-table {
            width: 100%;
            border-collapse: collapse;
            font-size: 13px;
        }
        .api-table th, .api-table td {
            padding: 10px 12px;
            border: 1px solid #2d2d44;
            text-align: left;
        }
        .api-table th { background: #0f3460; color: #48dbfb; }
        .method-badge {
            display: inline-block;
            padding: 2px 8px;
            border-radius: 4px;
            font-size: 11px;
            font-weight: 700;
            font-family: monospace;
        }
        .method-get { background: #2ecc7133; color: #2ecc71; }
        .method-post { background: #3498db33; color: #3498db; }
        .method-put { background: #f39c1233; color: #f39c12; }
        .method-patch { background: #9b59b633; color: #9b59b6; }
        .method-delete { background: #e74c3c33; color: #e74c3c; }
        .url-path { color: #a6e3a1; font-family: monospace; font-size: 12px; }
        .code-block {
            background: #0a0a1a;
            color: #a6e3a1;
            padding: 16px;
            border-radius: 8px;
            font-family: 'Courier New', monospace;
            font-size: 13px;
            line-height: 1.6;
            overflow-x: auto;
            white-space: pre;
            margin: 12px 0;
        }
        .rule-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
            gap: 12px;
        }
        .rule-item {
            background: #0f3460;
            border-radius: 8px;
            padding: 16px;
        }
        .rule-item h3 { font-size: 14px; color: #48dbfb; margin-bottom: 8px; }
        .rule-item p { font-size: 12px; color: #a6adc8; line-height: 1.6; }
        .good { color: #a6e3a1; }
        .bad { color: #f38ba8; }
        .comparison {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 12px;
            margin-top: 8px;
        }
        .comparison-box {
            padding: 12px;
            border-radius: 6px;
            font-family: monospace;
            font-size: 12px;
        }
        .good-box { background: #0a2e1a; border: 1px solid #2ecc71; }
        .bad-box { background: #2e0a0a; border: 1px solid #e74c3c; }
    </style>
</head>
<body>
    <div class="container">
        <h1>RESTful API 设计规范</h1>

        <!-- 用户资源API -->
        <div class="card">
            <h2>用户资源 API 设计</h2>
            <table class="api-table">
                <thead>
                    <tr>
                        <th>方法</th>
                        <th>路径</th>
                        <th>说明</th>
                        <th>请求体</th>
                        <th>响应</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td><span class="method-badge method-get">GET</span></td>
                        <td class="url-path">/api/v1/users</td>
                        <td>获取用户列表</td>
                        <td>-</td>
                        <td>用户数组+分页</td>
                    </tr>
                    <tr>
                        <td><span class="method-badge method-get">GET</span></td>
                        <td class="url-path">/api/v1/users/:id</td>
                        <td>获取单个用户</td>
                        <td>-</td>
                        <td>用户对象</td>
                    </tr>
                    <tr>
                        <td><span class="method-badge method-post">POST</span></td>
                        <td class="url-path">/api/v1/users</td>
                        <td>创建用户</td>
                        <td>用户数据</td>
                        <td>201+新用户</td>
                    </tr>
                    <tr>
                        <td><span class="method-badge method-put">PUT</span></td>
                        <td class="url-path">/api/v1/users/:id</td>
                        <td>全量更新用户</td>
                        <td>完整用户数据</td>
                        <td>更新后的用户</td>
                    </tr>
                    <tr>
                        <td><span class="method-badge method-patch">PATCH</span></td>
                        <td class="url-path">/api/v1/users/:id</td>
                        <td>部分更新用户</td>
                        <td>部分字段</td>
                        <td>更新后的用户</td>
                    </tr>
                    <tr>
                        <td><span class="method-badge method-delete">DELETE</span></td>
                        <td class="url-path">/api/v1/users/:id</td>
                        <td>删除用户</td>
                        <td>-</td>
                        <td>204 No Content</td>
                    </tr>
                </tbody>
            </table>
        </div>

        <!-- 设计规则 -->
        <div class="card">
            <h2>URL设计规则</h2>
            <div class="rule-grid">
                <div class="rule-item">
                    <h3>1. 使用名词而非动词</h3>
                    <div class="comparison">
                        <div class="comparison-box bad-box">
                            <span class="bad">✗</span> GET /getUsers<br>
                            <span class="bad">✗</span> POST /createUser<br>
                            <span class="bad">✗</span> DELETE /deleteUser/123
                        </div>
                        <div class="comparison-box good-box">
                            <span class="good">✓</span> GET /users<br>
                            <span class="good">✓</span> POST /users<br>
                            <span class="good">✓</span> DELETE /users/123
                        </div>
                    </div>
                </div>

                <div class="rule-item">
                    <h3>2. 使用复数名词</h3>
                    <div class="comparison">
                        <div class="comparison-box bad-box">
                            <span class="bad">✗</span> GET /user<br>
                            <span class="bad">✗</span> GET /user/123
                        </div>
                        <div class="comparison-box good-box">
                            <span class="good">✓</span> GET /users<br>
                            <span class="good">✓</span> GET /users/123
                        </div>
                    </div>
                </div>

                <div class="rule-item">
                    <h3>3. 嵌套资源不超过两层</h3>
                    <div class="comparison">
                        <div class="comparison-box bad-box">
                            <span class="bad">✗</span> /users/123/orders/456/items/789
                        </div>
                        <div class="comparison-box good-box">
                            <span class="good">✓</span> /users/123/orders<br>
                            <span class="good">✓</span> /orders/456/items
                        </div>
                    </div>
                </div>

                <div class="rule-item">
                    <h3>4. 使用小写和连字符</h3>
                    <div class="comparison">
                        <div class="comparison-box bad-box">
                            <span class="bad">✗</span> /userProfiles<br>
                            <span class="bad">✗</span> /user_profiles
                        </div>
                        <div class="comparison-box good-box">
                            <span class="good">✓</span> /user-profiles
                        </div>
                    </div>
                </div>

                <div class="rule-item">
                    <h3>5. 非CRUD操作用动词</h3>
                    <div class="comparison">
                        <div class="comparison-box bad-box">
                            <span class="bad">✗</span> PATCH /users/123/active/true
                        </div>
                        <div class="comparison-box good-box">
                            <span class="good">✓</span> POST /users/123/activate<br>
                            <span class="good">✓</span> POST /users/123/deactivate
                        </div>
                    </div>
                </div>

                <div class="rule-item">
                    <h3>6. 使用查询参数过滤</h3>
                    <div class="comparison">
                        <div class="comparison-box bad-box">
                            <span class="bad">✗</span> GET /activeUsers<br>
                            <span class="bad">✗</span> GET /users/active
                        </div>
                        <div class="comparison-box good-box">
                            <span class="good">✓</span> GET /users?status=active
                        </div>
                    </div>
                </div>
            </div>
        </div>

        <!-- 状态码使用 -->
        <div class="card">
            <h2>HTTP状态码使用规范</h2>
            <table class="api-table">
                <thead>
                    <tr>
                        <th>状态码</th>
                        <th>含义</th>
                        <th>使用场景</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td style="color:#2ecc71;">200</td>
                        <td>OK</td>
                        <td>GET成功、PUT/PATCH更新成功</td>
                    </tr>
                    <tr>
                        <td style="color:#2ecc71;">201</td>
                        <td>Created</td>
                        <td>POST创建资源成功</td>
                    </tr>
                    <tr>
                        <td style="color:#2ecc71;">204</td>
                        <td>No Content</td>
                        <td>DELETE成功(无返回内容)</td>
                    </tr>
                    <tr>
                        <td style="color:#f39c12;">301</td>
                        <td>Moved Permanently</td>
                        <td>API路径永久迁移</td>
                    </tr>
                    <tr>
                        <td style="color:#f39c12;">304</td>
                        <td>Not Modified</td>
                        <td>缓存有效(协商缓存)</td>
                    </tr>
                    <tr>
                        <td style="color:#e74c3c;">400</td>
                        <td>Bad Request</td>
                        <td>请求参数验证失败</td>
                    </tr>
                    <tr>
                        <td style="color:#e74c3c;">401</td>
                        <td>Unauthorized</td>
                        <td>未认证或Token过期</td>
                    </tr>
                    <tr>
                        <td style="color:#e74c3c;">403</td>
                        <td>Forbidden</td>
                        <td>已认证但无权限</td>
                    </tr>
                    <tr>
                        <td style="color:#e74c3c;">404</td>
                        <td>Not Found</td>
                        <td>资源不存在</td>
                    </tr>
                    <tr>
                        <td style="color:#e74c3c;">409</td>
                        <td>Conflict</td>
                        <td>资源冲突(如重复创建)</td>
                    </tr>
                    <tr>
                        <td style="color:#e74c3c;">422</td>
                        <td>Unprocessable Entity</td>
                        <td>语义错误(格式正确但值无效)</td>
                    </tr>
                    <tr>
                        <td style="color:#e74c3c;">429</td>
                        <td>Too Many Requests</td>
                        <td>请求频率超限</td>
                    </tr>
                    <tr>
                        <td style="color:#e74c3c;">500</td>
                        <td>Internal Server Error</td>
                        <td>服务器内部错误</td>
                    </tr>
                    <tr>
                        <td style="color:#e74c3c;">503</td>
                        <td>Service Unavailable</td>
                        <td>服务暂时不可用</td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>
</body>
</html>

示例2:API响应与错误码规范

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>API响应与错误码规范</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            background: #1e1e2e;
            color: #cdd6f4;
            min-height: 100vh;
            padding: 30px 20px;
        }
        .container { max-width: 1000px; margin: 0 auto; }
        h1 { text-align: center; color: #89b4fa; margin-bottom: 30px; }
        .card {
            background: #313244;
            border-radius: 12px;
            padding: 24px;
            margin-bottom: 16px;
            border: 1px solid #45475a;
        }
        .card h2 { font-size: 16px; color: #f9e2af; margin-bottom: 16px; }
        .json-block {
            background: #1e1e2e;
            border-radius: 8px;
            padding: 16px;
            font-family: 'Courier New', monospace;
            font-size: 13px;
            line-height: 1.6;
            overflow-x: auto;
            margin: 12px 0;
        }
        .json-key { color: #89b4fa; }
        .json-string { color: #a6e3a1; }
        .json-number { color: #fab387; }
        .json-bool { color: #f38ba8; }
        .json-null { color: #6c7086; }
        .error-table {
            width: 100%;
            border-collapse: collapse;
            font-size: 13px;
        }
        .error-table th, .error-table td {
            padding: 10px 12px;
            border: 1px solid #45475a;
            text-align: left;
        }
        .error-table th { background: #1e1e2e; color: #89b4fa; }
        .code-block {
            background: #1e1e2e;
            color: #a6e3a1;
            padding: 16px;
            border-radius: 8px;
            font-family: 'Courier New', monospace;
            font-size: 13px;
            line-height: 1.6;
            overflow-x: auto;
            white-space: pre;
            margin: 12px 0;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>API 响应与错误码规范</h1>

        <!-- 统一响应格式 -->
        <div class="card">
            <h2>统一响应格式</h2>

            <h3 style="font-size:14px; color:#a6e3a1; margin-bottom:8px;">成功响应 - 单个资源</h3>
            <div class="json-block">{
    "<span class="json-key">code</span>": <span class="json-number">0</span>,
    "<span class="json-key">message</span>": <span class="json-string">"success"</span>,
    "<span class="json-key">data</span>": {
        "<span class="json-key">id</span>": <span class="json-number">1</span>,
        "<span class="json-key">username</span>": <span class="json-string">"zhangsan"</span>,
        "<span class="json-key">email</span>": <span class="json-string">"zhangsan@example.com"</span>,
        "<span class="json-key">role</span>": <span class="json-string">"admin"</span>,
        "<span class="json-key">createdAt</span>": <span class="json-string">"2025-01-15T08:30:00Z"</span>,
        "<span class="json-key">updatedAt</span>": <span class="json-string">"2025-04-20T10:15:00Z"</span>
    }
}</div>

            <h3 style="font-size:14px; color:#a6e3a1; margin-bottom:8px;">成功响应 - 列表(带分页)</h3>
            <div class="json-block">{
    "<span class="json-key">code</span>": <span class="json-number">0</span>,
    "<span class="json-key">message</span>": <span class="json-string">"success"</span>,
    "<span class="json-key">data</span>": {
        "<span class="json-key">items</span>": [
            { "<span class="json-key">id</span>": <span class="json-number">1</span>, "<span class="json-key">name</span>": <span class="json-string">"张三"</span> },
            { "<span class="json-key">id</span>": <span class="json-number">2</span>, "<span class="json-key">name</span>": <span class="json-string">"李四"</span> }
        ],
        "<span class="json-key">pagination</span>": {
            "<span class="json-key">page</span>": <span class="json-number">1</span>,
            "<span class="json-key">pageSize</span>": <span class="json-number">20</span>,
            "<span class="json-key">total</span>": <span class="json-number">156</span>,
            "<span class="json-key">totalPages</span>": <span class="json-number">8</span>
        }
    }
}</div>

            <h3 style="font-size:14px; color:#f38ba8; margin-bottom:8px;">错误响应</h3>
            <div class="json-block">{
    "<span class="json-key">code</span>": <span class="json-number">40001</span>,
    "<span class="json-key">message</span>": <span class="json-string">"参数验证失败"</span>,
    "<span class="json-key">errors</span>": [
        {
            "<span class="json-key">field</span>": <span class="json-string">"email"</span>,
            "<span class="json-key">message</span>": <span class="json-string">"邮箱格式不正确"</span>,
            "<span class="json-key">value</span>": <span class="json-string">"invalid-email"</span>
        },
        {
            "<span class="json-key">field</span>": <span class="json-string">"age"</span>,
            "<span class="json-key">message</span>": <span class="json-string">"年龄必须在1-150之间"</span>,
            "<span class="json-key">value</span>": <span class="json-number">-5</span>
        }
    ],
    "<span class="json-key">requestId</span>": <span class="json-string">"req_abc123"</span>,
    "<span class="json-key">timestamp</span>": <span class="json-string">"2025-04-22T10:30:00Z"</span>
}</div>
        </div>

        <!-- 错误码设计 -->
        <div class="card">
            <h2>错误码设计规范</h2>
            <p style="font-size:13px; color:#a6adc8; margin-bottom:12px;">
                错误码格式:5位数字,前3位对应HTTP状态码,后2位为业务错误码。
            </p>
            <table class="error-table">
                <thead>
                    <tr>
                        <th>错误码</th>
                        <th>HTTP状态码</th>
                        <th>说明</th>
                        <th>前端处理</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td style="color:#a6e3a1;">0</td>
                        <td>200</td>
                        <td>成功</td>
                        <td>正常处理</td>
                    </tr>
                    <tr>
                        <td style="color:#f38ba8;">40001</td>
                        <td>400</td>
                        <td>参数验证失败</td>
                        <td>显示字段错误提示</td>
                    </tr>
                    <tr>
                        <td style="color:#f38ba8;">40002</td>
                        <td>400</td>
                        <td>请求格式错误</td>
                        <td>提示检查请求</td>
                    </tr>
                    <tr>
                        <td style="color:#f38ba8;">40101</td>
                        <td>401</td>
                        <td>Token无效或过期</td>
                        <td>刷新Token或跳转登录</td>
                    </tr>
                    <tr>
                        <td style="color:#f38ba8;">40102</td>
                        <td>401</td>
                        <td>Token缺失</td>
                        <td>跳转登录页</td>
                    </tr>
                    <tr>
                        <td style="color:#f38ba8;">40301</td>
                        <td>403</td>
                        <td>无权限访问</td>
                        <td>显示无权限提示</td>
                    </tr>
                    <tr>
                        <td style="color:#f38ba8;">40302</td>
                        <td>403</td>
                        <td>账号被禁用</td>
                        <td>提示联系管理员</td>
                    </tr>
                    <tr>
                        <td style="color:#f38ba8;">40401</td>
                        <td>404</td>
                        <td>资源不存在</td>
                        <td>显示404页面</td>
                    </tr>
                    <tr>
                        <td style="color:#f38ba8;">40901</td>
                        <td>409</td>
                        <td>资源冲突(重复)</td>
                        <td>提示已存在</td>
                    </tr>
                    <tr>
                        <td style="color:#f38ba8;">42901</td>
                        <td>429</td>
                        <td>请求频率超限</td>
                        <td>等待后重试</td>
                    </tr>
                    <tr>
                        <td style="color:#f38ba8;">50001</td>
                        <td>500</td>
                        <td>服务器内部错误</td>
                        <td>重试或提示稍后再试</td>
                    </tr>
                    <tr>
                        <td style="color:#f38ba8;">50002</td>
                        <td>500</td>
                        <td>数据库错误</td>
                        <td>重试或提示稍后再试</td>
                    </tr>
                    <tr>
                        <td style="color:#f38ba8;">50301</td>
                        <td>503</td>
                        <td>服务维护中</td>
                        <td>显示维护通知</td>
                    </tr>
                </tbody>
            </table>
        </div>

        <!-- 分页规范 -->
        <div class="card">
            <h2>分页与排序规范</h2>
            <div class="code-block">// 请求参数
GET /api/v1/users?page=1&pageSize=20&sort=createdAt&order=desc&role=admin

// 分页参数说明
page:      页码,从1开始,默认1
pageSize:  每页条数,默认20,最大100
sort:      排序字段
order:     排序方向,asc(升序) / desc(降序),默认desc

// 多字段排序
GET /api/v1/users?sort=role:asc,createdAt:desc

// 响应格式
{
    "code": 0,
    "data": {
        "items": [...],
        "pagination": {
            "page": 1,           // 当前页码
            "pageSize": 20,      // 每页条数
            "total": 156,        // 总记录数
            "totalPages": 8      // 总页数
        }
    }
}

// 游标分页(大数据量推荐)
// 请求
GET /api/v1/users?cursor=eyJpZCI6MTAwfQ&limit=20

// 响应
{
    "code": 0,
    "data": {
        "items": [...],
        "pagination": {
            "nextCursor": "eyJpZCI6MTIwfQ",  // 下一页游标
            "hasMore": true,                   // 是否有更多
            "limit": 20
        }
    }
}</div>
        </div>

        <!-- 前端API层封装 -->
        <div class="card">
            <h2>前端API层封装</h2>
            <div class="code-block">// api/client.js - 基础客户端
import HttpClient from '@/utils/http';

const client = new HttpClient({
    baseURL: '/api/v1',
    timeout: 10000
});

// 请求拦截器 - 添加Token
client.addRequestInterceptor((config) => {
    const token = localStorage.getItem('access_token');
    if (token) {
        config.headers['Authorization'] = `Bearer ${token}`;
    }
    return config;
});

// 响应拦截器 - 统一错误处理
client.addResponseInterceptor(
    (response) => {
        // 业务错误码处理
        if (response.data.code !== 0) {
            const error = new BusinessError(
                response.data.code,
                response.data.message,
                response.data.errors
            );
            return Promise.reject(error);
        }
        return response.data;
    },
    (error) => {
        // HTTP错误处理
        if (error.status === 401) {
            // Token过期
            return refreshTokenAndRetry(error.config);
        }
        return Promise.reject(error);
    }
);

// api/users.js - 用户API
export const userApi = {
    // 获取用户列表
    getList(params) {
        return client.get('/users', { params });
    },

    // 获取单个用户
    getById(id) {
        return client.get(`/users/${id}`);
    },

    // 创建用户
    create(data) {
        return client.post('/users', data);
    },

    // 更新用户
    update(id, data) {
        return client.put(`/users/${id}`, data);
    },

    // 部分更新
    patch(id, data) {
        return client.patch(`/users/${id}`, data);
    },

    // 删除用户
    remove(id) {
        return client.delete(`/users/${id}`);
    },

    // 激活用户
    activate(id) {
        return client.post(`/users/${id}/activate`);
    },

    // 获取用户的订单
    getOrders(userId, params) {
        return client.get(`/users/${userId}/orders`, { params });
    }
};

// 在组件中使用
import { userApi } from '@/api/users';

async function loadUsers() {
    try {
        const result = await userApi.getList({
            page: 1,
            pageSize: 20,
            role: 'admin'
        });
        // result.data.items - 用户列表
        // result.data.pagination - 分页信息
    } catch (error) {
        if (error.code === 40001) {
            // 参数验证失败
            showFieldErrors(error.errors);
        } else {
            showToast(error.message);
        }
    }
}</div>
        </div>
    </div>
</body>
</html>

示例3:OpenAPI文档规范

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>OpenAPI文档规范</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            background: #f5f7fa;
            min-height: 100vh;
            padding: 30px 20px;
        }
        .container { max-width: 1000px; margin: 0 auto; }
        h1 { text-align: center; color: #2c3e50; margin-bottom: 30px; }
        .card {
            background: white;
            border-radius: 12px;
            padding: 24px;
            margin-bottom: 16px;
            box-shadow: 0 2px 12px rgba(0,0,0,0.06);
        }
        .card h2 { font-size: 16px; color: #2c3e50; margin-bottom: 16px; }
        .yaml-block {
            background: #2c3e50;
            color: #ecf0f1;
            padding: 16px;
            border-radius: 8px;
            font-family: 'Courier New', monospace;
            font-size: 12px;
            line-height: 1.6;
            overflow-x: auto;
            white-space: pre;
            margin: 12px 0;
        }
        .yaml-key { color: #3498db; }
        .yaml-string { color: #2ecc71; }
        .yaml-number { color: #e67e22; }
        .yaml-bool { color: #e74c3c; }
        .yaml-comment { color: #7f8c8d; }
        .feature-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
            gap: 12px;
            margin-top: 12px;
        }
        .feature-item {
            padding: 16px;
            background: #f8f9fa;
            border-radius: 8px;
            text-align: center;
        }
        .feature-item h3 { font-size: 14px; color: #2c3e50; margin-bottom: 4px; }
        .feature-item p { font-size: 12px; color: #888; }
        .feature-icon { font-size: 24px; margin-bottom: 8px; }
    </style>
</head>
<body>
    <div class="container">
        <h1>OpenAPI 文档规范</h1>

        <!-- OpenAPI概述 -->
        <div class="card">
            <h2>OpenAPI (Swagger) 概述</h2>
            <p style="font-size:14px; color:#666; line-height:1.8; margin-bottom:12px;">
                OpenAPI Specification(OAS)是REST API的标准化描述格式,原名Swagger。
                它使用YAML/JSON格式描述API的结构,可以被机器读取,自动生成文档、客户端代码和测试用例。
            </p>
            <div class="feature-grid">
                <div class="feature-item">
                    <div class="feature-icon">📄</div>
                    <h3>自动文档</h3>
                    <p>生成交互式API文档</p>
                </div>
                <div class="feature-item">
                    <div class="feature-icon">💻</div>
                    <h3>代码生成</h3>
                    <p>自动生成客户端SDK</p>
                </div>
                <div class="feature-item">
                    <div class="feature-icon">🔎</div>
                    <h3>API测试</h3>
                    <p>自动生成测试用例</p>
                </div>
                <div class="feature-item">
                    <div class="feature-icon">🤝</div>
                    <h3>团队协作</h3>
                    <p>前后端契约先行</p>
                </div>
            </div>
        </div>

        <!-- OpenAPI文档示例 -->
        <div class="card">
            <h2>OpenAPI 3.0 文档示例</h2>
            <div class="yaml-block"><span class="yaml-key">openapi</span>: <span class="yaml-string">'3.0.3'</span>
<span class="yaml-key">info</span>:
  <span class="yaml-key">title</span>: <span class="yaml-string">用户管理API</span>
  <span class="yaml-key">description</span>: <span class="yaml-string">用户管理系统的RESTful API文档</span>
  <span class="yaml-key">version</span>: <span class="yaml-string">'1.0.0'</span>
  <span class="yaml-key">contact</span>:
    <span class="yaml-key">name</span>: <span class="yaml-string">API支持团队</span>
    <span class="yaml-key">email</span>: <span class="yaml-string">api@example.com</span>

<span class="yaml-key">servers</span>:
  - <span class="yaml-key">url</span>: <span class="yaml-string">https://api.example.com/v1</span>
    <span class="yaml-key">description</span>: <span class="yaml-string">生产环境</span>
  - <span class="yaml-key">url</span>: <span class="yaml-string">https://staging-api.example.com/v1</span>
    <span class="yaml-key">description</span>: <span class="yaml-string">测试环境</span>

<span class="yaml-key">tags</span>:
  - <span class="yaml-key">name</span>: <span class="yaml-string">Users</span>
    <span class="yaml-key">description</span>: <span class="yaml-string">用户管理</span>
  - <span class="yaml-key">name</span>: <span class="yaml-string">Auth</span>
    <span class="yaml-key">description</span>: <span class="yaml-string">认证授权</span>

<span class="yaml-key">paths</span>:
  <span class="yaml-key">/users</span>:
    <span class="yaml-key">get</span>:
      <span class="yaml-key">tags</span>: [<span class="yaml-string">Users</span>]
      <span class="yaml-key">summary</span>: <span class="yaml-string">获取用户列表</span>
      <span class="yaml-key">operationId</span>: <span class="yaml-string">getUsers</span>
      <span class="yaml-key">parameters</span>:
        - <span class="yaml-key">name</span>: <span class="yaml-string">page</span>
          <span class="yaml-key">in</span>: <span class="yaml-string">query</span>
          <span class="yaml-key">schema</span>:
            <span class="yaml-key">type</span>: <span class="yaml-string">integer</span>
            <span class="yaml-key">default</span>: <span class="yaml-number">1</span>
        - <span class="yaml-key">name</span>: <span class="yaml-string">pageSize</span>
          <span class="yaml-key">in</span>: <span class="yaml-string">query</span>
          <span class="yaml-key">schema</span>:
            <span class="yaml-key">type</span>: <span class="yaml-string">integer</span>
            <span class="yaml-key">default</span>: <span class="yaml-number">20</span>
            <span class="yaml-key">maximum</span>: <span class="yaml-number">100</span>
        - <span class="yaml-key">name</span>: <span class="yaml-string">role</span>
          <span class="yaml-key">in</span>: <span class="yaml-string">query</span>
          <span class="yaml-key">description</span>: <span class="yaml-string">按角色过滤</span>
          <span class="yaml-key">schema</span>:
            <span class="yaml-key">type</span>: <span class="yaml-string">string</span>
            <span class="yaml-key">enum</span>: [<span class="yaml-string">admin</span>, <span class="yaml-string">editor</span>, <span class="yaml-string">user</span>]
      <span class="yaml-key">responses</span>:
        <span class="yaml-key">'200'</span>:
          <span class="yaml-key">description</span>: <span class="yaml-string">成功</span>
          <span class="yaml-key">content</span>:
            <span class="yaml-key">application/json</span>:
              <span class="yaml-key">schema</span>:
                <span class="yaml-key">$ref</span>: <span class="yaml-string">'#/components/schemas/UserListResponse'</span>

    <span class="yaml-key">post</span>:
      <span class="yaml-key">tags</span>: [<span class="yaml-string">Users</span>]
      <span class="yaml-key">summary</span>: <span class="yaml-string">创建用户</span>
      <span class="yaml-key">operationId</span>: <span class="yaml-string">createUser</span>
      <span class="yaml-key">requestBody</span>:
        <span class="yaml-key">required</span>: <span class="yaml-bool">true</span>
        <span class="yaml-key">content</span>:
          <span class="yaml-key">application/json</span>:
            <span class="yaml-key">schema</span>:
              <span class="yaml-key">$ref</span>: <span class="yaml-string">'#/components/schemas/CreateUserRequest'</span>
      <span class="yaml-key">responses</span>:
        <span class="yaml-key">'201'</span>:
          <span class="yaml-key">description</span>: <span class="yaml-string">创建成功</span>
          <span class="yaml-key">content</span>:
            <span class="yaml-key">application/json</span>:
              <span class="yaml-key">schema</span>:
                <span class="yaml-key">$ref</span>: <span class="yaml-string">'#/components/schemas/UserResponse'</span>
        <span class="yaml-key">'400'</span>:
          <span class="yaml-key">$ref</span>: <span class="yaml-string">'#/components/responses/BadRequest'</span>
        <span class="yaml-key">'409'</span>:
          <span class="yaml-key">description</span>: <span class="yaml-string">用户已存在</span>

  <span class="yaml-key">/users/{id}</span>:
    <span class="yaml-key">get</span>:
      <span class="yaml-key">tags</span>: [<span class="yaml-string">Users</span>]
      <span class="yaml-key">summary</span>: <span class="yaml-string">获取单个用户</span>
      <span class="yaml-key">parameters</span>:
        - <span class="yaml-key">name</span>: <span class="yaml-string">id</span>
          <span class="yaml-key">in</span>: <span class="yaml-string">path</span>
          <span class="yaml-key">required</span>: <span class="yaml-bool">true</span>
          <span class="yaml-key">schema</span>:
            <span class="yaml-key">type</span>: <span class="yaml-string">integer</span>
      <span class="yaml-key">responses</span>:
        <span class="yaml-key">'200'</span>:
          <span class="yaml-key">description</span>: <span class="yaml-string">成功</span>
          <span class="yaml-key">content</span>:
            <span class="yaml-key">application/json</span>:
              <span class="yaml-key">schema</span>:
                <span class="yaml-key">$ref</span>: <span class="yaml-string">'#/components/schemas/UserResponse'</span>
        <span class="yaml-key">'404'</span>:
          <span class="yaml-key">$ref</span>: <span class="yaml-string">'#/components/responses/NotFound'</span>

<span class="yaml-key">components</span>:
  <span class="yaml-key">securitySchemes</span>:
    <span class="yaml-key">BearerAuth</span>:
      <span class="yaml-key">type</span>: <span class="yaml-string">http</span>
      <span class="yaml-key">scheme</span>: <span class="yaml-string">bearer</span>
      <span class="yaml-key">bearerFormat</span>: <span class="yaml-string">JWT</span>

  <span class="yaml-key">schemas</span>:
    <span class="yaml-key">User</span>:
      <span class="yaml-key">type</span>: <span class="yaml-string">object</span>
      <span class="yaml-key">required</span>: [<span class="yaml-string">id</span>, <span class="yaml-string">username</span>, <span class="yaml-string">email</span>]
      <span class="yaml-key">properties</span>:
        <span class="yaml-key">id</span>:
          <span class="yaml-key">type</span>: <span class="yaml-string">integer</span>
          <span class="yaml-key">example</span>: <span class="yaml-number">1</span>
        <span class="yaml-key">username</span>:
          <span class="yaml-key">type</span>: <span class="yaml-string">string</span>
          <span class="yaml-key">minLength</span>: <span class="yaml-number">3</span>
          <span class="yaml-key">maxLength</span>: <span class="yaml-number">20</span>
          <span class="yaml-key">example</span>: <span class="yaml-string">"zhangsan"</span>
        <span class="yaml-key">email</span>:
          <span class="yaml-key">type</span>: <span class="yaml-string">string</span>
          <span class="yaml-key">format</span>: <span class="yaml-string">email</span>
          <span class="yaml-key">example</span>: <span class="yaml-string">"zhangsan@example.com"</span>
        <span class="yaml-key">role</span>:
          <span class="yaml-key">type</span>: <span class="yaml-string">string</span>
          <span class="yaml-key">enum</span>: [<span class="yaml-string">admin</span>, <span class="yaml-string">editor</span>, <span class="yaml-string">user</span>]
          <span class="yaml-key">default</span>: <span class="yaml-string">user</span>
        <span class="yaml-key">createdAt</span>:
          <span class="yaml-key">type</span>: <span class="yaml-string">string</span>
          <span class="yaml-key">format</span>: <span class="yaml-string">date-time</span>

    <span class="yaml-key">CreateUserRequest</span>:
      <span class="yaml-key">type</span>: <span class="yaml-string">object</span>
      <span class="yaml-key">required</span>: [<span class="yaml-string">username</span>, <span class="yaml-string">email</span>, <span class="yaml-string">password</span>]
      <span class="yaml-key">properties</span>:
        <span class="yaml-key">username</span>:
          <span class="yaml-key">type</span>: <span class="yaml-string">string</span>
        <span class="yaml-key">email</span>:
          <span class="yaml-key">type</span>: <span class="yaml-string">string</span>
          <span class="yaml-key">format</span>: <span class="yaml-string">email</span>
        <span class="yaml-key">password</span>:
          <span class="yaml-key">type</span>: <span class="yaml-string">string</span>
          <span class="yaml-key">format</span>: <span class="yaml-string">password</span>
          <span class="yaml-key">minLength</span>: <span class="yaml-number">8</span>
        <span class="yaml-key">role</span>:
          <span class="yaml-key">type</span>: <span class="yaml-string">string</span>
          <span class="yaml-key">enum</span>: [<span class="yaml-string">admin</span>, <span class="yaml-string">editor</span>, <span class="yaml-string">user</span>]

    <span class="yaml-key">UserResponse</span>:
      <span class="yaml-key">type</span>: <span class="yaml-string">object</span>
      <span class="yaml-key">properties</span>:
        <span class="yaml-key">code</span>:
          <span class="yaml-key">type</span>: <span class="yaml-string">integer</span>
          <span class="yaml-key">example</span>: <span class="yaml-number">0</span>
        <span class="yaml-key">message</span>:
          <span class="yaml-key">type</span>: <span class="yaml-string">string</span>
          <span class="yaml-key">example</span>: <span class="yaml-string">"success"</span>
        <span class="yaml-key">data</span>:
          <span class="yaml-key">$ref</span>: <span class="yaml-string">'#/components/schemas/User'</span>

    <span class="yaml-key">UserListResponse</span>:
      <span class="yaml-key">type</span>: <span class="yaml-string">object</span>
      <span class="yaml-key">properties</span>:
        <span class="yaml-key">code</span>:
          <span class="yaml-key">type</span>: <span class="yaml-string">integer</span>
        <span class="yaml-key">data</span>:
          <span class="yaml-key">type</span>: <span class="yaml-string">object</span>
          <span class="yaml-key">properties</span>:
            <span class="yaml-key">items</span>:
              <span class="yaml-key">type</span>: <span class="yaml-string">array</span>
              <span class="yaml-key">items</span>:
                <span class="yaml-key">$ref</span>: <span class="yaml-string">'#/components/schemas/User'</span>
            <span class="yaml-key">pagination</span>:
              <span class="yaml-key">$ref</span>: <span class="yaml-string">'#/components/schemas/Pagination'</span>

    <span class="yaml-key">Pagination</span>:
      <span class="yaml-key">type</span>: <span class="yaml-string">object</span>
      <span class="yaml-key">properties</span>:
        <span class="yaml-key">page</span>:
          <span class="yaml-key">type</span>: <span class="yaml-string">integer</span>
        <span class="yaml-key">pageSize</span>:
          <span class="yaml-key">type</span>: <span class="yaml-string">integer</span>
        <span class="yaml-key">total</span>:
          <span class="yaml-key">type</span>: <span class="yaml-string">integer</span>
        <span class="yaml-key">totalPages</span>:
          <span class="yaml-key">type</span>: <span class="yaml-string">integer</span>

    <span class="yaml-key">ErrorResponse</span>:
      <span class="yaml-key">type</span>: <span class="yaml-string">object</span>
      <span class="yaml-key">properties</span>:
        <span class="yaml-key">code</span>:
          <span class="yaml-key">type</span>: <span class="yaml-string">integer</span>
        <span class="yaml-key">message</span>:
          <span class="yaml-key">type</span>: <span class="yaml-string">string</span>
        <span class="yaml-key">errors</span>:
          <span class="yaml-key">type</span>: <span class="yaml-string">array</span>
          <span class="yaml-key">items</span>:
            <span class="yaml-key">type</span>: <span class="yaml-string">object</span>
            <span class="yaml-key">properties</span>:
              <span class="yaml-key">field</span>:
                <span class="yaml-key">type</span>: <span class="yaml-string">string</span>
              <span class="yaml-key">message</span>:
                <span class="yaml-key">type</span>: <span class="yaml-string">string</span>

  <span class="yaml-key">responses</span>:
    <span class="yaml-key">BadRequest</span>:
      <span class="yaml-key">description</span>: <span class="yaml-string">参数验证失败</span>
      <span class="yaml-key">content</span>:
        <span class="yaml-key">application/json</span>:
          <span class="yaml-key">schema</span>:
            <span class="yaml-key">$ref</span>: <span class="yaml-string">'#/components/schemas/ErrorResponse'</span>
    <span class="yaml-key">NotFound</span>:
      <span class="yaml-key">description</span>: <span class="yaml-string">资源不存在</span>
      <span class="yaml-key">content</span>:
        <span class="yaml-key">application/json</span>:
          <span class="yaml-key">schema</span>:
            <span class="yaml-key">$ref</span>: <span class="yaml-string">'#/components/schemas/ErrorResponse'</span>

<span class="yaml-key">security</span>:
  - <span class="yaml-key">BearerAuth</span>: []</div>
        </div>

        <!-- 文档工具 -->
        <div class="card">
            <h2>API文档工具生态</h2>
            <table style="width:100%; border-collapse:collapse; font-size:13px;">
                <thead>
                    <tr style="background:#2c3e50; color:white;">
                        <th style="padding:10px;">工具</th>
                        <th style="padding:10px;">类型</th>
                        <th style="padding:10px;">说明</th>
                    </tr>
                </thead>
                <tbody>
                    <tr style="border-bottom:1px solid #eee;">
                        <td style="padding:10px;"><strong>Swagger UI</strong></td>
                        <td style="padding:10px;">文档展示</td>
                        <td style="padding:10px;">最流行的API文档展示工具,支持在线测试</td>
                    </tr>
                    <tr style="border-bottom:1px solid #eee;">
                        <td style="padding:10px;"><strong>Redoc</strong></td>
                        <td style="padding:10px;">文档展示</td>
                        <td style="padding:10px;">更美观的文档展示,适合对外发布</td>
                    </tr>
                    <tr style="border-bottom:1px solid #eee;">
                        <td style="padding:10px;"><strong>Swagger Codegen</strong></td>
                        <td style="padding:10px;">代码生成</td>
                        <td style="padding:10px;">根据OpenAPI规范生成客户端SDK</td>
                    </tr>
                    <tr style="border-bottom:1px solid #eee;">
                        <td style="padding:10px;"><strong>Prism</strong></td>
                        <td style="padding:10px;">Mock服务器</td>
                        <td style="padding:10px;">根据OpenAPI规范启动Mock服务器</td>
                    </tr>
                    <tr style="border-bottom:1px solid #eee;">
                        <td style="padding:10px;"><strong>Spectral</strong></td>
                        <td style="padding:10px;">规范检查</td>
                        <td style="padding:10px;">OpenAPI规范Lint工具</td>
                    </tr>
                    <tr>
                        <td style="padding:10px;"><strong>Stoplight Studio</strong></td>
                        <td style="padding:10px;">可视化编辑</td>
                        <td style="padding:10px;">可视化OpenAPI编辑器</td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>
</body>
</html>

示例4:API Mock与测试

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>API Mock与测试</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            background: #0f0f23;
            color: #e0e0e0;
            min-height: 100vh;
            padding: 30px 20px;
        }
        .container { max-width: 900px; margin: 0 auto; }
        h1 { text-align: center; color: #48dbfb; margin-bottom: 30px; }
        .card {
            background: #1a1a2e;
            border: 1px solid #2d2d44;
            border-radius: 12px;
            padding: 24px;
            margin-bottom: 16px;
        }
        .card h2 { font-size: 16px; color: #feca57; margin-bottom: 16px; }
        .code-block {
            background: #0a0a1a;
            color: #a6e3a1;
            padding: 16px;
            border-radius: 8px;
            font-family: 'Courier New', monospace;
            font-size: 13px;
            line-height: 1.6;
            overflow-x: auto;
            white-space: pre;
            margin: 12px 0;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>API Mock 与测试</h1>

        <!-- Mock方案 -->
        <div class="card">
            <h2>前端Mock方案</h2>
            <div class="code-block">// 方案1:条件请求(开发环境使用Mock)
const API_BASE = process.env.NODE_ENV === 'development'
    ? '/mock-api'    // 开发环境指向Mock
    : '/api/v1';     // 生产环境指向真实API

// 方案2:请求拦截(Mock Service Worker)
// 安装:npm install msw --save-dev

// mocks/handlers.js
import { http, HttpResponse } from 'msw';

export const handlers = [
    // 获取用户列表
    http.get('/api/v1/users', ({ request }) => {
        const url = new URL(request.url);
        const page = parseInt(url.searchParams.get('page') || '1');
        const pageSize = parseInt(url.searchParams.get('pageSize') || '20');

        return HttpResponse.json({
            code: 0,
            message: 'success',
            data: {
                items: generateMockUsers(pageSize),
                pagination: {
                    page,
                    pageSize,
                    total: 156,
                    totalPages: 8
                }
            }
        });
    }),

    // 获取单个用户
    http.get('/api/v1/users/:id', ({ params }) => {
        const { id } = params;
        return HttpResponse.json({
            code: 0,
            message: 'success',
            data: {
                id: parseInt(id),
                username: `user_${id}`,
                email: `user${id}@example.com`,
                role: 'user',
                createdAt: new Date().toISOString()
            }
        });
    }),

    // 创建用户
    http.post('/api/v1/users', async ({ request }) => {
        const body = await request.json();

        // 模拟验证错误
        if (!body.username || body.username.length < 3) {
            return HttpResponse.json({
                code: 40001,
                message: '参数验证失败',
                errors: [
                    { field: 'username', message: '用户名至少3个字符' }
                ]
            }, { status: 400 });
        }

        return HttpResponse.json({
            code: 0,
            message: 'success',
            data: {
                id: Math.floor(Math.random() * 1000),
                ...body,
                createdAt: new Date().toISOString()
            }
        }, { status: 201 });
    }),

    // 模拟服务器错误
    http.get('/api/v1/error/500', () => {
        return HttpResponse.json({
            code: 50001,
            message: '服务器内部错误'
        }, { status: 500 });
    }),

    // 模拟延迟
    http.get('/api/v1/slow', async () => {
        await new Promise(resolve => setTimeout(resolve, 3000));
        return HttpResponse.json({ code: 0, data: { message: '延迟响应' } });
    })
];

function generateMockUsers(count) {
    return Array.from({ length: count }, (_, i) => ({
        id: i + 1,
        username: `user_${i + 1}`,
        email: `user${i + 1}@example.com`,
        role: ['admin', 'editor', 'user'][i % 3],
        status: ['active', 'inactive'][i % 2],
        createdAt: new Date(Date.now() - i * 86400000).toISOString()
    }));
}

// mocks/browser.js
import { setupWorker } from 'msw/browser';
import { handlers } from './handlers';

export const worker = setupWorker(...handlers);

// main.js - 启动MSW
if (process.env.NODE_ENV === 'development') {
    const { worker } = await import('./mocks/browser');
    await worker.start();
}

// 方案3:json-server(快速Mock REST API)
// npm install json-server --save-dev

// db.json
// {
//   "users": [
//     { "id": 1, "name": "张三", "email": "zhangsan@example.com" }
//   ],
//   "posts": [
//     { "id": 1, "title": "文章1", "userId": 1 }
//   ]
// }

// 启动:npx json-server db.json --port 3001
// 自动生成 RESTful API:
// GET    /users
// GET    /users/1
// POST   /users
// PUT    /users/1
// PATCH  /users/1
// DELETE /users/1

// 方案4:Vite插件Mock
// vite.config.js
import { viteMockServe } from 'vite-plugin-mock';

export default {
    plugins: [
        viteMockServe({
            mockPath: 'mock',     // mock文件目录
            localEnabled: true    // 开发环境启用
        })
    ]
};

// mock/user.js
export default [
    {
        url: '/api/v1/users',
        method: 'get',
        response: ({ query }) => ({
            code: 0,
            data: {
                items: [...],
                pagination: { page: query.page || 1, total: 100 }
            }
        })
    }
];</div>
        </div>

        <!-- API测试 -->
        <div class="card">
            <h2>API集成测试</h2>
            <div class="code-block">// 使用Jest + MSW进行API测试
import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';
import { userApi } from '../api/users';

// 设置Mock服务器
const server = setupServer(
    // 成功响应
    http.get('/api/v1/users/1', () => {
        return HttpResponse.json({
            code: 0,
            data: { id: 1, username: 'testuser', email: 'test@example.com' }
        });
    }),

    // 404响应
    http.get('/api/v1/users/999', () => {
        return HttpResponse.json(
            { code: 40401, message: '用户不存在' },
            { status: 404 }
        );
    })
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

describe('userApi', () => {
    test('获取用户成功', async () => {
        const result = await userApi.getById(1);
        expect(result.code).toBe(0);
        expect(result.data.username).toBe('testuser');
    });

    test('用户不存在返回404', async () => {
        await expect(userApi.getById(999)).rejects.toThrow();
    });

    test('创建用户参数验证', async () => {
        server.use(
            http.post('/api/v1/users', () => {
                return HttpResponse.json(
                    {
                        code: 40001,
                        message: '参数验证失败',
                        errors: [{ field: 'email', message: '邮箱格式不正确' }]
                    },
                    { status: 400 }
                );
            })
        );

        await expect(
            userApi.create({ username: 'test', email: 'invalid' })
        ).rejects.toThrow();
    });
});</div>
        </div>
    </div>
</body>
</html>

浏览器兼容性

特性 Chrome Firefox Safari Edge IE
Fetch API 42+ 39+ 10.1+ 14+ 不支持
AbortController 66+ 57+ 12.1+ 16+ 不支持
URLSearchParams 49+ 29+ 10.1+ 12+ 不支持
JSON.parse 全部 全部 全部 全部 8+

注意事项与最佳实践

1. API设计原则

代码示例

// 1. 命名一致性
// 使用camelCase(前端JS惯例)或snake_case(后端惯例),团队统一
// 推荐:camelCase(与JS一致)
{ "firstName": "张三", "lastName": "张" }

// 2. 时间格式统一使用ISO 8601
{ "createdAt": "2025-04-22T10:30:00Z" }

// 3. 金额使用字符串或分(避免浮点精度问题)
{ "price": "99.90" }       // 字符串
{ "priceInCents": 9990 }   // 分

// 4. 空值处理
// 方案1:省略空值字段
{ "name": "张三" }  // email为空时不包含此字段

// 方案2:使用null
{ "name": "张三", "email": null }

// 团队统一选择一种方案

// 5. ID使用字符串(避免大数精度问题)
{ "id": "1234567890123456789" }  // 推荐
{ "id": 1234567890123456789 }     // JS中可能丢失精度

2. API版本管理

代码示例

// 推荐方案:URL路径版本
// 优点:简单直观,易于理解和调试

// 版本升级策略:
// 1. 向后兼容的修改(新增字段)→ 不需要新版本
// 2. 破坏性修改(删除字段、修改类型)→ 新增版本

// v1和v2可以共存
// /api/v1/users - 旧版本
// /api/v2/users - 新版本

// 废弃通知
// 在响应头中添加废弃警告
// Deprecation: true
// Sunset: Sat, 01 Jan 2026 00:00:00 GMT
// Link: </api/v2/users>; rel="successor-version"

3. API安全

代码示例

// 1. 始终使用HTTPS
// 2. 实施速率限制(Rate Limiting)
// 响应头:
// X-RateLimit-Limit: 100        // 窗口内允许的请求数
// X-RateLimit-Remaining: 95     // 剩余请求数
// X-RateLimit-Reset: 1650000000 // 窗口重置时间

// 3. 输入验证
// 后端必须验证所有输入,不信任前端

// 4. CORS配置
// 只允许信任的源

// 5. 敏感数据脱敏
// 密码字段永远不返回
// 手机号中间四位脱敏:138****1234

代码规范示例

规范的API模块结构

代码示例

src/
 api/
    client.js          # HTTP客户端配置
    interceptors.js    # 请求/响应拦截器
    errors.js          # 自定义错误类
    users.js           # 用户API
    orders.js          # 订单API
    auth.js            # 认证API
    index.js           # 统一导出
 mock/
    handlers/          # MSW handlers
       users.js
       orders.js
    browser.js         # 浏览器端MSW
    server.js          # Node端MSW(测试用)
 types/
     api.d.ts           # API类型定义

常见问题与解决方案

问题1:前后端数据格式不一致

代码示例

// 问题:后端返回snake_case,前端使用camelCase

// 解决方案:使用转换层
function transformKeys(obj, transformer) {
    if (Array.isArray(obj)) {
        return obj.map(item => transformKeys(item, transformer));
    }
    if (obj !== null && typeof obj === 'object') {
        return Object.entries(obj).reduce((acc, [key, value]) => {
            acc[transformer(key)] = transformKeys(value, transformer);
            return acc;
        }, {});
    }
    return obj;
}

// snake_case → camelCase
const toCamelCase = (str) =>
    str.replace(/_([a-z])/g, (_, c) => c.toUpperCase());

// camelCase → snake_case
const toSnakeCase = (str) =>
    str.replace(/[A-Z]/g, c => `_${c.toLowerCase()}`);

// 在响应拦截器中自动转换
client.addResponseInterceptor((response) => {
    response.data = transformKeys(response.data, toCamelCase);
    return response;
});

// 在请求拦截器中自动转换
client.addRequestInterceptor((config) => {
    if (config.data) {
        config.data = transformKeys(config.data, toSnakeCase);
    }
    return config;
});

问题2:API文档与实际不一致

代码示例

// 解决方案1:契约测试(Pact)
// 前端定义期望的API响应格式
// 后端验证是否满足契约

// 解决方案2:OpenAPI规范 + 自动化验证
// 使用Spectral检查OpenAPI规范
// 使用Dredd或Schemathesis自动测试API是否符合文档

// 解决方案3:TypeScript类型生成
// 根据OpenAPI规范自动生成TypeScript类型
// npx openapi-typescript openapi.yaml -o types/api.d.ts

// 生成的类型
interface User {
    id: number;
    username: string;
    email: string;
    role: 'admin' | 'editor' | 'user';
    createdAt: string;
}

// 前端代码使用类型,编译时就能发现不一致

总结

API设计与文档是前后端协作的基石,本教程涵盖了以下内容:

  1. RESTful API设计:掌握资源导向设计、HTTP方法语义化、URL设计规则、状态码使用规范等核心原则。

  2. 响应格式规范:统一成功响应、列表响应(带分页)、错误响应的格式,建立前后端一致的数据契约。

  3. 错误码设计:设计结构化的错误码体系,包含HTTP状态码映射、业务错误码、字段级错误详情。

  4. 分页与过滤:实现页码分页和游标分页,支持排序、过滤、搜索、字段选择等查询参数。

  5. OpenAPI规范:使用OpenAPI 3.0描述API结构,包括路径、参数、请求体、响应、安全方案等。

  6. API文档工具:了解Swagger UI、Redoc、Swagger Codegen、Prism、Spectral等工具的用途。

  7. Mock与测试:掌握MSW、json-server、Vite插件等Mock方案,以及API集成测试的编写方法。

  8. 前端API层封装:规范API模块结构,统一请求客户端、拦截器、错误处理,实现类型安全的API调用。

常见问题

什么是20. API设计与文档?

20. API设计与文档是HTML5开发中的重要技术,本教程详细介绍了其核心概念和实践方法。

如何学习20. API设计与文档的实际应用?

教程中提供了完整的代码示例和实践指导,建议结合示例代码动手练习。

20. API设计与文档有哪些注意事项?

常见注意事项包括兼容性、性能优化等,教程的注意事项与最佳实践部分有详细说明。

20. API设计与文档适合初学者吗?

本教程从基础概念讲起,循序渐进,适合有一定HTML和JavaScript基础的初学者。

20. API设计与文档的核心要点是什么?

核心要点包括教程简介等内容,建议按教程顺序逐步学习。

标签: Promise HTTP头部 RESTful API OAuth 响应码 网络安全 键盘输入 async await

本文由小确幸生活整理发布,转载请注明出处

本文涉及AI创作

内容由AI创作,请仔细甄别

list快速访问

上一篇: 后端通信:19. 错误处理与重试 - 从入门到实践详解 下一篇: AI生成PPT工具对比:6款主流AI PPT制作工具详细评测

poll相关推荐