pin_drop当前位置:知识文库 ❯ 图文
后端通信:20. API设计与文档 - 从入门到实践详解
教程简介
API(Application Programming Interface)是前后端通信的契约。良好的API设计不仅能提升开发效率,还能降低沟通成本、减少集成问题。本教程将全面讲解RESTful API设计原则、API版本管理、请求与响应规范、错误码设计、分页与过滤、API文档编写(OpenAPI/Swagger规范)等内容,帮助你设计和维护高质量的Web API。
核心概念
RESTful API设计原则
HTTP方法与CRUD映射
API版本管理策略
响应格式规范
代码示例
// 成功响应
{
"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>浏览器兼容性
注意事项与最佳实践
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设计与文档是前后端协作的基石,本教程涵盖了以下内容:
RESTful API设计:掌握资源导向设计、HTTP方法语义化、URL设计规则、状态码使用规范等核心原则。
响应格式规范:统一成功响应、列表响应(带分页)、错误响应的格式,建立前后端一致的数据契约。
错误码设计:设计结构化的错误码体系,包含HTTP状态码映射、业务错误码、字段级错误详情。
分页与过滤:实现页码分页和游标分页,支持排序、过滤、搜索、字段选择等查询参数。
OpenAPI规范:使用OpenAPI 3.0描述API结构,包括路径、参数、请求体、响应、安全方案等。
API文档工具:了解Swagger UI、Redoc、Swagger Codegen、Prism、Spectral等工具的用途。
Mock与测试:掌握MSW、json-server、Vite插件等Mock方案,以及API集成测试的编写方法。
前端API层封装:规范API模块结构,统一请求客户端、拦截器、错误处理,实现类型安全的API调用。
常见问题
什么是20. API设计与文档?
20. API设计与文档是HTML5开发中的重要技术,本教程详细介绍了其核心概念和实践方法。
如何学习20. API设计与文档的实际应用?
教程中提供了完整的代码示例和实践指导,建议结合示例代码动手练习。
20. API设计与文档有哪些注意事项?
常见注意事项包括兼容性、性能优化等,教程的注意事项与最佳实践部分有详细说明。
20. API设计与文档适合初学者吗?
本教程从基础概念讲起,循序渐进,适合有一定HTML和JavaScript基础的初学者。
20. API设计与文档的核心要点是什么?
核心要点包括教程简介等内容,建议按教程顺序逐步学习。
本文由小确幸生活整理发布,转载请注明出处
本文涉及AI创作
内容由AI创作,请仔细甄别