RESTful API设计规范详解
目录
概述
RESTful API(Representational State Transfer)是一套基于 HTTP 协议,围绕"资源"进行设计的接口规范。由 Roy Fielding 在 2000 年的博士论文中首次提出,其核心在于利用标准的 HTTP 方法对资源执行操作,从而构建出结构清晰、易于维护的 Web 服务。
REST架构风格
REST架构的六个约束条件:
┌─────────────────────────────────────────────┐
│ 1. 客户端-服务器 (Client-Server) │
│ - 关注点分离 │
│ - 客户端和服务器独立演化 │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 2. 无状态 (Stateless) │
│ - 每个请求包含所有必要信息 │
│ - 服务器不保存客户端状态 │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 3. 可缓存 (Cacheable) │
│ - 响应必须明确是否可缓存 │
│ - 提升性能和可扩展性 │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 4. 统一接口 (Uniform Interface) │
│ - 资源标识(URI) │
│ - 资源操作(HTTP方法) │
│ - 自描述消息 │
│ - 超媒体(HATEOAS,可选) │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 5. 分层系统 (Layered System) │
│ - 允许通过中间层扩展 │
│ - 客户端无需知道是否连接到最终服务器 │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 6. 按需代码 (Code on Demand,可选) │
│ - 服务器可向客户端发送可执行代码 │
│ - 如JavaScript、Applet等 │
└─────────────────────────────────────────────┘
RESTful API核心特点
RESTful API的核心特点:
┌─────────────────────────────────────────────┐
│ 1. 资源为中心 │
│ - 一切皆为资源 │
│ - 每个资源通过唯一的URI标识 │
│ - 资源通过表示(Representation)传输 │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 2. 统一接口 │
│ - 使用标准HTTP方法 │
│ - GET/POST/PUT/DELETE等 │
│ - 语义明确,易于理解 │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 3. 无状态 │
│ - 服务器不保存客户端会话状态 │
│ - 每次请求包含所有必要信息 │
│ - 支持水平扩展 │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 4. 可缓存 │
│ - 充分利用HTTP缓存机制 │
│ - 提升性能和可扩展性 │
│ - 减少服务器负载 │
└─────────────────────────────────────────────┘
REST架构层次
REST架构层次:
┌─────────────────────────────────────────────┐
│ Level 0: 使用HTTP作为传输协议 │
│ - 所有操作都通过POST │
│ - 如:POST /getUser │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Level 1: 引入资源概念 │
│ - 每个资源有唯一URI │
│ - 如:POST /users/123/get │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Level 2: 使用HTTP动词 │
│ - GET/POST/PUT/DELETE │
│ - 使用HTTP状态码 │
│ - 如:GET /users/123 │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Level 3: 超媒体控制(HATEOAS) │
│ - 响应中包含相关链接 │
│ - 客户端通过链接发现API │
│ - 如:响应中包含self、update等链接 │
└─────────────────────────────────────────────┘
说明:大多数RESTful API处于Level 2,Level 3(HATEOAS)在实际项目中较少使用。
设计目标
| 目标 | 说明 | 实现方式 |
|---|---|---|
| 清晰性 | 接口语义明确,易于理解 | 使用标准HTTP方法和状态码 |
| 一致性 | 统一的命名和结构规范 | 遵循RESTful约定 |
| 可维护性 | 易于扩展和修改 | 版本控制、向后兼容 |
| 可测试性 | 便于自动化测试 | 无状态、幂等性 |
| 安全性 | 内置安全考虑 | HTTPS、认证、授权 |
| 可扩展性 | 支持水平扩展 | 无状态、可缓存 |
核心设计原则
1. 资源为中心
原则:一切皆为资源,每个资源通过唯一的 URI 标识。
示例:
资源示例:
- 用户:/users
- 订单:/orders
- 商品:/products
2. 统一接口
原则:使用标准的 HTTP 方法(GET, POST, PUT, DELETE 等)对资源进行操作。
HTTP方法映射:
| HTTP方法 | 操作 | 幂等性 | 示例 |
|---|---|---|---|
| GET | 查询/获取资源 | ✅ 是 | GET /users |
| POST | 创建资源或执行非CRUD操作 | ❌ 否 | POST /users |
| PUT | 全量更新资源 | ✅ 是 | PUT /users/1 |
| PATCH | 部分更新资源 | ✅ 是 | PATCH /users/1 |
| DELETE | 删除资源 | ✅ 是 | DELETE /users/1 |
3. 无状态
原则:服务器不保存客户端会话状态,每次请求都应包含所有必要信息。
实现方式:
- 使用 Token(如 JWT)传递身份信息
- 分页参数在请求中传递
- 不依赖服务器端 Session
4. 可缓存
原则:充分利用 HTTP 缓存机制提升性能。
缓存策略:
- 使用
Cache-Control头控制缓存 - GET 请求支持缓存
- POST/PUT/DELETE 通常不缓存
缓存控制头:
| 响应头 | 说明 | 示例 |
|---|---|---|
Cache-Control: public |
响应可被任何缓存存储 | 公开资源 |
Cache-Control: private |
响应只能被私有缓存存储 | 用户个人信息 |
Cache-Control: no-cache |
必须先验证才能使用缓存 | 动态内容 |
Cache-Control: max-age=3600 |
缓存有效期(秒) | 1小时内有效 |
ETag: "abc123" |
资源版本标识 | 用于条件请求 |
Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT |
最后修改时间 | 用于条件请求 |
缓存验证流程:
客户端请求
↓
┌─────────────────────────────────────────────┐
│ 检查本地缓存 │
│ - 有缓存且未过期 → 直接返回 │
│ - 有缓存但过期 → 验证缓存 │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 条件请求(If-None-Match / If-Modified-Since)│
│ - 服务器返回304 Not Modified → 使用缓存 │
│ - 服务器返回200 OK → 更新缓存 │
└─────────────────────────────────────────────┘
5. 分层系统
原则:允许通过网关、代理等中间层进行扩展,客户端无需关心内部架构。
分层示例:
客户端
↓
API网关(路由、认证、限流)
↓
业务服务
↓
数据层
关键实践规范
协议与域名
协议
规范 :始终使用 HTTPS 保障数据传输安全。
原因:
- 防止数据被窃听
- 防止数据被篡改
- 提升用户信任度
域名
推荐方式:
| 方式 | 示例 | 说明 |
|---|---|---|
| 专用子域名(推荐) | https://api.example.com |
清晰分离,便于管理 |
| 主域名路径 | https://example.com/api/ |
简单直接,适合小型项目 |
选择建议:
- 大型项目:使用专用子域名
- 小型项目:使用主域名路径
- 关键:保持一致性
API版本控制
版本控制方式
方式一:URL路径(最常用)
GET /v1/users
GET /v2/users
优势:
- 直观明了
- 便于调试
- 日志记录清晰
方式二:请求头
Accept: application/vnd.myapp.v1+json
方式三:查询参数
GET /users?version=v1
推荐方案
建议:优先使用 URL 路径,因其最直观,便于调试和日志记录。
版本命名规范:
- 使用
v1,v2,v3等数字版本 - 避免使用
v1.0,v1.1等小数版本 - 主版本号变更表示不兼容变更
资源路径URI设计
1. 使用名词,不用动词
原则:路径应代表资源,操作由 HTTP 方法体现。
对比:
| 推荐 | 不推荐 | 说明 |
|---|---|---|
GET /users |
GET /getUser |
动词在方法中,不在路径中 |
POST /users |
POST /createUser |
创建操作由POST方法表示 |
DELETE /users/1 |
DELETE /deleteUser/1 |
删除操作由DELETE方法表示 |
2. 使用复数形式
原则:表示资源集合,保持一致性。
对比:
| 推荐 | 不推荐 | 说明 |
|---|---|---|
/users |
/user |
资源集合使用复数 |
/users/123 |
/user/123 |
单个资源在复数路径下 |
3. 层级不宜过深
原则:建议不超过 2 层,更深的关系可通过查询参数表达。
对比:
| 不推荐 | 推荐 | 说明 |
|---|---|---|
/users/1/orders/2/items/3 |
/items?orderId=2&userId=1 |
避免过深嵌套 |
/companies/1/departments/2/employees/3 |
/employees?departmentId=2&companyId=1 |
使用查询参数 |
4. 其他约定
命名规范:
- 使用小写字母和短横线
-,避免下划线_和驼峰命名 - 路径末尾不加
/ - 使用连字符分隔多个单词:
/user-profiles
示例:
✅ 正确:
GET /user-profiles
GET /order-items
❌ 错误:
GET /user_profiles
GET /userProfiles
GET /users/
HTTP方法语义
方法对照表
| HTTP方法 | 核心语义 | 是否幂等 | 是否安全 | 是否有请求体 | 典型场景 |
|---|---|---|---|---|---|
| GET | 查询/获取资源 | ✅ 是 | ✅ 是 | ❌ 否 | 获取用户列表 GET /users |
| POST | 创建资源或执行非CRUD操作 | ❌ 否 | ❌ 否 | ✅ 是 | 创建用户 POST /users |
| PUT | 全量更新资源 | ✅ 是 | ❌ 否 | ✅ 是 | 完整替换用户信息 PUT /users/1 |
| PATCH | 部分更新资源 | ✅ 是* | ❌ 否 | ✅ 是 | 仅更新用户邮箱 PATCH /users/1 |
| DELETE | 删除资源 | ✅ 是 | ❌ 否 | ❌ 否 | 删除用户 DELETE /users/1 |
| HEAD | 获取资源的元信息(如headers) | ✅ 是 | ✅ 是 | ❌ 否 | 检查资源是否存在 |
| OPTIONS | 获取资源支持的HTTP方法 | ✅ 是 | ✅ 是 | ❌ 否 | CORS预检请求 |
说明:
- 幂等性:多次执行同一请求,对系统状态的影响与执行一次相同
- 安全性:请求不会修改服务器状态(只读操作)
- PATCH幂等性:需服务端保证,通过版本号或乐观锁实现
HTTP方法选择决策树
需要修改资源?
├─ 否 → GET(查询)
└─ 是
├─ 创建新资源? → POST
├─ 删除资源? → DELETE
└─ 更新资源?
├─ 提供完整资源数据? → PUT
└─ 只提供部分字段? → PATCH
方法使用详解
GET - 查询资源
GET /users # 获取用户列表
GET /users/123 # 获取单个用户
GET /users/123/orders # 获取用户的订单列表
特点:
- 幂等性:是
- 可缓存:是
- 不应有请求体
POST - 创建资源
POST /users
Content-Type: application/json
{
"name": "张三",
"email": "zhangsan@example.com"
}
特点:
- 幂等性:否(多次调用可能创建多个资源)
- 可缓存:否
- 返回:201 Created,Location头包含新资源URI
PUT - 全量更新
PUT /users/123
Content-Type: application/json
{
"name": "张三-updated",
"email": "zhangsan.updated@example.com"
}
特点:
- 幂等性:是(多次调用结果相同)
- 必须提供完整资源数据
- 如果资源不存在,可以创建(取决于实现)
PATCH - 部分更新
PATCH /users/123
Content-Type: application/json
{
"email": "new.email@example.com"
}
特点:
- 幂等性:是(需服务端保证)
- 只需提供要更新的字段
- 更灵活,节省带宽
DELETE - 删除资源
DELETE /users/123
特点:
- 幂等性:是
- 返回:204 No Content(成功但无返回内容)
- 或返回:200 OK(带删除确认信息)
查询参数
常用查询参数
分页:
?page=2&per_page=20
或
?offset=20&limit=20
排序:
?sort=created_at&order=desc
或
?sort=created_at,desc
或
?sort=-created_at,name # -表示降序
过滤:
?status=active&role=admin
?age_min=18&age_max=65
?created_after=2024-01-01
搜索:
?q=keyword
?search=keyword
字段选择:
?fields=id,name,email
(按需返回字段,减少传输量)
范围查询:
?price_min=100&price_max=1000
?date_from=2024-01-01&date_to=2024-12-31
包含关系:
?include=orders,profile
(返回关联资源)
查询参数设计原则
原则:
- 使用小写字母和下划线
- 保持命名一致性
- 提供默认值和限制
- 文档化所有参数
参数命名规范:
| 参数类型 | 命名规范 | 示例 |
|---|---|---|
| 分页 | page, per_page 或 offset, limit |
?page=1&per_page=20 |
| 排序 | sort, order |
?sort=created_at&order=desc |
| 过滤 | 字段名 + 操作符 | ?status=active, ?age_min=18 |
| 搜索 | q 或 search |
?q=keyword |
| 字段 | fields |
?fields=id,name |
| 包含 | include |
?include=orders |
示例:
✅ 正确:
GET /users?first_name=zhang&page=1&per_page=20&sort=created_at,desc
❌ 错误:
GET /users?firstName=zhang&Page=1&perPage=20&Sort=createdAt
查询参数组合示例
复杂查询:
GET /users?status=active&role=admin&age_min=18&age_max=65&sort=-created_at,name&page=1&per_page=20&fields=id,name,email&include=profile
解析:
- 状态过滤:
status=active - 角色过滤:
role=admin - 年龄范围:
age_min=18&age_max=65 - 排序:按创建时间降序,然后按姓名升序
- 分页:第1页,每页20条
- 字段选择:只返回id、name、email
- 包含关系:同时返回profile信息
HTTP状态码
状态码分类
2xx 成功:
| 状态码 | 说明 | 使用场景 | 响应体 |
|---|---|---|---|
| 200 OK | 通用成功 | 用于 GET/PUT/PATCH | 包含资源数据 |
| 201 Created | 资源创建成功 | POST创建资源 | 包含新创建的资源,响应头Location包含新资源URI |
| 204 No Content | 成功但无返回内容 | 常用于DELETE | 无响应体 |
| 202 Accepted | 请求已接受 | 将在后台异步处理 | 包含任务ID或状态查询地址 |
3xx 重定向:
| 状态码 | 说明 | 使用场景 |
|---|---|---|
| 301 Moved Permanently | 永久重定向 | 资源已永久移动到新URI |
| 302 Found | 临时重定向 | 资源临时移动到新URI |
| 304 Not Modified | 未修改 | 缓存有效,无需重新传输 |
4xx 客户端错误:
| 状态码 | 说明 | 使用场景 | 常见原因 |
|---|---|---|---|
| 400 Bad Request | 请求参数错误 | 参数格式不正确 | JSON格式错误、缺少必需参数 |
| 401 Unauthorized | 未认证 | 缺少或无效的身份凭证 | Token缺失、Token过期 |
| 403 Forbidden | 已认证但无权限 | 无权限访问该资源 | 权限不足、资源被保护 |
| 404 Not Found | 资源不存在 | 请求的资源不存在 | URI错误、资源已删除 |
| 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 | 服务不可用 | 服务暂时不可用(如维护中) | 返回Retry-After头 |
| 504 Gateway Timeout | 网关超时 | 上游服务器响应超时 | 检查超时配置 |
状态码选择决策树
请求处理结果
├─ 成功
│ ├─ 有响应体 → 200 OK
│ ├─ 创建资源 → 201 Created
│ ├─ 无响应体 → 204 No Content
│ └─ 异步处理 → 202 Accepted
│
├─ 客户端错误
│ ├─ 未认证 → 401 Unauthorized
│ ├─ 无权限 → 403 Forbidden
│ ├─ 资源不存在 → 404 Not Found
│ ├─ 参数错误 → 400 Bad Request
│ ├─ 验证失败 → 422 Unprocessable Entity
│ └─ 请求过频 → 429 Too Many Requests
│
└─ 服务器错误
├─ 未知错误 → 500 Internal Server Error
├─ 服务不可用 → 503 Service Unavailable
└─ 网关错误 → 502/504
状态码使用原则
原则:
- 应返回语义准确的 HTTP 状态码
- 不要一律使用 200
- 错误响应体可补充详细信息,但不能替代状态码
错误响应示例:
json
{
"error": {
"code": "INVALID_TOKEN",
"message": "Token已过期",
"detail": "Token在2024-01-01过期"
}
}
请求与响应格式
数据格式
默认格式 :使用 JSON ,设置 Content-Type: application/json
请求头:
Content-Type: application/json
Accept: application/json
成功响应
推荐结构:
json
{
"data": {
"id": 1,
"name": "张三",
"email": "zhangsan@example.com"
}
}
列表响应:
json
{
"data": [
{"id": 1, "name": "张三"},
{"id": 2, "name": "李四"}
],
"pagination": {
"page": 1,
"per_page": 20,
"total": 100
}
}
错误响应
推荐结构:
json
{
"error": {
"code": "INVALID_EMAIL",
"message": "邮箱格式不正确",
"detail": {
"field": "email",
"value": "invalid-email"
}
}
}
注意:
- HTTP 状态码本身不应放在 JSON 里
- 状态码是协议层信息
- JSON 中的错误码是业务层信息
身份认证与安全
传输安全
强制使用 HTTPS:
- 防止数据被窃听(加密传输)
- 防止数据被篡改(完整性校验)
- 防止中间人攻击(证书验证)
- 提升用户信任度
HTTPS配置建议:
- 使用TLS 1.2及以上版本
- 配置HSTS(HTTP Strict Transport Security)
- 定期更新SSL证书
- 使用强加密套件
认证方式对比
| 认证方式 | 工作原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| Basic Auth | 用户名密码Base64编码 | 简单易用 | 安全性低,需HTTPS | 内部系统 |
| Bearer Token (JWT) | JSON Web Token | 无状态、可扩展 | Token泄露风险 | 现代Web应用 |
| OAuth 2.0 | 授权码流程 | 安全、标准化 | 实现复杂 | 第三方应用授权 |
| API Key | 固定密钥 | 简单直接 | 安全性较低 | 服务间调用 |
| HMAC | 签名验证 | 高安全性 | 实现复杂 | 高安全要求场景 |
JWT认证流程
JWT认证流程:
┌─────────────────────────────────────────────┐
│ 1. 客户端登录 │
│ POST /auth/login │
│ { username, password } │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 2. 服务器验证并生成JWT │
│ 返回:{ token: "eyJhbGc..." } │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 3. 客户端存储Token │
│ - localStorage / sessionStorage │
│ - 或内存中(更安全) │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 4. 后续请求携带Token │
│ Authorization: Bearer eyJhbGc... │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 5. 服务器验证Token │
│ - 验证签名 │
│ - 检查过期时间 │
│ - 提取用户信息 │
└─────────────────────────────────────────────┘
JWT结构:
JWT = Header.Payload.Signature
Header: {
"alg": "HS256",
"typ": "JWT"
}
Payload: {
"sub": "1234567890",
"name": "John Doe",
"exp": 1516239022
}
Signature: HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
OAuth 2.0授权流程
OAuth 2.0授权码流程:
┌─────────────────────────────────────────────┐
│ 1. 用户授权 │
│ 客户端 → 授权服务器 │
│ GET /oauth/authorize?client_id=... │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 2. 用户登录并授权 │
│ 授权服务器返回授权码 │
│ redirect_uri?code=AUTHORIZATION_CODE │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 3. 客户端用授权码换取Token │
│ POST /oauth/token │
│ { code, client_id, client_secret } │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 4. 授权服务器返回Access Token │
│ { access_token, refresh_token, ... } │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 5. 客户端使用Token访问资源 │
│ Authorization: Bearer ACCESS_TOKEN │
└─────────────────────────────────────────────┘
安全实践清单
传输层安全:
- 强制使用HTTPS
- 配置HSTS
- 使用强加密套件
- 定期更新SSL证书
认证安全:
- Token过期时间设置合理
- 实现Token刷新机制
- 敏感操作需要二次验证
- 记录登录日志
授权安全:
- 实现基于角色的访问控制(RBAC)
- 实现资源级权限控制
- 最小权限原则
- 定期审查权限
输入安全:
- 所有输入进行验证
- 防止SQL注入(参数化查询)
- 防止XSS攻击(输出编码)
- 防止CSRF攻击(Token验证)
限流与监控:
- 实施API限流
- 监控异常请求
- 记录访问日志
- 设置告警机制
限流策略
限流算法:
| 算法 | 说明 | 适用场景 |
|---|---|---|
| 固定窗口 | 固定时间窗口内限制请求数 | 简单场景 |
| 滑动窗口 | 滑动时间窗口内限制请求数 | 更精确控制 |
| 令牌桶 | 按固定速率生成令牌 | 允许突发流量 |
| 漏桶 | 按固定速率处理请求 | 平滑流量 |
限流响应头:
X-RateLimit-Limit: 1000 # 限制总数
X-RateLimit-Remaining: 999 # 剩余次数
X-RateLimit-Reset: 1640995200 # 重置时间(Unix时间戳)
Retry-After: 60 # 建议重试时间(秒)
限流响应示例:
http
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1640995200
Retry-After: 60
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "请求过于频繁,请稍后再试",
"retryAfter": 60
}
}
高级概念HATEOAS
什么是HATEOAS
HATEOAS (Hypermedia as the Engine of Application State) 旨在让 API 响应"自描述",即通过返回的链接引导客户端进行下一步操作,减少客户端对 URI 结构的硬编码依赖。
示例
json
{
"id": 123,
"name": "张三",
"links": [
{
"rel": "self",
"href": "/users/123",
"method": "GET"
},
{
"rel": "update",
"href": "/users/123",
"method": "PUT"
},
{
"rel": "orders",
"href": "/users/123/orders",
"method": "GET"
}
]
}
使用建议
适用场景:
- 公开API
- 需要客户端动态发现资源
不适用场景:
- 内部微服务间调用
- 追求简洁高效的场景
注意:HATEOAS 在实践中复杂度较高,可根据项目需求选择性实现。
实战示例:用户管理API
API设计对比
错误设计 vs 正确设计
错误设计(RPC风格):
POST /getUserById
POST /createUser
POST /updateUser
POST /deleteUser
POST /getUserList
正确设计(RESTful风格):
GET /users # 获取用户列表
POST /users # 创建用户
GET /users/123 # 获取单个用户
PUT /users/123 # 全量更新用户
PATCH /users/123 # 部分更新用户
DELETE /users/123 # 删除用户
对比优势:
- 语义清晰:HTTP方法表达操作意图
- 符合标准:遵循HTTP协议规范
- 易于缓存:GET请求可被缓存
- 工具支持:浏览器、代理服务器原生支持
完整API示例
1. 获取用户列表(分页)
http
GET /v1/users?page=1&per_page=20
响应:
json
{
"data": [
{"id": 1, "name": "张三", "email": "zhangsan@example.com"},
{"id": 2, "name": "李四", "email": "lisi@example.com"}
],
"pagination": {
"page": 1,
"per_page": 20,
"total": 100
}
}
2. 创建用户
http
POST /v1/users
Content-Type: application/json
{
"name": "李四",
"email": "lisi@example.com"
}
响应(201 Created):
json
{
"data": {
"id": 123,
"name": "李四",
"email": "lisi@example.com"
}
}
响应头:
Location: /v1/users/123
3. 获取单个用户
http
GET /v1/users/123
响应:
json
{
"data": {
"id": 123,
"name": "李四",
"email": "lisi@example.com"
}
}
4. 全量更新用户
http
PUT /v1/users/123
Content-Type: application/json
{
"name": "李四-updated",
"email": "lisi.updated@example.com"
}
响应:
json
{
"data": {
"id": 123,
"name": "李四-updated",
"email": "lisi.updated@example.com"
}
}
5. 部分更新用户
http
PATCH /v1/users/123
Content-Type: application/json
{
"email": "new.email@example.com"
}
响应:
json
{
"data": {
"id": 123,
"name": "李四-updated",
"email": "new.email@example.com"
}
}
6. 删除用户
http
DELETE /v1/users/123
响应(204 No Content):无响应体
完整API设计示例
用户资源完整CRUD
资源:用户(User)
基础路径:/v1/users
┌─────────────────────────────────────────────────────────┐
│ 操作 │ HTTP方法 │ URI │ 状态码 │
├─────────────────────────────────────────────────────────┤
│ 获取列表 │ GET │ /v1/users │ 200 │
│ 创建用户 │ POST │ /v1/users │ 201 │
│ 获取单个 │ GET │ /v1/users/123 │ 200 │
│ 全量更新 │ PUT │ /v1/users/123 │ 200 │
│ 部分更新 │ PATCH │ /v1/users/123 │ 200 │
│ 删除用户 │ DELETE │ /v1/users/123 │ 204 │
└─────────────────────────────────────────────────────────┘
订单资源(关联资源)
资源:订单(Order)
基础路径:/v1/orders
┌─────────────────────────────────────────────────────────┐
│ 操作 │ HTTP方法 │ URI │ 说明 │
├─────────────────────────────────────────────────────────┤
│ 获取订单列表 │ GET │ /v1/orders │ 所有订单│
│ 获取用户订单 │ GET │ /v1/orders?userId=123 │ 过滤 │
│ 创建订单 │ POST │ /v1/orders │ 新建 │
│ 获取订单详情 │ GET │ /v1/orders/456 │ 单个 │
│ 更新订单 │ PATCH │ /v1/orders/456 │ 部分更新│
│ 取消订单 │ POST │ /v1/orders/456/cancel │ 动作 │
│ 删除订单 │ DELETE │ /v1/orders/456 │ 删除 │
└─────────────────────────────────────────────────────────┘
注意 :/v1/orders/456/cancel 是动作型操作,使用POST是合理的。
常见反模式与规避方法
反模式总览
常见反模式分类:
┌─────────────────────────────────────────────┐
│ 1. 设计层面 │
│ - 动词化URI │
│ - 滥用HTTP方法 │
│ - 破坏向后兼容性 │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 2. 实现层面 │
│ - 一律返回200 │
│ - N+1查询问题 │
│ - 暴露内部模型 │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 3. 安全层面 │
│ - 可预测ID │
│ - 有状态API │
│ - 敏感信息泄露 │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 4. 架构层面 │
│ - 无意义代理 │
│ - 文档缺失 │
│ - 过度设计 │
└─────────────────────────────────────────────┘
1. 动词化URI与滥用HTTP方法
反模式
问题:
- 在路径中使用
add,update,delete等动词 - 所有操作都使用 POST
- GET 请求携带请求体执行复杂查询
错误示例:
POST /users/add
GET /users/search (带请求体)
规避方法
正确做法:
- URI 使用名词:
/users,/users/123/orders - 正确使用 HTTP 方法:
- GET:查询
- POST:创建资源或执行非CRUD操作
- PUT:全量更新
- PATCH:部分更新
- DELETE:删除
- 复杂查询:可使用
POST /users/search,但需文档明确
2. 一律返回200 OK
反模式
问题:无论成功或失败,HTTP 状态码始终为 200,错误信息混杂在响应体中。
错误示例:
json
{
"code": 0,
"msg": "error"
}
规避方法
正确做法:
- 遵循标准语义:使用准确的 HTTP 状态码
- 错误响应体:可补充
error_code,message,details等字段,但不能替代状态码
正确示例:
http
HTTP/1.1 404 Not Found
Content-Type: application/json
{
"error": {
"code": "USER_NOT_FOUND",
"message": "用户不存在"
}
}
3. 破坏向后兼容性
反模式
问题:
- 随意修改或删除已有字段
- 改变数据类型
- 将可选字段设为必填
规避方法
正确做法:
- 只增不减:新增字段时使用新名称,不修改或删除旧字段
- 保持可选:避免将可选字段变为必填
- 版本控制:当必须进行不兼容变更时,通过版本号管理
- 充分沟通:废弃旧版本前,需提前通知并给出迁移期
4. N+1查询与过度循环调用
反模式
问题:列表接口只提供获取单个资源的接口,导致客户端为获取 N 条数据而循环调用 N 次。
规避方法
正确做法:
- 提供批量接口:
GET /users?ids=1,2,3或POST /users/batch - 优化服务端:使用 JOIN 或 IN 查询,一次性查出所需数据
- 支持字段筛选:通过
fields参数按需返回字段
5. 暴露内部领域模型
反模式
问题:直接将数据库实体(Entity)或 ORM 对象序列化为 JSON 返回,可能暴露敏感信息。
规避方法
正确做法:
- 使用 DTO (数据传输对象):为 API 定义专门的请求和响应 DTO
- 过滤敏感字段:在 Entity 中使用
@JsonIgnore或在 DTO 中不包含敏感字段
6. 过度设计与滥用泛型
反模式
问题 :为不确定的未来需求,将简单字段设计为复杂结构,或使用 Map<String, Object> 等泛型字段。
规避方法
正确做法:
- 保持简单:只为当前业务需求设计字段
- 渐进式扩展:未来需要新字段时,通过添加新字段并保持向后兼容
7. 使用可预测的顺序ID
反模式
问题:使用数据库自增 ID 作为资源标识,攻击者可轻易遍历所有用户数据。
规避方法
正确做法:
- 使用不可预测的 ID:优先使用 UUID 或 Snowflake 等全局唯一 ID
- 权限校验:即使使用随机 ID,也必须对资源进行权限校验
8. 有状态API
反模式
问题 :API 依赖服务器端的会话状态(如通过 sessionId 进行分页),导致服务器无法水平扩展。
规避方法
正确做法:
- 保持无状态:每个请求都应包含所有必要信息(如认证 Token、分页参数)
- 使用 Token:通过 JWT 等机制在请求间传递用户身份和权限信息
9. 无意义的API代理
反模式
问题:在微服务架构中,一个服务仅作为"转发代理",将请求原封不动地传递给下游服务。
规避方法
正确做法:
- 移除无意义代理:如果网关或服务对请求没有业务逻辑处理,应让客户端直接调用
- 明确网关职责:仅在网关层实现路由、认证、限流、熔断等横切关注点
10. 文档缺失或混乱
反模式
问题:没有文档,或文档与实际接口严重不符。
规避方法
正确做法:
- 使用专业工具:采用 OpenAPI (Swagger) 等工具自动生成和维护文档
- 文档即契约:确保文档与代码同步更新
- 提供示例:为接口提供清晰的请求/响应示例和错误码说明
进阶实践细节
1. 统一请求/响应格式
Content-Type / Accept
请求:
Content-Type: application/json
响应:
Accept: application/json
统一响应体结构
成功响应:
json
{
"data": { ... },
"pagination": { ... } // 可选
}
错误响应:
json
{
"error": {
"code": "invalid_email",
"message": "邮箱格式不正确",
"details": { ... }
}
}
注意:HTTP 状态码本身不应放在 JSON 里,它是协议层信息。
2. 过滤、排序、分页与字段选择
过滤 (Filter)
GET /users?status=active&role=admin
排序 (Sort)
GET /users?sort=created_at,desc
(多字段用逗号分隔,约定 - 为降序)
分页 (Pagination)
偏移量方式:
?page=2&per_page=20
游标方式:
?cursor=xxx&limit=20
(大数据量下性能更优)
字段选择 (Fields)
GET /users?fields=id,name,email
(按需返回,减少传输量)
3. 认证、权限与安全
传输安全
强制使用 HTTPS,禁止 HTTP 明文传输。
认证 (Authentication)
常用方式:
- Bearer Token (JWT)
- OAuth 2.0
- API Key
权限 (Authorization)
控制方式:
- 基于用户角色 (RBAC)
- 对象级权限(如"只能修改自己的订单")
输入校验
校验内容:
- 参数格式
- 数据类型
- 业务规则
- 防止 SQL 注入、XSS 等攻击
限流 (Rate Limiting)
响应头:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1640995200
超限响应:429 Too Many Requests
CORS 配置
注意 :避免将 Access-Control-Allow-Origin 设为 *,尤其是在涉及凭据(cookies)时。
4. 幂等性设计
幂等性定义
定义:多次执行同一请求,对系统状态的影响与执行一次相同。
幂等方法
幂等:
- GET
- PUT
- DELETE
- HEAD
- OPTIONS
非幂等:
- POST(通常)
实践建议
创建操作:
POST /orders返回新资源 ID- 若客户端重试,服务端需能识别并去重(如通过唯一业务单号)
更新操作:
- 优先使用 PUT 进行全量更新以保证幂等
- PATCH 用于部分更新,需由服务端保证其幂等性(如使用版本号或乐观锁)
5. 批量与异步操作
批量接口
示例:
POST /users/batch
Content-Type: application/json
{
"users": [
{"name": "张三", "email": "zhangsan@example.com"},
{"name": "李四", "email": "lisi@example.com"}
]
}
异步处理
流程:
- 客户端调用
POST /tasks/import立即获得202 Accepted响应 - 响应头或 body 中包含任务状态查询地址(如
Location: /tasks/123) - 客户端轮询或通过 Webhook 监听任务状态,完成后获取结果
6. 非CRUD操作的处理
并非所有业务都严格对应CRUD,对于"计算"、"翻译"等动作型需求:
方式一:使用动词
GET /translate?text=...&from=en&to=zh
方式二:使用RPC风格
POST /actions/translate
或
POST /jobs/translate
关键:在整个项目中保持风格一致。
7. 把API当成"产品UI"
对调用方而言,API就是他们要面对的"界面"。设计时应像对待前端UI一样,考虑其易用性:
命名直观:
- 用
status而非flag - 用
created_at而非ctime
错误提示友好:
json
{
"error": {
"code": "VALIDATION_ERROR",
"message": "验证失败",
"details": {
"email": ["不能为空", "格式不正确"]
}
}
}
提供清晰示例:
- 文档中应包含可直接复制的
curl命令 - 提供请求/响应示例
8. 谨慎设计资源粒度与嵌套
避免过细
不推荐:
/users/{id}/name
/users/{id}/email
推荐:
/users/{id}
避免过深
不推荐:
/users/1/orders/2/items/3
推荐:
/items?orderId=2&userId=1
原则:嵌套层级建议控制在 2-3 层以内。
9. 明确GET请求的"只读"原则
GET 请求绝不能用于修改状态。
错误示例:
GET /users/123/activate
GET /orders/1/cancel?reason=xxx
正确做法:
- 使用 POST 或 PUT
- GET 请求易被缓存、预加载或记录在浏览器历史中
- 若用于修改状态,可能导致严重的安全问题
10. 统一分页与元数据返回方式
推荐方式一:HTTP头 + 正文数据
Headers:
X-Total-Count: 1000
Body:
json
{
"data": [...],
"offset": 0,
"limit": 20
}
推荐方式二:完全内联在响应体中
json
{
"data": [...],
"pagination": {
"total": 1000,
"page": 1,
"size": 20
}
}
关键:全项目统一,避免混用。
11. 善用"信封(Envelope)"包装响应
虽然HTTP协议本身支持通过状态码和Header传递信息,但在实际开发中,为响应体添加一层"信封"能带来诸多便利。
示例:
json
{
"code": 0,
"msg": "ok",
"data": { ... },
"pagination": { ... }
}
何时使用:
- 客户端无法读取HTTP状态码或Header
- 需要兼容JSONP调用
- 团队内部约定统一使用
code和msg进行业务处理
注意:若非必要,无需为每个接口都添加,保持简洁。
12. 设计一致且有意义的错误码
除了HTTP状态码,在响应体中提供结构化的业务错误码,能极大提升问题排查效率。
示例:
json
{
"error": {
"code": "USER_NOT_FOUND",
"message": "用户不存在",
"detail": "id=999 的用户在系统中未找到",
"requestId": "req-20260114-001"
}
}
建议:
code:使用机器可读的常量或枚举(如INVALID_TOKEN)message:面向开发者,用于理解错误detail:提供详细上下文,如出错的字段或值requestId:关联服务端日志,便于追踪
13. 统一命名规范
URL路径:
- 使用小写字母和连字符
- - 如:
/user-profiles
JSON字段:
- 团队内部统一风格
- 推荐
camelCase(如userName)
查询参数:
- 通常使用小写和下划线
_ - 如:
first_name=zhang
关键:一致性比选择哪种风格更重要。
14. 规划API的"废弃(Deprecation)"策略
当某个接口需要变更或下线时,应有一套标准的废弃流程。
流程:
-
标记废弃 :在文档和响应头中明确告知
Deprecation: true Sunset: Wed, 31 Dec 2025 23:59:59 GMT -
通知与过渡:提前通知调用方,并提供迁移指南和充足的时间窗口
-
最终下线:到期后在网关或路由层彻底关闭旧版本接口
这能体现API作为"产品"的长期责任感。
15. 将安全视为设计的一部分
安全不应是事后补救,而需融入设计之初。
安全措施:
- 强制HTTPS:全站强制使用,并配置好HSTS
- 最小权限原则:默认拒绝所有访问,按需授权
- 防范常见攻击:对所有输入进行校验,防范SQL/NoSQL注入、XSS等
- 实施访问控制:对敏感接口(如批量删除)增加二次确认或权限校验
- 避免敏感信息泄露:不在响应中返回密码、密钥、内部ID等
许多API安全问题源于"只实现了功能,却忘了安全"。
16. 拥抱自动化:文档与测试
将文档和测试视为开发流程的必选项,而非额外负担。
自动化文档:
- 使用 OpenAPI (Swagger) 等工具
- 从代码注释或契约测试中自动生成文档
- 确保文档与实现同步
OpenAPI示例:
yaml
openapi: 3.0.0
info:
title: User API
version: 1.0.0
paths:
/users:
get:
summary: 获取用户列表
parameters:
- name: page
in: query
schema:
type: integer
default: 1
responses:
'200':
description: 成功
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/User'
自动化测试:
- 为接口编写单元测试和契约测试
- 确保变更不会破坏现有功能
- 使用Postman/Newman进行API测试
测试金字塔:
┌─────────┐
│ E2E测试 │ ← 少量,覆盖关键流程
└─────────┘
┌─────────────┐
│ 集成测试 │ ← 中等,覆盖服务间交互
└─────────────┘
┌─────────────────┐
│ 单元测试 │ ← 大量,覆盖业务逻辑
└─────────────────┘
这能极大降低维护成本,尤其是在大型项目或微服务架构中。
技术选型权衡
REST vs GraphQL vs gRPC vs WebSocket
技术对比表
| 特性 | REST | GraphQL | gRPC | WebSocket |
|---|---|---|---|---|
| 协议 | HTTP/HTTPS | HTTP/HTTPS | HTTP/2 | WebSocket |
| 数据格式 | JSON/XML | JSON | Protocol Buffers | 文本/二进制 |
| 请求方式 | 多个端点 | 单一端点 | 多个端点 | 持久连接 |
| 数据获取 | 固定结构 | 按需查询 | 固定结构 | 双向通信 |
| 类型系统 | 无 | 强类型Schema | 强类型 | 无 |
| 性能 | 中等 | 中等 | 高 | 高(实时) |
| 缓存 | 支持HTTP缓存 | 需自定义 | 需自定义 | 不支持 |
| 学习曲线 | 低 | 中 | 中 | 中 |
| 适用场景 | 通用Web API | 前端需求多变 | 微服务内部 | 实时通信 |
详细对比
RESTful API:
优势:
- 简单直观,易于理解
- 充分利用HTTP特性(缓存、状态码等)
- 工具和生态成熟
- 浏览器原生支持
劣势:
- 可能产生N+1查询问题
- 前端需要多次请求获取数据
- 版本管理需要额外工作
适用场景:
- 通用Web API
- 公开API
- 移动应用后端
- 微服务间通信(简单场景)
GraphQL:
优势:
- 前端按需查询,减少请求次数
- 强类型Schema,自动生成文档
- 单一端点,简化客户端
劣势:
- 查询复杂度控制困难
- 缓存实现复杂
- 学习曲线较陡
适用场景:
- 前端需求频繁变化
- 需要聚合多个数据源
- 移动应用(减少流量)
gRPC:
优势:
- 高性能(二进制协议)
- 强类型,编译时检查
- 支持流式传输
- 自动生成客户端代码
劣势:
- 浏览器支持有限
- 调试相对困难
- 需要定义.proto文件
适用场景:
- 微服务内部通信
- 高性能要求场景
- 需要流式传输
WebSocket:
优势:
- 双向实时通信
- 低延迟
- 支持服务器推送
劣势:
- 连接管理复杂
- 不支持HTTP缓存
- 需要处理连接断开
适用场景:
- 实时聊天
- 实时通知
- 实时数据推送
- 在线游戏
选择决策树
需要实时双向通信?
├─ 是 → WebSocket
└─ 否
├─ 微服务内部通信?
│ ├─ 是 → gRPC(高性能)或 REST(简单)
│ └─ 否
│ ├─ 前端需求频繁变化?
│ │ ├─ 是 → GraphQL
│ │ └─ 否 → REST
│ └─ 公开API?
│ ├─ 是 → REST
│ └─ 否 → 根据团队技术栈选择
混合使用策略
实际项目中,可以混合使用多种技术:
架构示例:
┌─────────────────────────────────────────────┐
│ 客户端 │
│ ├─ Web前端 → REST API(公开接口) │
│ └─ 移动App → GraphQL(按需查询) │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ API网关 │
│ ├─ 路由 │
│ ├─ 认证 │
│ └─ 限流 │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 业务服务 │
│ ├─ 用户服务 ← gRPC(内部通信) │
│ ├─ 订单服务 ← gRPC(内部通信) │
│ └─ 通知服务 ← WebSocket(实时推送) │
└─────────────────────────────────────────────┘
选择原则
关键在于,无论选择何种技术,都应遵循其核心思想:
- 接口清晰:语义明确,易于理解
- 版本可控:支持版本管理
- 安全可靠:内置安全考虑
- 性能优化:考虑实际性能需求
- 团队能力:考虑团队技术栈和学习成本
性能优化实践
缓存策略
客户端缓存
ETag验证:
请求:
GET /users/123
If-None-Match: "abc123"
响应(未修改):
304 Not Modified
ETag: "abc123"
Last-Modified验证:
请求:
GET /users/123
If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
响应(未修改):
304 Not Modified
Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
服务器端缓存
缓存策略选择:
| 资源类型 | 缓存策略 | Cache-Control | 示例 |
|---|---|---|---|
| 静态资源 | 长期缓存 | max-age=31536000 |
图片、CSS、JS |
| 用户数据 | 不缓存 | no-cache |
个人信息 |
| 公开数据 | 短期缓存 | max-age=3600 |
商品列表 |
| 动态数据 | 验证缓存 | no-cache + ETag |
实时数据 |
分页优化
偏移量分页 vs 游标分页
偏移量分页(Offset-based):
GET /users?page=2&per_page=20
优点:
- 实现简单
- 支持跳页
缺点:
- 大数据量时性能差
- 数据变化时可能重复或遗漏
游标分页(Cursor-based):
GET /users?cursor=eyJpZCI6MTIzfQ&limit=20
优点:
- 性能好(使用索引)
- 数据一致性强
缺点:
- 不支持跳页
- 实现相对复杂
选择建议:
- 小数据量(<1000条):使用偏移量分页
- 大数据量(>1000条):使用游标分页
响应压缩
启用Gzip压缩:
请求头:
Accept-Encoding: gzip, deflate
响应头:
Content-Encoding: gzip
压缩效果:
- JSON响应:通常可压缩70-90%
- 文本响应:可压缩60-80%
- 图片/视频:已压缩,效果有限
字段选择(Field Selection)
按需返回字段:
GET /users?fields=id,name,email
优势:
- 减少传输量
- 提升响应速度
- 降低带宽成本
实现方式:
- 查询参数:
?fields=id,name,email - 请求头:
X-Fields: id,name,email - GraphQL:原生支持
批量操作
批量查询:
GET /users?ids=1,2,3,4,5
或
POST /users/batch
{ "ids": [1, 2, 3, 4, 5] }
批量更新:
PATCH /users/batch
{
"updates": [
{"id": 1, "status": "active"},
{"id": 2, "status": "inactive"}
]
}
优势:
- 减少网络往返
- 提升性能
- 支持事务处理
总结
RESTful API设计核心要点
RESTful API设计核心:
┌─────────────────────────────────────────────┐
│ 1. 资源为中心 │
│ - 使用名词,不用动词 │
│ - 使用复数形式 │
│ - 层级不宜过深 │
│ - 每个资源有唯一URI │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 2. 正确使用HTTP方法 │
│ - GET:查询(幂等、安全) │
│ - POST:创建(非幂等) │
│ - PUT:全量更新(幂等) │
│ - PATCH:部分更新(幂等) │
│ - DELETE:删除(幂等) │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 3. 准确的状态码 │
│ - 2xx:成功(200/201/204/202) │
│ - 3xx:重定向(301/302/304) │
│ - 4xx:客户端错误(400/401/403/404/422) │
│ - 5xx:服务器错误(500/502/503/504) │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 4. 统一的数据格式 │
│ - JSON格式(默认) │
│ - 统一的响应结构 │
│ - 清晰的错误信息 │
│ - 支持字段选择 │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 5. 安全与认证 │
│ - HTTPS强制 │
│ - Token认证(JWT/OAuth) │
│ - 权限控制(RBAC/ABAC) │
│ - 输入校验和限流 │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 6. 性能优化 │
│ - HTTP缓存(ETag/Last-Modified) │
│ - 响应压缩(Gzip) │
│ - 分页优化(游标分页) │
│ - 批量操作 │
└─────────────────────────────────────────────┘
设计原则总结
| 原则 | 说明 | 实现方式 |
|---|---|---|
| 清晰性 | 接口语义明确,易于理解 | 使用标准HTTP方法和状态码 |
| 一致性 | 统一的命名和结构规范 | 遵循RESTful约定 |
| 可维护性 | 易于扩展和修改 | 版本控制、向后兼容 |
| 可测试性 | 便于自动化测试 | 无状态、幂等性 |
| 安全性 | 内置安全考虑 | HTTPS、认证、授权、限流 |
| 向后兼容 | 保持API的稳定性 | 只增不减、版本管理 |
| 文档完善 | 提供清晰的文档和示例 | OpenAPI/Swagger |
最佳实践清单
设计阶段:
- 使用名词作为资源路径
- 正确使用HTTP方法(考虑幂等性和安全性)
- 设计清晰的URI结构(不超过2-3层)
- 规划API版本控制策略
- 设计统一的响应格式
实现阶段:
- 返回准确的状态码
- 统一响应格式(成功/错误)
- 实现认证和授权(JWT/OAuth)
- 添加输入校验和输出编码
- 实施限流策略
- 启用HTTPS和HSTS
优化阶段:
- 实现HTTP缓存(ETag/Last-Modified)
- 启用响应压缩(Gzip)
- 优化分页策略(大数据量使用游标)
- 支持字段选择(减少传输量)
- 提供批量操作接口
维护阶段:
- 保持向后兼容(只增不减)
- 及时更新文档(OpenAPI)
- 监控API性能(响应时间、错误率)
- 规划废弃策略(Deprecation)
- 收集用户反馈
常见问题速查
| 问题 | 解决方案 |
|---|---|
| 如何设计嵌套资源? | 不超过2-3层,更深关系用查询参数 |
| PUT和PATCH的区别? | PUT全量更新,PATCH部分更新 |
| 如何保证幂等性? | POST使用唯一业务标识,PATCH使用版本号 |
| 如何处理复杂查询? | 使用POST /search,或GraphQL |
| 如何实现版本控制? | URL路径(推荐)或请求头 |
| 如何优化性能? | 缓存、压缩、分页、批量操作 |
记住:REST是风格,不是宗教。在遵循核心原则的基础上,根据实际需求灵活调整,设计出清晰、易用、安全、高性能的API。