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

343 lines
7.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: rest-design-patterns
description: REST 设计模式参考手册
metadata:
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 —— 获取资源**
```http
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 —— 创建资源**
```http
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 —— 替换资源**
```http
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 —— 部分更新**
```http
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 —— 删除资源**
```http
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
包含指向相关资源和可用操作的链接:
```json
{
"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(超文本应用语言)
```json
{
"id": 123,
"name": "John Doe",
"_links": {
"self": { "href": "/users/123" }
},
"_embedded": {
"orders": [
{
"id": 456,
"total": 99.99,
"_links": {
"self": { "href": "/orders/456" }
}
}
]
}
}
```
## 内容协商
### Accept 头部
```http
GET /users/123
Accept: application/json
GET /users/123
Accept: application/xml
GET /users/123
Accept: application/hal+json
```
### 响应 Content-Type
```http
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
```http
POST /payments
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json
{
"amount": 100.00,
"currency": "USD"
}
```
服务端存储幂等键,对重复请求返回相同响应。
## 缓存控制
### 缓存头部
```http
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
```
### 条件请求
```http
GET /users/123
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
响应:304 Not Modified
```
```http
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 具备自文档和自发现能力