好的 API 设计是后端服务的基石。本文系统梳理 REST 架构风格的核心约束、URL 设计规范、HTTP 方法语义、状态码使用、统一响应格式、分页策略和版本控制方案。
1.1 什么是 REST
REST(Representational State Transfer,表述性状态转移)由 Roy Fielding 在 2000 年的博士论文《Architectural Styles and the Design of Network-based Software Architectures》中提出。它不是协议,不是标准,而是一种架构风格 – 一组设计分布式系统的约束条件。
REST 的核心思想:
- 资源(Resource):网络上的任何实体都是资源,用 URI 标识
- 表述(Representation):资源的具体表现形式(JSON、XML、HTML 等)
- 状态转移(State Transfer):通过 HTTP 方法对资源进行操作,实现状态变更
资源的 URI: /users/123
资源的表述: {"id": 123, "name": "Alice", "email": "alice@example.com"}
状态转移: DELETE /users/123 -> 资源从"存在"变为"不存在"
1.1.1 REST 与 HTTP 的关系
REST 经常与 HTTP 混淆,但它们是不同层面的概念:
| 维度 | REST | HTTP |
|---|---|---|
| 本质 | 架构风格 | 传输协议 |
| 关注点 | 如何设计 API | 如何传输数据 |
| 依赖 | 不依赖特定协议(但通常基于 HTTP) | 具体的协议实现 |
REST API 通常基于 HTTP,但 REST 本身不限定传输协议。
1.2 REST 六大约束
Fielding 定义了六个约束条件,满足全部六个约束的系统才是 RESTful 的。
约束一:客户端-服务器分离(Client-Server)
前后端分离,独立演进,通过统一接口通信。
客户端(前端) 服务器(后端)
React/Vue <-> FastAPI/Django
移动端 App <-> API 服务
第三方服务 <-> 微服务
好处:
- 前后端可独立开发、部署、扩展
- 服务器可被不同客户端复用
- 关注点分离,降低系统复杂度
约束二:无状态(Stateless)
每个请求必须包含处理该请求所需的全部信息,服务器不在请求之间存储客户端上下文。
# 无状态(REST 推荐)
GET /users/123
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
# 有状态(传统 Session)
GET /users/123
Cookie: session_id=abc123 # 服务器需要记住这个 session
为什么无状态重要:
- 服务器可水平扩展,任意节点都能处理请求
- 请求可被路由到不同服务器,无需会话亲和性
- 系统更可靠,单节点故障不影响其他请求
约束三:可缓存(Cacheable)
响应必须明确标记是否可缓存,以提升性能。
# 服务器响应头
Cache-Control: max-age=3600 # 缓存 1 小时
ETag: "abc123" # 资源版本标识
# 客户端条件请求
If-None-Match: "abc123" # 资源未变化则返回 304
| HTTP 方法 | 默认可缓存 | 说明 |
|---|---|---|
| GET | 是 | 获取资源,通常可缓存 |
| POST | 否 | 创建资源,通常不缓存 |
| PUT | 否 | 更新资源 |
| DELETE | 否 | 删除资源 |
约束四:统一接口(Uniform Interface)
这是 REST 最核心的约束,包含四个子约束:
- 资源标识:每个资源有唯一 URI
- 资源表述:通过表述操作资源(如 JSON body)
- 自描述消息:请求/响应包含足够的元信息(Content-Type、Accept 等)
- 超媒体即应用状态引擎(HATEOAS):响应中包含可执行的下一步操作链接
// HATEOAS 示例
{
"id": 123,
"name": "Alice",
"links": [
{"rel": "self", "href": "/users/123", "method": "GET"},
{"rel": "update", "href": "/users/123", "method": "PATCH"},
{"rel": "delete", "href": "/users/123", "method": "DELETE"},
{"rel": "orders", "href": "/users/123/orders", "method": "GET"}
]
}
实际项目中,HATEOAS 实施成本较高,大多数 REST API 只实现前三个子约束。
约束五:分层系统(Layered System)
客户端无需知道是直连服务器还是通过中间层。
客户端 -> 负载均衡 -> API 网关 -> 认证服务 -> 业务服务 -> 数据库
好处:
- 可在不同层添加安全策略、负载均衡、缓存
- 单层变化不影响其他层
约束六:按需代码(Code-On-Demand,可选)
服务器可返回可执行代码扩展客户端功能(如返回 JavaScript)。
这是唯一一个可选约束,实际 REST API 中很少使用。
1.3 URL 设计规范
URL 是 API 的门面,好的 URL 设计让 API 自解释。
1.3.1 使用名词复数,避免动词
API 围绕资源设计,HTTP 方法已经表达了操作语义。
# 正确:名词 + HTTP 方法表达操作
GET /api/v1/users # 获取用户列表
POST /api/v1/users # 创建用户
GET /api/v1/users/123 # 获取用户
PUT /api/v1/users/123 # 更新用户
DELETE /api/v1/users/123 # 删除用户
# 错误:URL 中包含动词
GET /api/v1/getUsers
POST /api/v1/createUser
DELETE /api/v1/deleteUser/123
例外情况:当操作无法映射到 CRUD 时,可以用动词表示动作:
POST /api/v1/users/123/activate # 激活用户
POST /api/v1/orders/456/cancel # 取消订单
POST /api/v1/reports/export # 导出报告
POST /api/v1/auth/login # 登录(动作,非资源)
POST /api/v1/auth/logout # 登出
1.3.2 使用复数形式
# 正确
GET /users
GET /users/123
GET /products
GET /orders
# 避免
GET /user # 单数不清晰:是获取一个还是列表?
GET /user/123 # 语义:/user 资源下的 123?
1.3.3 嵌套资源控制层级
表达资源间的从属关系时使用嵌套,但不超过 2 层。
# 合理:用户 -> 订单(1 层嵌套)
GET /users/123/orders
GET /users/123/orders/456
# 过深:应扁平化
GET /users/123/orders/456/items/789/details
# 改为扁平设计
GET /items/789
GET /items?order_id=456
GET /orders/456/items
判断标准:如果子资源可以独立存在,就用扁平设计;如果强依赖父资源,就用嵌套。
1.3.4 使用连字符,小写字母
# 正确
GET /user-profiles
GET /order-items
GET /api/v1/user-accounts
# 避免
GET /user_profiles # 下划线在某些字体中不易辨识
GET /userProfiles # 驼峰不适合 URL
GET /UserProfiles # 大写字母在 URL 中不规范
1.3.5 查询参数的使用场景
# 过滤
GET /users?status=active&role=admin
# 排序
GET /users?sort_by=created_at&order=desc
# 分页
GET /users?page=1&page_size=20
# 搜索
GET /users?keyword=alice
# 字段选择(减少响应体积)
GET /users?fields=id,name,email
# 组合
GET /users?status=active&sort_by=name&order=asc&page=1&page_size=10
1.4 HTTP 方法语义
1.4.1 标准方法
| 方法 | 用途 | 幂等 | 安全 | 请求体 | 响应体 |
|---|---|---|---|---|---|
| GET | 获取资源 | 是 | 是 | 否 | 是 |
| POST | 创建资源 | 否 | 否 | 是 | 是 |
| PUT | 完整替换资源 | 是 | 否 | 是 | 可选 |
| PATCH | 部分更新资源 | 否* | 否 | 是 | 是 |
| DELETE | 删除资源 | 是 | 否 | 否 | 可选 |
| HEAD | 获取资源元信息 | 是 | 是 | 否 | 否 |
| OPTIONS | 获取支持的方法 | 是 | 是 | 否 | 是 |
幂等:多次请求产生相同结果。GET、PUT、DELETE 是幂等的;POST 不是。 安全:不修改服务器状态。只有 GET 和 HEAD 是安全的。 *PATCH 理论上不幂等,但实践中可以设计为幂等。
1.4.2 GET – 获取资源
# 获取列表
GET /api/v1/users?page=1&page_size=20 HTTP/1.1
Accept: application/json
# 获取单个资源
GET /api/v1/users/123 HTTP/1.1
Accept: application/json
# 响应
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: max-age=60
{"id": 123, "name": "Alice", "email": "alice@example.com"}
GET 请求:
- 不应有副作用(不修改服务器状态)
- 可被缓存
- 可被浏览器预取
- 参数通过查询字符串传递
1.4.3 POST – 创建资源
POST /api/v1/users HTTP/1.1
Content-Type: application/json
{"name": "Bob", "email": "bob@example.com"}
# 响应
HTTP/1.1 201 Created
Content-Type: application/json
Location: /api/v1/users/124
{"id": 124, "name": "Bob", "email": "bob@example.com", "created_at": "2026-05-26T10:00:00Z"}
POST 请求:
- 用于创建新资源
- 服务器分配资源 ID
- 响应应包含
Location头指向新资源 - 状态码用
201 Created - 不幂等:相同请求会创建多个资源
1.4.4 PUT – 完整替换
PUT /api/v1/users/123 HTTP/1.1
Content-Type: application/json
{"name": "Alice Updated", "email": "alice.new@example.com", "age": 26}
# 响应
HTTP/1.1 200 OK
Content-Type: application/json
{"id": 123, "name": "Alice Updated", "email": "alice.new@example.com", "age": 26}
PUT 请求:
- 完整替换资源,未提供的字段应被设为默认值或 null
- 幂等:多次相同请求结果一致
- 如果资源不存在,可选择创建(
201)或报错(404)
1.4.5 PATCH – 部分更新
PATCH /api/v1/users/123 HTTP/1.1
Content-Type: application/json
{"age": 27}
# 响应
HTTP/1.1 200 OK
Content-Type: application/json
{"id": 123, "name": "Alice Updated", "email": "alice.new@example.com", "age": 27}
PATCH 请求:
- 只更新提供的字段,其他字段保持不变
- 比 PUT 更常用(大多数更新场景只需改部分字段)
- 理论上不幂等,但可以设计为幂等
1.4.6 DELETE – 删除资源
DELETE /api/v1/users/123 HTTP/1.1
# 响应(无返回体)
HTTP/1.1 204 No Content
# 或者返回删除的资源
HTTP/1.1 200 OK
Content-Type: application/json
{"id": 123, "name": "Alice", "deleted": true}
DELETE 请求:
- 幂等:删除已删除的资源仍返回成功
- 软删除场景返回
200并标记deleted字段 - 硬删除场景返回
204 No Content
1.5 HTTP 状态码详解
状态码是 API 与客户端沟通的重要方式。正确使用状态码能让客户端快速判断请求结果。
1.5.1 2xx – 成功
| 状态码 | 含义 | 使用场景 |
|---|---|---|
| 200 OK | 请求成功 | GET、PUT、PATCH 的常规响应 |
| 201 Created | 资源已创建 | POST 创建资源成功 |
| 202 Accepted | 请求已接受,待处理 | 异步任务提交成功 |
| 204 No Content | 成功,无返回体 | DELETE 成功 |
201 Created 场景:
POST /users -> 创建成功 -> 返回新用户 + Location 头
202 Accepted 场景:
POST /reports/generate -> 提交生成任务 -> 返回任务 ID
GET /reports/tasks/456 -> 查询任务状态
204 No Content 场景:
DELETE /users/123 -> 删除成功 -> 无返回体
1.5.2 4xx – 客户端错误
| 状态码 | 含义 | 使用场景 |
|---|---|---|
| 400 Bad Request | 请求格式错误 | JSON 解析失败、缺少必填字段 |
| 401 Unauthorized | 未认证 | 缺少 Token、Token 过期 |
| 403 Forbidden | 无权限 | 已认证但权限不足 |
| 404 Not Found | 资源不存在 | 访问不存在的资源 |
| 405 Method Not Allowed | 方法不支持 | 对只读资源使用 DELETE |
| 409 Conflict | 资源冲突 | 重复创建(如邮箱已注册) |
| 422 Unprocessable Entity | 验证失败 | 字段格式正确但业务验证不通过 |
| 429 Too Many Requests | 请求超限 | 触发限流 |
401 vs 403 的区别:
401 Unauthorized(未认证):
"你是谁?请先证明身份。"
-> 缺少认证信息
-> Token 无效或过期
-> 客户端应重新登录
403 Forbidden(无权限):
"我知道你是谁,但你没有权限。"
-> 认证通过,但权限不足
-> 普通用户访问管理员接口
-> 客户端不应重试
1.5.3 5xx – 服务端错误
| 状态码 | 含义 | 使用场景 |
|---|---|---|
| 500 Internal Server Error | 服务器内部错误 | 未捕获的异常 |
| 502 Bad Gateway | 网关错误 | 上游服务返回无效响应 |
| 503 Service Unavailable | 服务不可用 | 服务器过载或维护 |
| 504 Gateway Timeout | 网关超时 | 上游服务响应超时 |
原则:5xx 错误不应暴露内部细节,生产环境返回通用错误信息。
1.6 统一响应格式
一致的响应格式降低客户端解析成本,是 API 设计的关键一环。
1.6.1 基础响应结构
// 成功响应
{
"code": 200,
"message": "success",
"data": { ... }
}
// 错误响应
{
"code": 422,
"message": "请求参数验证失败",
"errors": [
{"field": "email", "detail": "邮箱格式不正确"},
{"field": "age", "detail": "年龄必须大于 0"}
]
}
1.6.2 分页响应
{
"code": 200,
"message": "success",
"data": {
"items": [
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"}
],
"pagination": {
"total": 100,
"page": 1,
"page_size": 20,
"total_pages": 5
}
}
}
1.6.3 分页参数规范
| 参数 | 说明 | 默认值 | 示例 |
|---|---|---|---|
page | 页码(从 1 开始) | 1 | ?page=2 |
page_size / limit | 每页数量 | 20 | ?page_size=50 |
cursor | 游标分页标识 | - | ?cursor=eyJpZCI6MTAwfQ== |
偏移分页 vs 游标分页:
偏移分页(Offset Pagination):
GET /users?page=1&page_size=20
GET /users?page=2&page_size=20
优点:支持跳页
缺点:深页性能差(OFFSET 100000 很慢)、数据变动导致错位
游标分页(Cursor Pagination):
GET /users?page_size=20
GET /users?page_size=20&cursor=eyJpZCI6MjB9
优点:性能稳定、无数据错位
缺点:只能前后翻页,不能跳页
建议:数据量大、实时性高的场景(如信息流)用游标分页;后台管理等需要跳页的场景用偏移分页。
1.7 API 版本控制
当 API 需要不兼容的变更时,版本控制保护现有客户端。
1.7.1 版本控制策略
| 策略 | 示例 | 优点 | 缺点 |
|---|---|---|---|
| URL 路径 | /api/v1/users | 直观、易路由 | URL 变长 |
| 请求头 | Accept: application/vnd.api+json;version=1 | URL 干净 | 不直观、调试不便 |
| 查询参数 | /users?version=1 | 简单 | 不规范、易混淆 |
推荐:URL 路径方式,最直观且易于实现。
1.7.2 版本管理最佳实践
/api/v1/users # v1 版本
/api/v2/users # v2 版本(不兼容变更)
# v1 版本标记为 deprecated,设置过渡期
# 响应头:
Deprecation: true
Sunset: Sat, 01 Jan 2027 00:00:00 GMT
Link: </api/v2/users>; rel="successor-version"
原则:
- 新增字段不需要新版本(向后兼容)
- 删除字段、修改字段类型、改变语义需要新版本
- 旧版本至少保留 6 个月过渡期
- 同时维护不超过 2 个版本
1.8 实际设计示例
1.8.1 用户管理 API 设计
# 用户 CRUD
GET /api/v1/users # 用户列表(分页、过滤、排序)
POST /api/v1/users # 创建用户
GET /api/v1/users/{id} # 获取用户详情
PATCH /api/v1/users/{id} # 更新用户信息
DELETE /api/v1/users/{id} # 删除用户
# 用户子资源
GET /api/v1/users/{id}/orders # 用户的订单列表
GET /api/v1/users/{id}/posts # 用户的文章列表
# 用户动作
POST /api/v1/users/{id}/activate # 激活用户
POST /api/v1/users/{id}/deactivate # 停用用户
POST /api/v1/users/{id}/reset-password # 重置密码
1.8.2 电商 API 设计
# 商品
GET /api/v1/products # 商品列表
GET /api/v1/products/{id} # 商品详情
GET /api/v1/products/{id}/reviews # 商品评价
# 购物车
GET /api/v1/cart # 获取购物车
POST /api/v1/cart/items # 添加商品到购物车
PATCH /api/v1/cart/items/{id} # 更新购物车商品数量
DELETE /api/v1/cart/items/{id} # 从购物车移除
# 订单
GET /api/v1/orders # 订单列表
POST /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}/confirm # 确认收货
1.9 常见设计反模式
| 反模式 | 问题 | 正确做法 |
|---|---|---|
| URL 包含动词 | /getUsers、/createOrder | 用 HTTP 方法表达操作 |
| 忽略状态码 | 所有响应都返回 200 | 使用语义化的状态码 |
| 返回体不一致 | 有时返回 {data},有时直接返回数组 | 统一响应格式 |
| 过度嵌套 | /users/1/orders/2/items/3/reviews | 扁平化设计 |
| 忽略版本控制 | 破坏性变更直接改接口 | URL 路径版本控制 |
| 暴露内部实现 | /api/getUserByIdFromDb | 面向资源,隐藏实现 |
| 忽略分页 | 一次返回全部数据 | 默认分页,可配置 page_size |
1.10 小结
| 要点 | 规范 |
|---|---|
| 资源命名 | 名词复数,小写 + 连字符 |
| URL 层级 | 最多 2 层嵌套 |
| HTTP 方法 | GET(读)、POST(创建)、PUT(替换)、PATCH(部分更新)、DELETE(删) |
| 状态码 | 2xx 成功、4xx 客户端错、5xx 服务端错 |
| 响应格式 | 统一 {code, message, data} 结构 |
| 分页 | 数据量大用游标分页,管理后台用偏移分页 |
| 版本控制 | URL 路径 /api/v1/... |
| 安全 | 无状态认证(JWT)、HTTPS、限流 |

