Files
2026-07-13 21:36:01 +08:00

7.4 KiB
Raw Permalink Blame History

name, description, metadata
name description metadata
rest-design-patterns REST 设计模式参考手册
type
reference

REST 设计模式

面向资源的架构

REST API 围绕资源(而非动作)构建。资源是 API 中的名词。

资源标识

良好的资源 URI

GET    /users                  # 集合
GET    /users/{id}             # 单个资源
GET    /users/{id}/orders      # 嵌套集合
POST   /users                  # 创建资源
PUT    /users/{id}             # 替换资源
PATCH  /users/{id}             # 更新资源
DELETE /users/{id}             # 删除资源

不良的资源 URI

POST   /getUser                # URI 中包含动词
POST   /createUser             # URI 中包含动词
GET    /user?action=delete     # 将动作作为查询参数

资源命名约定

  • 集合使用复数名词:/users/orders/products
  • 使用小写字母和连字符以提高可读性:/shipping-addresses
  • 避免深层嵌套(最多 2-3 层):/users/{id}/orders/{orderId}
  • 使用查询参数进行过滤:/users?status=active&role=admin

HTTP 方法语义

安全方法与幂等方法

方法 安全 幂等 使用场景
GET 获取资源
POST 创建资源、非幂等操作
PUT 替换整个资源
PATCH 部分更新
DELETE 删除资源
HEAD 仅获取元数据
OPTIONS 获取允许的方法

方法使用

GET —— 获取资源

GET /users/123
Accept: application/json

响应:200 OK
{
  "id": 123,
  "name": "John Doe",
  "email": "john@example.com",
  "created_at": "2024-01-15T10:30:00Z"
}

POST —— 创建资源

POST /users
Content-Type: application/json

{
  "name": "Jane Smith",
  "email": "jane@example.com"
}

响应:201 Created
Location: /users/124
{
  "id": 124,
  "name": "Jane Smith",
  "email": "jane@example.com",
  "created_at": "2024-01-16T14:20:00Z"
}

PUT —— 替换资源

PUT /users/123
Content-Type: application/json

{
  "name": "John Doe Updated",
  "email": "john.new@example.com"
}

响应:200 OK
{
  "id": 123,
  "name": "John Doe Updated",
  "email": "john.new@example.com",
  "updated_at": "2024-01-17T09:15:00Z"
}

PATCH —— 部分更新

PATCH /users/123
Content-Type: application/json

{
  "email": "john.updated@example.com"
}

响应:200 OK
{
  "id": 123,
  "name": "John Doe",
  "email": "john.updated@example.com",
  "updated_at": "2024-01-17T10:00:00Z"
}

DELETE —— 删除资源

DELETE /users/123

响应:204 No Content

HTTP 状态码

成功状态码(2xx

  • 200 OK —— 请求成功(GET、PUT、PATCH
  • 201 Created —— 资源已创建(POST),需包含 Location 头部
  • 202 Accepted —— 请求已接受,待异步处理
  • 204 No Content —— 成功但无响应体(DELETE

重定向(3xx

  • 301 Moved Permanently —— 资源已永久迁移
  • 302 Found —— 临时重定向
  • 304 Not Modified —— 缓存版本仍然有效

客户端错误(4xx

  • 400 Bad Request —— 请求语法无效或校验错误
  • 401 Unauthorized —— 需要认证或认证失败
  • 403 Forbidden —— 已认证但无权限
  • 404 Not Found —— 资源不存在
  • 405 Method Not Allowed —— 该 HTTP 方法不被资源支持
  • 409 Conflict —— 请求与当前状态冲突(例如重复)
  • 422 Unprocessable Entity —— 语法正确但语义错误
  • 429 Too Many Requests —— 请求频率超限

服务端错误(5xx

  • 500 Internal Server Error —— 服务器意外错误
  • 502 Bad Gateway —— 上游服务器返回无效响应
  • 503 Service Unavailable —— 服务器暂时不可用
  • 504 Gateway Timeout —— 上游服务器超时

HATEOAS(超媒体)

超媒体驱动的 API

包含指向相关资源和可用操作的链接:

{
  "id": 123,
  "name": "John Doe",
  "email": "john@example.com",
  "_links": {
    "self": { "href": "/users/123" },
    "orders": { "href": "/users/123/orders" },
    "update": { "href": "/users/123", "method": "PATCH" },
    "delete": { "href": "/users/123", "method": "DELETE" }
  }
}

HAL(超文本应用语言)

{
  "id": 123,
  "name": "John Doe",
  "_links": {
    "self": { "href": "/users/123" }
  },
  "_embedded": {
    "orders": [
      {
        "id": 456,
        "total": 99.99,
        "_links": {
          "self": { "href": "/orders/456" }
        }
      }
    ]
  }
}

内容协商

Accept 头部

GET /users/123
Accept: application/json

GET /users/123
Accept: application/xml

GET /users/123
Accept: application/hal+json

响应 Content-Type

Content-Type: application/json; charset=utf-8
Content-Type: application/problem+json
Content-Type: application/hal+json

幂等性

幂等操作

PUT —— 始终幂等: 多次相同的 PUT 请求与单次请求产生相同的结果。

DELETE —— 幂等: 第一次 DELETE 返回 204,后续 DELETE 返回 404(最终状态相同)。

POST —— 默认不幂等: 使用 Idempotency-Key 头部实现幂等 POST

POST /payments
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json

{
  "amount": 100.00,
  "currency": "USD"
}

服务端存储幂等键,对重复请求返回相同响应。

缓存控制

缓存头部

Cache-Control: public, max-age=3600
Cache-Control: private, no-cache
Cache-Control: no-store
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Last-Modified: Wed, 15 Jan 2024 10:30:00 GMT

条件请求

GET /users/123
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"

响应:304 Not Modified
PUT /users/123
If-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Content-Type: application/json

{
  "name": "Updated Name"
}

响应:412 Precondition Failed(如果 ETag 不匹配)

URI 模式

一致的 URI 结构

/{version}/{resource}
/{version}/{resource}/{id}
/{version}/{resource}/{id}/{sub-resource}
/{version}/{resource}/{id}/{sub-resource}/{sub-id}

查询参数

过滤:

GET /users?status=active&role=admin
GET /products?category=electronics&price_min=100&price_max=500

排序:

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

字段选择:

GET /users?fields=id,name,email
GET /users?exclude=password,social_security_number

搜索:

GET /users?q=john
GET /products?search=laptop

最佳实践

  1. 使用名词,而非动词 —— 资源是名词,方法是动词
  2. 集合使用复数 —— 使用 /users 而非 /user
  3. 命名保持一致 —— 选择 snake_case 或 camelCase 并坚持使用
  4. 使用正确的状态码 —— 选用恰当的 HTTP 状态码
  5. 包含元数据 —— 在响应中提供分页、过滤、排序信息
  6. 对 API 进行版本控制 —— 从第一天起就规划演进
  7. 记录所有内容 —— OpenAPI 规范、示例、错误码
  8. 默认启用安全 —— HTTPS、认证、速率限制
  9. 支持过滤 —— 让客户端精确获取所需数据
  10. 实现 HATEOAS —— 使 API 具备自文档和自发现能力