0%

RESTful API 设计最佳实践

一、前言

在现代软件开发中,API(Application Programming Interface)已成为系统间通信的核心纽带。无论是微服务架构、前后端分离,还是第三方集成,都需要设计良好、易于使用的 API。

RESTful API 作为一种基于 REST(Representational State Transfer)架构风格的 API 设计方法,凭借其简洁性、可扩展性和无状态性,已成为业界事实标准。根据 2026 年 API 调查报告,83% 的公共 API 采用 RESTful 风格,而在企业内部微服务中,这一比例高达91%

然而,设计一个优秀的 RESTful API 并非易事。许多开发者在设计 API 时常常遇到以下问题:

  • ❌ 资源命名不规范,URL 混乱难懂
  • ❌ HTTP 方法使用不当,混淆 GET 和 POST
  • ❌ 状态码滥用,无法准确反映操作结果
  • ❌ 缺乏版本控制,升级导致兼容性问题
  • ❌ 错误处理不统一,客户端难以处理异常
  • ❌ 安全机制薄弱,存在数据泄露风险

本文将系统性地介绍 RESTful API 设计的最佳实践,涵盖从基础概念到高级特性的完整知识体系。通过 3 个架构图、2 个实战案例和大量代码示例,帮助你设计出专业、规范、易用的 RESTful API。


二、RESTful 核心概念

2.1 什么是 REST?

REST(Representational State Transfer)是一种软件架构风格,由 Roy Fielding 在 2000 年的博士论文中首次提出。REST 不是协议,也不是标准,而是一组设计原则和约束

REST 的六大约束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
graph TB
A[REST 架构] --> B[客户端 - 服务器分离]
A --> C[无状态]
A --> D[可缓存]
A --> E[统一接口]
A --> F[分层系统]
A --> G[按需代码]

B --> B1[前端专注 UI<br/>后端专注数据]
C --> C1[每个请求包含<br/>所有必要信息]
D --> D1[响应可缓存<br/>提升性能]
E --> E1[资源标识<br/>操作标准化]
F --> F1[隐藏实现细节<br/>提高可扩展性]
G --> G1[动态下载执行代码<br/>如 JavaScript]

2.2 RESTful API 的核心要素

1. 资源(Resource)

资源是 RESTful API 的核心概念。任何事物都可以被视为资源,如用户、订单、商品等。每个资源通过URI(统一资源标识符) 唯一标识。

1
2
3
4
5
# 资源示例
/users # 用户集合
/users/123 # ID 为 123 的用户
/orders # 订单集合
/orders/456 # ID 为 456 的订单

2. 表现层(Representation)

资源可以通过多种格式表示,如 JSON、XML、HTML 等。客户端通过 Accept 头指定期望的格式。

1
2
3
4
5
6
7
8
9
GET /users/123
Accept: application/json

Response:
{
"id": 123,
"name": "John",
"email": "john@example.com"
}

3. 状态转移(State Transfer)

客户端通过 HTTP 方法对资源进行操作,实现状态转移。常见的操作包括:获取、创建、更新、删除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
sequenceDiagram
participant Client as 客户端
participant API as RESTful API
participant DB as 数据库

Client->>API: POST /users (创建)
API->>DB: INSERT INTO users
DB-->>API: 返回 ID=123
API-->>Client: 201 Created + Location: /users/123

Client->>API: GET /users/123 (获取)
API->>DB: SELECT * FROM users WHERE id=123
DB-->>API: 返回用户数据
API-->>Client: 200 OK + 用户 JSON

Client->>API: PUT /users/123 (更新)
API->>DB: UPDATE users SET ...
DB-->>API: 更新成功
API-->>Client: 200 OK + 更新后数据

Client->>API: DELETE /users/123 (删除)
API->>DB: DELETE FROM users WHERE id=123
DB-->>API: 删除成功
API-->>Client: 204 No Content

三、资源命名规范

3.1 使用名词,避免动词

RESTful API 的核心是资源,而非操作。URL 应该使用名词表示资源,操作通过 HTTP 方法表达。

1
2
3
4
5
6
7
8
9
10
11
12
✅ 正确做法:
GET /users # 获取用户列表
POST /users # 创建用户
GET /users/123 # 获取用户详情
PUT /users/123 # 更新用户
DELETE /users/123 # 删除用户

❌ 错误做法:
GET /getUsers
POST /createUser
POST /updateUser
POST /deleteUser

3.2 使用复数名词

为保持一致性,资源名称应使用复数名词。这样可以明确表示操作的是资源集合。

1
2
3
4
5
6
7
8
✅ 正确做法:
/users # 用户集合
/orders # 订单集合
/products # 商品集合

❌ 错误做法:
/user # 单数,不一致
/User # 大小写不一致

3.3 使用小写字母和连字符

URL 应该使用小写字母,单词之间用连字符(-) 分隔,避免使用下划线或驼峰命名。

1
2
3
4
5
6
7
8
9
✅ 正确做法:
/user-profiles
/order-items
/product-categories

❌ 错误做法:
/userProfiles # 驼峰命名
/user_profiles # 下划线分隔
/UserProfiles # 大写字母

3.4 嵌套资源的使用

对于存在从属关系的资源,可以使用嵌套 URL 结构。但要注意嵌套层级不宜超过 3 层,否则会导致 URL 过于复杂。

1
2
3
4
5
6
7
8
9
10
11
12
13
✅ 合理嵌套(2 层):
/users/123/orders # 用户 123 的订单
/orders/456/items # 订单 456 的商品

✅ 合理嵌套(3 层):
/users/123/orders/456/items # 用户 123 的订单 456 的商品

❌ 过度嵌套(4 层+):
/users/123/orders/456/items/789/reviews # 过于复杂,建议扁平化

✅ 扁平化替代:
/order-items?order_id=456
/item-reviews?item_id=789

3.5 资源命名完整示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 用户相关
GET /users # 获取用户列表
POST /users # 创建用户
GET /users/{id} # 获取用户详情
PUT /users/{id} # 更新用户(全量)
PATCH /users/{id} # 更新用户(部分)
DELETE /users/{id} # 删除用户

# 订单相关
GET /orders # 获取订单列表
POST /orders # 创建订单
GET /orders/{id} # 获取订单详情
PUT /orders/{id} # 更新订单
DELETE /orders/{id} # 删除订单

# 嵌套资源
GET /users/{id}/orders # 获取用户的订单
GET /orders/{id}/items # 获取订单的商品

# 子资源操作
POST /users/{id}/avatar # 上传用户头像
GET /users/{id}/followers # 获取用户的粉丝
POST /users/{id}/follow # 关注用户

四、HTTP 方法使用规范

4.1 标准 HTTP 方法

RESTful API 应充分利用 HTTP 协议的标准方法,每种方法都有明确的语义。

1
2
3
4
5
6
7
8
9
10
11
12
graph LR
A[HTTP 方法] --> B[GET]
A --> C[POST]
A --> D[PUT]
A --> E[PATCH]
A --> F[DELETE]

B --> B1[获取资源<br/>安全/幂等]
C --> C1[创建资源<br/>不安全/不幂等]
D --> D1[全量更新<br/>不安全/幂等]
E --> E1[部分更新<br/>不安全/幂等]
F --> F1[删除资源<br/>不安全/幂等]

4.2 方法详解

1. GET - 获取资源

  • 用途:获取资源或资源列表
  • 特性:安全(不修改数据)、幂等(多次执行结果相同)
  • 请求体:不应包含请求体
  • 参数:通过查询字符串传递
1
2
3
4
5
6
7
8
# 获取用户列表
GET /users?page=1&limit=20&status=active

# 获取用户详情
GET /users/123

# 搜索用户
GET /users?name=john&city=beijing

2. POST - 创建资源

  • 用途:创建新资源或执行复杂操作
  • 特性:不安全(修改数据)、不幂等(多次执行创建多个资源)
  • 请求体:包含新资源的数据
  • 响应:返回 201 Created,Location 头指向新资源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 创建用户
POST /users
Content-Type: application/json

{
"name": "John",
"email": "john@example.com",
"password": "secure123"
}

Response:
HTTP/1.1 201 Created
Location: /users/123

{
"id": 123,
"name": "John",
"email": "john@example.com",
"created_at": "2026-03-12T10:00:00Z"
}

3. PUT - 全量更新

  • 用途:完全替换现有资源
  • 特性:不安全、幂等
  • 请求体:包含完整的资源数据
  • 注意:未提供的字段会被置为 null 或默认值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 全量更新用户
PUT /users/123
Content-Type: application/json

{
"name": "John Updated",
"email": "john.updated@example.com",
"age": 30,
"city": "Shanghai"
}

Response:
HTTP/1.1 200 OK

{
"id": 123,
"name": "John Updated",
"email": "john.updated@example.com",
"age": 30,
"city": "Shanghai",
"updated_at": "2026-03-12T11:00:00Z"
}

4. PATCH - 部分更新

  • 用途:部分修改资源
  • 特性:不安全、幂等(如果实现正确)
  • 请求体:只包含需要更新的字段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 部分更新用户
PATCH /users/123
Content-Type: application/json

{
"age": 31,
"city": "Beijing"
}

Response:
HTTP/1.1 200 OK

{
"id": 123,
"name": "John Updated",
"email": "john.updated@example.com",
"age": 31,
"city": "Beijing",
"updated_at": "2026-03-12T12:00:00Z"
}

5. DELETE - 删除资源

  • 用途:删除指定资源
  • 特性:不安全、幂等
  • 响应:通常返回 204 No Content
1
2
3
4
5
# 删除用户
DELETE /users/123

Response:
HTTP/1.1 204 No Content

4.3 特殊操作的处理

对于无法用标准 HTTP 方法表示的操作,可以使用以下方式:

1. 使用子资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 用户登录
POST /sessions
{
"email": "john@example.com",
"password": "secure123"
}

# 用户登出
DELETE /sessions/{session_id}

# 重置密码
POST /users/{id}/password-reset
{
"new_password": "newsecure123"
}

2. 使用自定义 HTTP 头

1
2
3
4
5
6
POST /orders/123
X-HTTP-Method-Override: APPROVE

# 或
POST /orders/123
Action: approve

3. 使用状态字段

1
2
3
4
PATCH /orders/123
{
"status": "approved"
}

五、HTTP 状态码使用规范

5.1 常用状态码

正确使用 HTTP 状态码可以帮助客户端准确理解操作结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 2xx 成功
200 OK # 请求成功(GET/PUT/PATCH)
201 Created # 资源创建成功(POST)
204 No Content # 请求成功,无返回内容(DELETE)

# 3xx 重定向
301 Moved Permanently # 资源永久移动
304 Not Modified # 资源未修改(缓存)

# 4xx 客户端错误
400 Bad Request # 请求格式错误
401 Unauthorized # 未认证
403 Forbidden # 无权限
404 Not Found # 资源不存在
409 Conflict # 资源冲突(如重复)
422 Unprocessable Entity # 数据验证失败
429 Too Many Requests # 请求过多(限流)

# 5xx 服务器错误
500 Internal Server Error # 服务器内部错误
502 Bad Gateway # 网关错误
503 Service Unavailable # 服务不可用

5.2 状态码选择指南

1
2
3
4
5
6
7
8
9
10
11
12
13
14
graph TD
A[请求处理] --> B{认证?}
B -->|否 | C[401 Unauthorized]
B -->|是 | D{授权?}
D -->|否 | E[403 Forbidden]
D -->|是 | F{资源存在?}
F -->|否 | G[404 Not Found]
F -->|是 | H{数据有效?}
H -->|否 | I[422 Unprocessable Entity]
H -->|是 | J{操作成功?}
J -->|是 | K{有返回?}
K -->|是 | L[200 OK]
K -->|否 | M[204 No Content]
J -->|否 | N[500 Internal Server Error]

5.3 错误响应格式

统一的错误响应格式有助于客户端处理异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"error": {
"code": "VALIDATION_ERROR",
"message": "请求数据验证失败",
"details": [
{
"field": "email",
"message": "邮箱格式不正确"
},
{
"field": "password",
"message": "密码长度至少 8 位"
}
],
"request_id": "req_abc123",
"timestamp": "2026-03-12T10:00:00Z"
}
}

六、版本控制策略

6.1 为什么需要版本控制?

API 在迭代过程中,可能需要修改接口行为。版本控制可以:

  • ✅ 保持向后兼容,不影响现有客户端
  • ✅ 平滑过渡,给客户端升级时间
  • ✅ 清晰标识 breaking changes

6.2 版本控制方案对比

1
2
3
4
5
6
7
8
9
10
graph TB
A[版本控制方案] --> B[URL 路径版本]
A --> C[查询参数版本]
A --> D[HTTP 头版本]
A --> E[内容协商版本]

B --> B1["/api/v1/users"<br/>✅ 直观易见<br/>❌ 违反 REST 原则]
C --> C1["/users?version=1"<br/>✅ 简单<br/>❌ 缓存困难]
D --> D1["X-API-Version: 1"<br/>✅ URL 干净<br/>❌ 调试不便]
E --> E1["Accept: application/vnd.api.v1+json"<br/>✅ RESTful<br/>❌ 复杂]

6.3 推荐方案:URL 路径版本

尽管存在争议,URL 路径版本 是最常用且最实用的方案。

1
2
3
4
5
# 版本 1
GET /api/v1/users

# 版本 2
GET /api/v2/users

优点

  • 直观可见,易于调试
  • 浏览器直接访问友好
  • 文档清晰明确

缺点

  • URL 包含版本,理论上违反 REST 原则
  • 需要维护多套路由

6.4 版本迭代策略

1. 增量添加,避免删除

1
2
3
4
5
6
7
8
9
10
11
12
13
// v1 响应
{
"name": "John",
"email": "john@example.com"
}

// v2 响应(添加新字段,不删除旧字段)
{
"name": "John",
"email": "john@example.com",
"phone": "+86-138-0000-0000", // 新增
"avatar": "https://..." // 新增
}

2. 废弃标记(Deprecation)

1
2
3
4
GET /api/v1/users/old-endpoint
Deprecation: true
Sunset: Sat, 01 Jun 2026 00:00:00 GMT
Link: </api/v2/users>; rel="successor-version"

3. 版本生命周期

1
2
3
4
5
6
7
8
9
gantt
title API 版本生命周期
dateFormat YYYY-MM-DD
section v1.0
活跃期 :active, 2024-01-01, 365d
废弃期 :crit, 2025-01-01, 180d
下线期 :done, 2025-07-01, 1d
section v2.0
活跃期 :active, 2025-01-01, 730d

七、分页、过滤、排序

7.1 分页策略

1. Offset-Limit 分页(传统方式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
GET /users?page=2&limit=20
# 或
GET /users?offset=20&limit=20

Response:
{
"data": [...],
"pagination": {
"page": 2,
"limit": 20,
"total": 150,
"total_pages": 8
}
}

优点:简单直观,支持跳页
缺点:大数据量性能差,数据可能重复/遗漏

2. Cursor 分页(推荐)

1
2
3
4
5
6
7
8
9
10
11
12
GET /users?limit=20&cursor=eyJpZCI6MTIzfQ==

Response:
{
"data": [...],
"pagination": {
"limit": 20,
"next_cursor": "eyJpZCI6MTQzfQ==",
"prev_cursor": "eyJpZCI6MTAzfQ==",
"has_more": true
}
}

优点:性能优异,数据一致性好
缺点:不支持跳页,实现复杂

7.2 过滤查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 单字段过滤
GET /users?status=active

# 多字段过滤
GET /users?status=active&city=beijing&role=admin

# 范围过滤
GET /orders?created_at[gte]=2026-01-01&created_at[lte]=2026-03-12

# 模糊搜索
GET /users?name[john]=contains

# IN 查询
GET /users?role[in]=admin,user

7.3 排序

1
2
3
4
5
6
7
8
# 单字段排序
GET /users?sort=created_at

# 多字段排序
GET /users?sort=created_at,-name

# 指定方向
GET /users?sort[created_at]=desc&sort[name]=asc

7.4 字段选择

1
2
3
4
5
# 只返回指定字段
GET /users?fields=id,name,email

# 嵌套资源字段选择
GET /users?fields=id,name&include=orders&fields[orders]=id,total

八、安全与认证

8.1 认证方式对比

1
2
3
4
5
6
7
8
9
10
graph TB
A[认证方式] --> B[API Key]
A --> C[JWT]
A --> D[OAuth 2.0]
A --> E[Basic Auth]

B --> B1[简单<br/>适合服务端<br/>❌ 无过期]
C --> C1[无状态<br/>适合移动端<br/>✅ 推荐]
D --> D1[授权流程<br/>适合第三方<br/>✅ 标准]
E --> E1[基础认证<br/>❌ 不推荐生产]

8.2 JWT 认证实现

1. 登录获取 Token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /auth/login
Content-Type: application/json

{
"email": "john@example.com",
"password": "secure123"
}

Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "dGhpcyBpcyBhIHJlZnJl..."
}

2. 使用 Token 访问资源

1
2
GET /users/123
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

3. Token 刷新

1
2
3
4
5
6
7
8
9
10
11
12
13
POST /auth/refresh
Content-Type: application/json

{
"refresh_token": "dGhpcyBpcyBhIHJlZnJl..."
}

Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600
}

8.3 安全最佳实践

1
2
3
4
5
6
7
8
// 1. 始终使用 HTTPS
// 2. 敏感信息加密存储
// 3. 密码加盐哈希(bcrypt/argon2)
// 4. 设置合理的 Token 过期时间
// 5. 实现限流防止暴力攻击
// 6. 验证输入防止注入攻击
// 7. 记录安全日志
// 8. 定期轮换密钥

九、实战案例

案例 1:电商平台 API 设计

背景:某电商平台需要设计一套 RESTful API,支持用户、商品、订单、支付等核心功能。

需求分析

  • 用户管理(注册、登录、个人信息)
  • 商品管理(CRUD、搜索、分类)
  • 订单管理(创建、支付、发货、退款)
  • 购物车管理
  • 评价系统

API 设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 用户相关
POST /api/v1/users # 注册
POST /api/v1/auth/login # 登录
GET /api/v1/users/me # 获取当前用户
PUT /api/v1/users/me # 更新用户信息
POST /api/v1/users/me/avatar # 上传头像

# 商品相关
GET /api/v1/products # 商品列表
GET /api/v1/products/{id} # 商品详情
POST /api/v1/products # 创建商品(管理员)
PUT /api/v1/products/{id} # 更新商品
DELETE /api/v1/products/{id} # 删除商品
GET /api/v1/categories # 分类列表
GET /api/v1/categories/{id}/products # 分类下商品

# 购物车
GET /api/v1/cart # 获取购物车
POST /api/v1/cart/items # 添加商品
PUT /api/v1/cart/items/{id} # 更新数量
DELETE /api/v1/cart/items/{id} # 删除商品

# 订单
POST /api/v1/orders # 创建订单
GET /api/v1/orders # 订单列表
GET /api/v1/orders/{id} # 订单详情
POST /api/v1/orders/{id}/pay # 支付订单
POST /api/v1/orders/{id}/cancel # 取消订单
POST /api/v1/orders/{id}/refund # 申请退款

# 评价
POST /api/v1/orders/{id}/review # 创建评价
GET /api/v1/products/{id}/reviews # 商品评价列表

实现代码(Node.js + Express)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
// routes/products.js
const express = require('express');
const router = express.Router();
const { body, query, param } = require('express-validator');
const Product = require('../models/Product');
const { authenticate, authorize } = require('../middleware/auth');

// 获取商品列表
router.get('/', [
query('page').optional().isInt({ min: 1 }),
query('limit').optional().isInt({ min: 1, max: 100 }),
query('category').optional().isString()
], async (req, res) => {
try {
const { page = 1, limit = 20, category } = req.query;
const offset = (page - 1) * limit;

const where = category ? { category_id: category } : {};

const [products, total] = await Promise.all([
Product.findAll({ where, offset, limit }),
Product.count({ where })
]);

res.json({
data: products,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total,
total_pages: Math.ceil(total / limit)
}
});
} catch (error) {
res.status(500).json({
error: {
code: 'SERVER_ERROR',
message: '获取商品列表失败',
request_id: req.id
}
});
}
});

// 获取商品详情
router.get('/:id', [
param('id').isInt()
], async (req, res) => {
try {
const product = await Product.findByPk(req.params.id);

if (!product) {
return res.status(404).json({
error: {
code: 'NOT_FOUND',
message: '商品不存在',
request_id: req.id
}
});
}

res.json({ data: product });
} catch (error) {
res.status(500).json({
error: {
code: 'SERVER_ERROR',
message: '获取商品详情失败',
request_id: req.id
}
});
}
});

// 创建商品(管理员)
router.post('/', [
authenticate,
authorize(['admin']),
body('name').notEmpty().isString(),
body('price').isFloat({ min: 0 }),
body('category_id').isInt()
], async (req, res) => {
try {
const product = await Product.create(req.body);

res.status(201).json({
data: product,
links: {
self: `/api/v1/products/${product.id}`
}
});
} catch (error) {
res.status(422).json({
error: {
code: 'VALIDATION_ERROR',
message: '创建商品失败',
details: error.errors,
request_id: req.id
}
});
}
});

module.exports = router;

案例 2:任务管理系统 API 设计

背景:某团队协作工具需要设计任务管理 API,支持项目、任务、成员、评论等功能。

核心资源

  • 项目(Project)
  • 任务(Task)
  • 成员(Member)
  • 评论(Comment)
  • 附件(Attachment)

API 设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 项目管理
GET /api/v1/projects # 项目列表
POST /api/v1/projects # 创建项目
GET /api/v1/projects/{id} # 项目详情
PUT /api/v1/projects/{id} # 更新项目
DELETE /api/v1/projects/{id} # 删除项目

# 任务管理
GET /api/v1/projects/{id}/tasks # 项目任务列表
POST /api/v1/projects/{id}/tasks # 创建任务
GET /api/v1/tasks/{id} # 任务详情
PUT /api/v1/tasks/{id} # 更新任务
DELETE /api/v1/tasks/{id} # 删除任务
PATCH /api/v1/tasks/{id}/status # 更新任务状态

# 成员管理
GET /api/v1/projects/{id}/members # 项目成员
POST /api/v1/projects/{id}/members # 添加成员
DELETE /api/v1/projects/{id}/members/{user_id} # 移除成员

# 评论
GET /api/v1/tasks/{id}/comments # 任务评论
POST /api/v1/tasks/{id}/comments # 添加评论
PUT /api/v1/comments/{id} # 更新评论
DELETE /api/v1/comments/{id} # 删除评论

# 附件
POST /api/v1/tasks/{id}/attachments # 上传附件
GET /api/v1/tasks/{id}/attachments # 附件列表
DELETE /api/v1/attachments/{id} # 删除附件

状态机设计

1
2
3
4
5
6
7
8
9
10
stateDiagram-v2
[*] --> BACKLOG: 创建
BACKLOG --> TODO: 开始处理
TODO --> IN_PROGRESS: 进行中
IN_PROGRESS --> REVIEW: 提交审核
REVIEW --> DONE: 审核通过
REVIEW --> IN_PROGRESS: 审核不通过
TODO --> BACKLOG: 暂停
IN_PROGRESS --> BACKLOG: 暂停
DONE --> [*]: 完成

任务状态更新实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// routes/tasks.js
router.patch('/:id/status', [
authenticate,
param('id').isInt(),
body('status').isIn(['backlog', 'todo', 'in_progress', 'review', 'done'])
], async (req, res) => {
try {
const task = await Task.findByPk(req.params.id, {
include: [{ model: Project, as: 'project' }]
});

if (!task) {
return res.status(404).json({
error: { code: 'NOT_FOUND', message: '任务不存在' }
});
}

// 检查权限
const member = await ProjectMember.findOne({
where: { project_id: task.project.id, user_id: req.user.id }
});

if (!member) {
return res.status(403).json({
error: { code: 'FORBIDDEN', message: '无权限操作' }
});
}

// 状态机验证
const validTransitions = {
backlog: ['todo'],
todo: ['in_progress', 'backlog'],
in_progress: ['review', 'backlog'],
review: ['done', 'in_progress'],
done: []
};

if (!validTransitions[task.status].includes(req.body.status)) {
return res.status(422).json({
error: {
code: 'INVALID_TRANSITION',
message: `无法从 ${task.status} 转移到 ${req.body.status}`
}
});
}

await task.update({ status: req.body.status });

res.json({
data: task,
links: { self: `/api/v1/tasks/${task.id}` }
});
} catch (error) {
res.status(500).json({
error: { code: 'SERVER_ERROR', message: '更新状态失败' }
});
}
});

十、性能优化

10.1 缓存策略

1. HTTP 缓存头

1
2
3
4
5
6
7
8
9
# 强缓存(1 小时)
Cache-Control: public, max-age=3600

# 协商缓存
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Last-Modified: Wed, 12 Mar 2026 10:00:00 GMT

# 禁止缓存
Cache-Control: no-store, no-cache, must-revalidate

2. 响应缓存实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// middleware/cache.js
const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 300 });

function cacheResponse(key, ttl = 300) {
return (req, res, next) => {
const cached = cache.get(key);
if (cached) {
return res.json(cached);
}

const originalJson = res.json;
res.json = (data) => {
cache.set(key, data, ttl);
return originalJson.call(res, data);
};

next();
};
}

// 使用
router.get('/products', cacheResponse('products:all'), getProductList);

10.2 数据压缩

1
2
3
4
5
6
// 启用 Gzip 压缩
const compression = require('compression');
app.use(compression());

// 响应头
Content-Encoding: gzip

10.3 数据库优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 1. 使用索引
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_orders_user_id ON orders(user_id);

// 2. 避免 N+1 查询
// ❌ 错误
users.forEach(user => {
const orders = await Order.findByUserId(user.id);
});

// ✅ 正确
const users = await User.findAll({ include: [Order] });

// 3. 分页查询
const products = await Product.findAll({
offset: 0,
limit: 20,
order: [['created_at', 'DESC']]
});

十一、文档与测试

11.1 API 文档工具

1. Swagger/OpenAPI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
openapi: 3.0.0
info:
title: 电商平台 API
version: 1.0.0
paths:
/users:
get:
summary: 获取用户列表
parameters:
- name: page
in: query
schema:
type: integer
default: 1
- name: limit
in: query
schema:
type: integer
default: 20
responses:
'200':
description: 成功
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/User'

2. Postman 集合

导出 Postman 集合,方便团队测试和分享。

11.2 自动化测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// tests/users.test.js
const request = require('supertest');
const app = require('../app');

describe('Users API', () => {
describe('GET /users', () => {
it('应该返回用户列表', async () => {
const res = await request(app)
.get('/api/v1/users')
.expect(200);

expect(res.body.data).toBeInstanceOf(Array);
expect(res.body.pagination).toBeDefined();
});

it('应该支持分页', async () => {
const res = await request(app)
.get('/api/v1/users?page=2&limit=10')
.expect(200);

expect(res.body.pagination.page).toBe(2);
expect(res.body.pagination.limit).toBe(10);
});
});

describe('POST /users', () => {
it('应该创建新用户', async () => {
const res = await request(app)
.post('/api/v1/users')
.send({
name: 'Test User',
email: 'test@example.com',
password: 'secure123'
})
.expect(201);

expect(res.body.data.id).toBeDefined();
expect(res.body.data.email).toBe('test@example.com');
});

it('应该验证必填字段', async () => {
const res = await request(app)
.post('/api/v1/users')
.send({ name: 'Test' })
.expect(422);

expect(res.body.error.code).toBe('VALIDATION_ERROR');
});
});
});

十二、总结

设计优秀的 RESTful API 需要遵循一系列最佳实践:

核心要点回顾

资源命名:使用复数名词、小写、连字符
HTTP 方法:正确使用 GET/POST/PUT/PATCH/DELETE
状态码:准确反映操作结果
版本控制:URL 路径版本最实用
分页过滤:支持灵活的数据查询
安全认证:JWT + HTTPS 是标配
错误处理:统一格式,便于调试
文档测试:Swagger + 自动化测试

检查清单

在发布 API 前,请确认:

  • URL 命名符合规范
  • HTTP 方法使用正确
  • 状态码准确
  • 错误响应格式统一
  • 实现了认证授权
  • 有完整的文档
  • 有自动化测试覆盖
  • 实现了限流和缓存
  • 日志记录完整
  • 监控告警配置

最后更新: 2026-03-12

标签: #RESTful #API 设计 #HTTP #微服务 #后端架构

分类: 后端开发/API 设计

参考资料: