- 什么是 RESTful API
- 资源(Resource):网络上的任何实体都可以被抽象为资源,通过 URI 来唯一标识
- 表述(Representation):资源的具体表现形式,如 JSON、XML、HTML 等
- 状态转移(State Transfer):客户端通过 HTTP 方法操作服务端资源,实现状态的变更
- 统一接口(Uniform Interface):使用标准的 HTTP 方法和状态码
- 移动应用与后端服务器通信
- 前后端分离的 Web 应用
- 微服务架构中的服务间通信
- 第三方开放平台(如微信、支付宝、GitHub 等)
- IoT 设备数据交互
- 云服务的公共接口
- REST 六大设计原则
- 资源标识:通过 URI 唯一标识资源
- 通过表述操作资源:客户端通过资源的表述来修改资源状态
- 自描述消息:每条消息包含足够的信息让接收者知道如何处理
- 超媒体驱动(HATEOAS):响应中包含下一步可能的操作链接
- HTTP 请求方法详解
- 幂等性:是 ✅ — 多次请求结果相同
- 安全性:是 ✅ — 不修改资源
- 可缓存:是 ✅
- 幂等性:否 ❌ — 多次请求会创建多个资源
- 安全性:否 ❌ — 修改服务器状态
- 可缓存:否 ❌
- 幂等性:是 ✅
- 安全性:否 ❌
- 可缓存:否 ❌
- 幂等性:不一定 ⚠️(取决于具体实现)
- 安全性:否 ❌
- 可缓存:否 ❌
- 幂等性:是 ✅
- 安全性:否 ❌
- 可缓存:否 ❌
- HTTP 状态码详解
- 使用最精确的状态码,而不是笼统的 200/500
- POST 创建成功应返回 201 而非 200
- DELETE 成功无返回体时用 204
- 异步操作用 202 并附带任务 ID 供查询
- URL 资源设计规范
- URL 代表资源(名词),HTTP 方法代表操作(动词)
- 使用名词而非动词
- 使用复数名词
- 使用小写字母和连字符
- 避免过深的嵌套层级
- 请求与响应格式
- application/json — JSON 格式(最常用)
- application/xml — XML 格式
- multipart/form-data — 文件上传
- application/x-www-form-urlencoded — 表单数据
- text/plain — 纯文本
- 偏移量分页:适合数据量不大、变化不频繁的场景,实现简单
- 游标分页:适合数据量大、实时性要求高的场景(如社交媒体 feed),性能更好
- API 版本控制策略
- 只维护最近 2-3 个活跃版本
- 提供版本弃用时间表
- 返回弃用警告响应头
- 向后兼容优先,尽量减少破坏性变更
- 删除字段或端点
- 修改字段类型或格式
- 改变认证方式
- 修改默认分页大小
- 新增必填参数
- 身份认证与安全
- 强制 HTTPS:所有 API 请求必须使用 TLS/SSL 加密
- CORS 配置:只允许信任的域名访问
- 输入验证:严格验证所有输入参数
- SQL 注入防护:使用参数化查询
- XSS 防护:对输出进行转义
- CSRF 防护:使用 CSRF token
- 敏感信息脱敏:不返回密码、密钥等敏感数据
- Token 应设置合理的过期时间(15-60分钟)
- 使用 Refresh Token 机制续期
- 不要将敏感信息存入 JWT payload
- 使用强密钥签名(HS256/RS256)
- 实现 Token 黑名单机制
- 分页、过滤与排序
- 大数据量场景推荐使用游标分页
- 为常用过滤字段建立数据库索引
- 限制最大分页数量防止爬取
- 提供默认 limit 值和最大 limit 限制
- 错误处理规范
- 始终返回有意义的错误信息
- 不在生产环境暴露技术细节(如堆栈信息)
- 为每个错误分配唯一的请求 ID 便于追踪
- 提供错误文档链接帮助开发者理解
- 对客户端错误和服务器错误分别处理
- 数据库连接字符串
- 服务器文件路径
- 完整的堆栈追踪信息
- 第三方服务的密钥
- 限流与缓存
- 固定窗口:固定时间段内允许固定次数的请求
- 滑动窗口:基于滑动时间窗口的计数
- 令牌桶:以固定速率产生令牌,请求需消耗令牌
- 漏桶:以固定速率处理请求
- 公开数据(如商品列表):较长缓存 + ETag 验证
- 用户私有数据:短缓存或不缓存
- 实时性要求高的数据:不缓存
- 静态资源:强缓存 + 文件名 hash
- 文档与规范
- Postman:API 开发和测试工具,自带文档功能
- API Blueprint:Markdown 格式的 API 描述语言
- RAML:RESTful API Modeling Language
- Redoc:基于 OpenAPI 的美观文档生成器
- Stoplight:API 设计和管理平台
- JSON:API — JSON API 规范 (jsonapi.org)
- Google API Design Guide — Google API 设计指南
- Microsoft REST API Guidelines — 微软 REST API 指南
- Zalando RESTful API Guidelines — Zalando API 指南
- 每个端点的描述、参数说明、请求示例
- 响应示例(成功和失败)
- 认证方式说明
- 错误码含义
- 限流策略
- SDK/代码示例
- 变更日志(Changelog)
- 常见框架与工具
- Postman:API 测试、调试、文档、Mock
- Insomnia:Postman 的开源替代
- cURL:命令行 HTTP 客户端
- HTTPie:更友好的命令行工具
- Swagger UI:交互式 API 文档
- JUnit / Pytest:单元测试框架
- Supertest:Node.js API 测试库
- JSON Server:一行命令启动 Mock API
- Mockoon:桌面 Mock 工具
- WireMock:Java Mock 服务器
- Prism:基于 OpenAPI 的 Mock 服务器
- 最佳实践总结
- 以资源为中心:URL 代表资源,HTTP 方法代表操作
- 使用名词而非动词:/users 而非 /getUsers
- 使用复数形式:保持一致性
- 嵌套不超过2层:避免过度复杂的 URL
- 正确使用 HTTP 状态码:不要所有响应都返回 200
- 使用 HTTPS:安全是基本要求
- 统一使用 JSON:除非有特殊需求
- 使用驼峰或蛇形命名:保持一致
- 日期使用 ISO 8601:UTC 时间
- 提供集合包装:数组包裹在对象中,便于扩展
- 支持字段选择:减少不必要的数据传输
- 实现限流:防止滥用
- 合理使用缓存:减少服务器负载
- 压缩响应:启用 gzip/brotli 压缩
- 异步处理耗时操作:返回 202 并提供状态查询
- 做好日志记录:便于排查问题
- 完整实战示例
- REST vs GraphQL vs gRPC
- 需要良好的 HTTP 缓存支持
- API 结构相对简单
- 面向公众的开放 API
- 团队对 REST 更熟悉
- 需要利用 HTTP 标准功能(如状态码、HEAD 方法)
- 移动端需要精确控制返回数据
- 前端需要灵活的查询能力
- 资源关系复杂(避免多次请求)
- 多客户端有不同数据需求
- 需要强大的开发者工具(GraphiQL)
- 微服务内部高性能通信
- 需要强类型和代码生成
- 需要双向流通信
- 多语言环境需要统一接口定义
- 对延迟和带宽敏感的场景
- 对外暴露 REST API,内部服务用 gRPC 通信
- 前端使用 GraphQL 聚合数据,后端使用 REST 获取数据
- BFF(Backend For Frontend)模式中使用 GraphQL
- 常见问题 FAQ
- 幂等:GET、PUT、DELETE(同一资源多次删除效果相同)
- 非幂等:POST(每次调用都创建新资源)
- 只添加新的可选字段,不删除或重命名已有字段
- 不改变已有字段的类型或格式
- 不新增必填参数
- 不改变错误的含义
- 使用版本控制处理破坏性变更
- 提供充足的弃用通知期
- API Gateway:统一入口,负责路由、认证、限流
- Service Mesh:服务间通信管理(如 Istio、Linkerd)
- 统一认证:SSO 单点登录,统一 Token 管理
- 统一文档:聚合所有服务的 API 文档
- 统一监控:分布式链路追踪(如 Jaeger、Zipkin)
- 简洁性 vs 功能性
- 灵活性 vs 一致性
- 性能 vs 可读性
- 安全性 vs 易用性
REST 的定义
REST(Representational State Transfer,表述性状态转移)是一种软件架构风格,由 Roy Fielding 在 2000 年的博士论文中首次提出。它定义了一组约束条件,用于指导如何设计可扩展、可维护的网络服务。
API 的定义
API(Application Programming Interface,应用程序编程接口)是软件系统之间交互的约定。RESTful API 就是遵循 REST 架构风格设计的 Web API。
RESTful API 的核心概念
RESTful API 的发展历史
在 REST 出现之前,Web 服务主要采用 SOAP(Simple Object Access Protocol)和 RPC(Remote Procedure Call)风格。SOAP 依赖于复杂的 XML 规范和 WSDL 描述文件,而 RPC 风格的 API 将所有操作映射到单一端点。
REST 的出现极大地简化了 Web 服务的设计,结合 JSON 的轻量级数据格式,使其成为当今最流行的 API 设计范式。
💡 关键区别
"REST" 是一种架构风格,"RESTful" 是符合 REST 约束的形容词。只有完全满足 REST 六大约束的 API 才能被称为 RESTful API。
RESTful API 的应用场景
1. 客户端-服务器分离(Client-Server)
客户端和服务器各司其职,通过统一的接口进行交互。这种分离使得客户端不需要了解数据存储逻辑,服务器不需要了解用户界面逻辑。
✅ 好处
提高了可移植性(客户端可以独立开发),简化了服务器组件,支持大规模系统的演进。
2. 无状态性(Stateless)
每个请求都包含服务器处理该请求所需的全部信息。服务器不会在请求之间保存客户端的会话状态。
// 无状态请求示例 - 每次请求都携带认证信息
GET /api/users/123 HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
// 不需要依赖服务器端的 session 存储
3. 可缓存性(Cacheable)
响应中需要明确声明该响应是否可以被缓存。通过 HTTP 缓存头(如 Cache-Control、ETag、Last-Modified)来控制缓存行为。
4. 统一接口(Uniform Interface)
这是 REST 最核心的约束,包含四个子约束:
// HATEOAS 示例 - 响应中包含关联资源链接
{
"id": 123,
"name": "张三",
"email": "zhangsan@example.com",
"_links": {
"self": { "href": "/api/users/123" },
"orders": { "href": "/api/users/123/orders" },
"update": { "href": "/api/users/123", "method": "PUT" },
"delete": { "href": "/api/users/123", "method": "DELETE" }
}
}
5. 分层系统(Layered System)
客户端不需要知道它是直接连接到服务器,还是连接到了中间层(如负载均衡器、缓存、网关等)。每一层只与相邻层交互。
6. 按需代码(Code on Demand)——可选
服务器可以临时性地通过下载可执行代码(如 JavaScript、Applet)来扩展客户端的功能。这是唯一可选的约束。
⚠️ 注意
一个 API 必须满足全部六个约束(前五个必须满足)才能被称为"RESTful"。许多自称"RESTful"的 API 实际上只是"HTTP API"或"REST-like API"。
RESTful API 使用标准的 HTTP 方法来定义对资源的操作。每个方法都有明确的语义和行为特征。
GET 获取资源
用于从服务器获取资源,不修改服务器状态。
# 获取所有用户
GET /api/users HTTP/1.1
Host: api.example.com
Accept: application/json
# 获取特定用户
GET /api/users/123 HTTP/1.1
Host: api.example.com
Accept: application/json
# 带查询参数
GET /api/users?page=1&limit=20&sort=name HTTP/1.1
POST 创建资源
用于在服务器创建新资源。
# 创建新用户
POST /api/users HTTP/1.1
Host: api.example.com
Content-Type: application/json
{
"name": "李四",
"email": "lisi@example.com",
"age": 28
}
# 成功响应
HTTP/1.1 201 Created
Location: /api/users/456
{
"id": 456,
"name": "李四",
"email": "lisi@example.com",
"age": 28,
"created_at": "2025-01-15T10:30:00Z"
}
PUT 完整更新/创建资源
用于完整替换指定资源。如果资源不存在则创建。
# 完整替换用户信息
PUT /api/users/123 HTTP/1.1
Host: api.example.com
Content-Type: application/json
{
"name": "张三(更新)",
"email": "zhangsan_new@example.com",
"age": 31,
"phone": "13800138000",
"address": "北京市朝阳区"
}
PATCH 部分更新资源
用于对资源进行部分修改,只发送需要变更的字段。
# 只更新用户的邮箱
PATCH /api/users/123 HTTP/1.1
Host: api.example.com
Content-Type: application/json
{
"email": "newemail@example.com"
}
# JSON Patch 格式(RFC 6902)
PATCH /api/users/123 HTTP/1.1
Content-Type: application/json-patch+json
[
{ "op": "replace", "path": "/email", "value": "newemail@example.com" },
{ "op": "add", "path": "/phone", "value": "13800138000" },
{ "op": "remove", "path": "/address" }
]
DELETE 删除资源
用于删除指定资源。
# 删除用户
DELETE /api/users/123 HTTP/1.1
Host: api.example.com
Authorization: Bearer token123
# 成功响应
HTTP/1.1 204 No Content
HEAD 获取响应头
与 GET 相同,但服务器只返回响应头,不返回响应体。常用于检查资源是否存在或获取元信息。
HEAD /api/users/123 HTTP/1.1
Host: api.example.com
# 响应(无 body)
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 1234
ETag: "abc123"
Last-Modified: Thu, 15 Jan 2025 10:30:00 GMT
OPTIONS 获取支持的方法
用于获取目标资源所支持的通信选项。常用于 CORS 预检请求。
OPTIONS /api/users HTTP/1.1
Host: api.example.com
Origin: https://frontend.example.com
# 响应
HTTP/1.1 204 No Content
Allow: GET, POST, OPTIONS
Access-Control-Allow-Origin: https://frontend.example.com
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
方法对照表
| 方法 | 操作 | 幂等 | 安全 | 请求体 |
|---|---|---|---|---|
| GET | 读取 | ✅ | ✅ | 不应有 |
| POST | 创建 | ❌ | ❌ | 必须有 |
| PUT | 替换 | ✅ | ❌ | 必须有 |
| PATCH | 部分更新 | ⚠️ | ❌ | 必须有 |
| DELETE | 删除 | ✅ | ❌ | 可选 |
HTTP 状态码是服务器向客户端返回的三位数字响应代码,用于表示请求的处理结果。RESTful API 应该使用准确的状态码。
2xx 成功 — 请求成功处理
| 状态码 | 含义 | 使用场景 |
|---|---|---|
| 200 OK | 请求成功 | GET、PUT、PATCH 成功返回数据 |
| 201 Created | 资源已创建 | POST 创建资源成功,应附带 Location 头 |
| 202 Accepted | 请求已接受但未处理完 | 异步操作,如批量任务 |
| 204 No Content | 成功但无返回内容 | DELETE 成功、PUT 更新成功 |
3xx 重定向 — 需要进一步操作
| 状态码 | 含义 | 使用场景 |
|---|---|---|
| 301 Moved Permanently | 资源永久移动 | URL 结构变更 |
| 302 Found | 资源临时移动 | 临时重定向 |
| 304 Not Modified | 资源未修改 | 使用缓存,配合 ETag/If-None-Match |
| 307 Temporary Redirect | 临时重定向(保持方法) | 重定向时保持原始 HTTP 方法 |
4xx 客户端错误 — 客户端请求有误
| 状态码 | 含义 | 使用场景 |
|---|---|---|
| 400 Bad Request | 请求格式错误 | 参数校验失败、JSON 格式不正确 |
| 401 Unauthorized | 未认证 | 缺少认证信息或 token 无效 |
| 403 Forbidden | 无权限 | 已认证但无权限访问该资源 |
| 404 Not Found | 资源不存在 | 请求的 URI 无对应资源 |
| 405 Method Not Allowed | 方法不允许 | 对资源使用了不支持的 HTTP 方法 |
| 406 Not Acceptable | 不可接受 | 无法返回客户端要求的格式 |
| 409 Conflict | 冲突 | 资源状态冲突,如重复创建 |
| 410 Gone | 资源已删除 | 资源曾经存在但已被永久删除 |
| 415 Unsupported Media Type | 不支持的媒体类型 | Content-Type 不受支持 |
| 422 Unprocessable Entity | 无法处理 | 格式正确但语义错误(验证失败) |
| 429 Too Many Requests | 请求过多 | 触发限流策略 |
5xx 服务器错误 — 服务器处理失败
| 状态码 | 含义 | 使用场景 |
|---|---|---|
| 500 Internal Server Error | 服务器内部错误 | 未预期的异常 |
| 501 Not Implemented | 功能未实现 | 服务器不支持请求的功能 |
| 502 Bad Gateway | 网关错误 | 上游服务不可用 |
| 503 Service Unavailable | 服务不可用 | 服务器过载或维护中 |
| 504 Gateway Timeout | 网关超时 | 上游服务响应超时 |
✅ 最佳实践
基本原则
好的 URL 设计 ✅
# 集合资源
GET /api/users # 获取用户列表
POST /api/users # 创建新用户
# 单个资源
GET /api/users/123 # 获取特定用户
PUT /api/users/123 # 更新用户
PATCH /api/users/123 # 部分更新用户
DELETE /api/users/123 # 删除用户
# 子资源
GET /api/users/123/orders # 获取用户的订单列表
GET /api/users/123/orders/456 # 获取用户的特定订单
POST /api/users/123/orders # 为用户创建订单
差的 URL 设计 ❌
# 使用动词 - 不好
GET /api/getUser?id=123
POST /api/createUser
GET /api/deleteUser/123
# 使用单数 - 不一致
GET /api/user/123
GET /api/products
# 过于复杂的嵌套
GET /api/companies/5/departments/3/teams/7/members/123
查询参数使用规范
# 过滤
GET /api/products?category=electronics&price_min=100&price_max=500
# 排序
GET /api/products?sort=price&order=desc
# 分页
GET /api/products?page=2&limit=20
# 搜索
GET /api/products?q=wireless+headphone
# 字段选择(稀疏字段集)
GET /api/users?fields=id,name,email
# 组合使用
GET /api/products?category=electronics&sort=-price&page=1&limit=20&fields=id,name,price
⚠️ 关于嵌套深度的建议
嵌套层级建议不超过 2 层。过深的嵌套会导致 URL 难以管理,并且暗示了不必要的依赖关系。
# 不推荐:3层嵌套
GET /api/users/123/orders/456/items/789
# 推荐:使用顶层资源直接访问
GET /api/orders/456
GET /api/order-items/789
特殊操作的 URL 设计
# 对于无法映射到 CRUD 的操作,可以:
# 方案1:作为子资源
POST /api/orders/123/cancel # 取消订单
POST /api/users/123/activate # 激活用户
POST /api/posts/456/publish # 发布文章
# 方案2:使用自定义动作端点
POST /api/actions/cancel-order
{
"order_id": 123
}
# 方案3:使用状态变更(PUT)
PUT /api/orders/123/status
{
"status": "cancelled"
}
Content-Type(内容类型)
请求和响应中必须明确指定数据的格式:
JSON 响应设计规范
// 单个资源
{
"data": {
"id": 123,
"type": "user",
"attributes": {
"name": "张三",
"email": "zhangsan@example.com",
"created_at": "2025-01-15T10:30:00Z"
}
}
}
// 集合资源
{
"data": [
{ "id": 1, "name": "张三", "email": "..." },
{ "id": 2, "name": "李四", "email": "..." }
],
"meta": {
"total": 150,
"page": 1,
"limit": 20,
"total_pages": 8
},
"links": {
"self": "/api/users?page=1",
"next": "/api/users?page=2",
"prev": null,
"last": "/api/users?page=8"
}
}
HTTP 头部规范
# 请求头
GET /api/users/123 HTTP/1.1
Host: api.example.com
Accept: application/json # 期望的响应格式
Authorization: Bearer token123 # 认证信息
Content-Type: application/json # 请求体格式
If-None-Match: "abc123" # 条件请求(缓存)
X-Request-ID: uuid-v4 # 请求追踪 ID
# 响应头
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Cache-Control: max-age=3600, public
ETag: "abc123"
Last-Modified: Thu, 15 Jan 2025 10:30:00 GMT
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1705312200
X-Request-ID: uuid-v4
日期时间格式
💡 推荐使用 ISO 8601 格式
统一使用 UTC 时间,格式为 YYYY-MM-DDTHH:mm:ssZ 或带时区偏移 YYYY-MM-DDTHH:mm:ss+08:00
// ISO 8601 示例
"created_at": "2025-01-15T10:30:00Z"
"updated_at": "2025-01-15T18:30:00+08:00"
分页方式
偏移量分页(Offset-based)
GET /api/users?page=3&limit=20
# 计算:offset = (page - 1) * limit = (3-1) * 20 = 40
游标分页(Cursor-based)
GET /api/users?cursor=eyJpZCI6MTAwfQ&limit=20
# 响应中返回下一页的 cursor
{
"data": [...],
"pagination": {
"next_cursor": "eyJpZCI6MTIwfQ",
"has_more": true
}
}
✅ 分页方式选择建议
API 版本控制是确保向后兼容性的重要手段。当 API 发生破坏性变更时,需要发布新版本。
方案一:URL 路径版本(最常用)
https://api.example.com/v1/users
https://api.example.com/v2/users
# 优点:直观、易理解、易路由
# 缺点:URL 中包含版本信息,不够 RESTful
方案二:请求头版本
GET /api/users HTTP/1.1
Host: api.example.com
Accept: application/vnd.myapi.v1+json
# 优点:URL 更干净
# 缺点:不够直观,调试不便
方案三:查询参数版本
GET /api/users?version=1 HTTP/1.1
GET /api/users?v=2 HTTP/1.1
# 优点:灵活
# 缺点:参数容易被忽略,缓存问题
方案四:主机名版本
https://v1.api.example.com/users
https://v2.api.example.com/users
# 优点:URL 简洁
# 缺点:需要多套域名配置
版本控制最佳实践
# 弃用警告响应头
HTTP/1.1 200 OK
Deprecation: true
Sunset: Sat, 01 Jan 2027 00:00:00 GMT
Link: <https://api.example.com/v3/docs>; rel="successor-version"
⚠️ 何时需要发新版本?
不需要新版本的变更:新增可选字段、新增端点、新增可选参数。
常见认证方式
1. API Key(API 密钥)
# 通过请求头传递
GET /api/data HTTP/1.1
X-API-Key: your-api-key-here
# 通过查询参数传递(不推荐)
GET /api/data?api_key=your-api-key-here
适合服务端间通信,不适合客户端应用(密钥容易泄露)。
2. Bearer Token(JWT)
# 认证流程
POST /api/auth/login HTTP/1.1
Content-Type: application/json
{
"username": "zhangsan",
"password": "password123"
}
# 响应返回 token
HTTP/1.1 200 OK
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "dGhpcyBpcyBhIHJlZnJlc2g..."
}
# 后续请求携带 token
GET /api/users/me HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
3. OAuth 2.0
# 授权码模式流程
# 1. 引导用户到授权页面
GET https://auth.example.com/authorize?
response_type=code&
client_id=your_client_id&
redirect_uri=https://yourapp.com/callback&
scope=read+write&
state=random_state_string
# 2. 用户授权后回调带 code
GET https://yourapp.com/callback?code=AUTH_CODE&state=random_state_string
# 3. 用 code 换取 token
POST https://auth.example.com/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=AUTH_CODE&
redirect_uri=https://yourapp.com/callback&
client_id=your_client_id&
client_secret=your_client_secret
4. Basic Auth(基本认证)
# Base64 编码 username:password
GET /api/data HTTP/1.1
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
# 注意:必须配合 HTTPS 使用!
安全最佳实践
CORS 配置示例
# 服务器端 CORS 响应头
Access-Control-Allow-Origin: https://your-frontend.com
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 86400
💡 JWT 安全提示
分页实现
偏移量分页(Offset Pagination)
GET /api/products?page=2&limit=20
# 响应
{
"data": [...],
"pagination": {
"current_page": 2,
"per_page": 20,
"total_items": 356,
"total_pages": 18,
"has_next": true,
"has_prev": true
}
}
# SQL 实现
SELECT * FROM products LIMIT 20 OFFSET 20;
游标分页(Cursor Pagination)
GET /api/feed?cursor=eyJpZCI6MTAwfQ&limit=20
# 响应
{
"data": [...],
"pagination": {
"next_cursor": "eyJpZCI6MTIwfQ",
"prev_cursor": "eyJpZCI6ODB9",
"has_more": true
}
}
# SQL 实现(以 id 为游标)
SELECT * FROM posts WHERE id < 100 ORDER BY id DESC LIMIT 20;
过滤(Filtering)
# 精确匹配
GET /api/users?status=active
# 范围查询
GET /api/products?price_gte=100&price_lte=500
GET /api/users?created_after=2025-01-01
# 包含/排除
GET /api/products?category=electronics,books
GET /api/users?exclude_roles=admin,moderator
# 模糊搜索
GET /api/products?q=wireless
# 复合条件
GET /api/products?status=published&category=electronics&price_lte=300&sort=-created_at
排序(Sorting)
# 升序(默认)
GET /api/products?sort=name
GET /api/products?sort=+name # 显式升序
# 降序
GET /api/products?sort=-price # 按价格降序
# 多字段排序
GET /api/products?sort=-category,name # 先按分类降序,再按名称升序
字段选择(Sparse Fieldsets)
# 只返回指定字段,减少传输数据量
GET /api/users?fields=id,name,email
GET /api/products?fields=id,name,price&include=category
# JSON:API 规范的写法
GET /api/products?fields[product]=name,price&fields[category]=name
✅ 性能优化建议
统一错误响应格式
{
"error": {
"code": "VALIDATION_ERROR",
"message": "请求参数验证失败",
"details": [
{
"field": "email",
"message": "邮箱格式不正确",
"value": "invalid-email"
},
{
"field": "age",
"message": "年龄必须大于 0 且小于 150",
"value": -5
}
],
"request_id": "req_abc123xyz",
"timestamp": "2025-01-15T10:30:00Z",
"path": "/api/users"
}
}
常见错误码定义
| 错误码 | HTTP 状态码 | 描述 |
|---|---|---|
| VALIDATION_ERROR | 400/422 | 参数验证失败 |
| UNAUTHORIZED | 401 | 未认证或 token 过期 |
| FORBIDDEN | 403 | 无权限访问 |
| NOT_FOUND | 404 | 资源不存在 |
| CONFLICT | 409 | 资源冲突 |
| RATE_LIMITED | 429 | 请求频率超限 |
| INTERNAL_ERROR | 500 | 服务器内部错误 |
| SERVICE_UNAVAILABLE | 503 | 服务暂时不可用 |
错误处理最佳实践
# 带文档链接的错误响应
{
"error": {
"code": "INVALID_PARAMETER",
"message": "参数 'status' 的值无效",
"documentation_url": "https://docs.api.com/errors#INVALID_PARAMETER",
"allowed_values": ["active", "inactive", "pending"],
"request_id": "req_xyz789"
}
}
⚠️ 安全提示
永远不要在错误响应中暴露:
API 限流(Rate Limiting)
限流是保护 API 免受滥用和 DDoS 攻击的重要手段。
限流策略
限流响应头
HTTP/1.1 200 OK
X-RateLimit-Limit: 1000 # 时间窗口内总限额
X-RateLimit-Remaining: 998 # 剩余次数
X-RateLimit-Reset: 1705312200 # 重置时间(Unix 时间戳)
X-RateLimit-Window: 3600 # 窗口大小(秒)
# 超限时返回
HTTP/1.1 429 Too Many Requests
Retry-After: 60 # 建议等待秒数
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1705312200
HTTP 缓存
强缓存
# 服务器设置缓存头
Cache-Control: max-age=3600, public
Expires: Thu, 15 Jan 2025 11:30:00 GMT
# max-age: 缓存有效期(秒)
# public: 可被任何缓存存储
# private: 只能被用户浏览器缓存
# no-cache: 需要向服务器验证
# no-store: 禁止缓存
协商缓存
# 首次响应 - 携带 ETag
HTTP/1.1 200 OK
ETag: "abc123"
Cache-Control: max-age=0, must-revalidate
# 后续请求 - 客户端携带 If-None-Match
GET /api/users/123 HTTP/1.1
If-None-Match: "abc123"
# 服务器判断:资源未变化
HTTP/1.1 304 Not Modified
# 无 body,客户端使用本地缓存
# 服务器判断:资源已变化
HTTP/1.1 200 OK
ETag: "def456"
{ "id": 123, "name": "张三(已更新)", ... }
基于时间的协商缓存
# 首次响应
HTTP/1.1 200 OK
Last-Modified: Thu, 15 Jan 2025 10:30:00 GMT
# 后续请求
GET /api/users/123 HTTP/1.1
If-Modified-Since: Thu, 15 Jan 2025 10:30:00 GMT
# 未修改则返回 304
✅ 缓存策略建议
API 文档标准
OpenAPI / Swagger(最流行)
openapi: 3.0.3
info:
title: 用户管理 API
description: 用户 CRUD 操作的 RESTful API
version: 1.0.0
contact:
email: api@example.com
servers:
- url: https://api.example.com/v1
description: 生产环境
- url: https://staging-api.example.com/v1
description: 测试环境
paths:
/users:
get:
summary: 获取用户列表
operationId: getUsers
tags:
- Users
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'
post:
summary: 创建新用户
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateUserRequest'
responses:
'201':
description: 用户创建成功
components:
schemas:
User:
type: object
properties:
id:
type: integer
name:
type: string
email:
type: string
format: email
CreateUserRequest:
type: object
required:
- name
- email
properties:
name:
type: string
email:
type: string
format: email
其他文档工具
API 规范参考
💡 文档应包含的内容
后端框架
| 语言 | 框架 | 特点 |
|---|---|---|
| Node.js | Express.js / Koa / Fastify / NestJS | 轻量、中间件机制、高性能 |
| Python | Django REST / Flask / FastAPI | FastAPI 自动文档、类型安全 |
| Java | Spring Boot / Quarkus | 企业级、生态成熟 |
| Go | Gin / Echo / Fiber | 高并发、编译型 |
| PHP | Laravel / Symfony | 快速开发、ORM 强大 |
| Ruby | Rails / Grape | 约定优于配置 |
| C# | ASP.NET Core | 跨平台、高性能 |
| Rust | Actix-web / Axum / Rocket | 内存安全、极致性能 |
测试工具
常用 cURL 命令
# GET 请求
curl -X GET "https://api.example.com/users" \
-H "Accept: application/json" \
-H "Authorization: Bearer token123"
# POST 请求
curl -X POST "https://api.example.com/users" \
-H "Content-Type: application/json" \
-d '{"name":"张三","email":"test@example.com"}'
# PUT 请求
curl -X PUT "https://api.example.com/users/123" \
-H "Content-Type: application/json" \
-d '{"name":"李四","email":"lisi@example.com"}'
# DELETE 请求
curl -X DELETE "https://api.example.com/users/123"
# 显示详细信息
curl -v -X GET "https://api.example.com/users"
# 保存到文件
curl -o response.json "https://api.example.com/users"
Mock 工具
# JSON Server - 快速搭建 Mock API
# 1. 创建 db.json
{
"users": [
{ "id": 1, "name": "张三" },
{ "id": 2, "name": "李四" }
]
}
# 2. 启动服务
npx json-server --watch db.json --port 3000
# 3. 自动支持 RESTful 端点
# GET /users
# GET /users/1
# POST /users
# PUT /users/1
# DELETE /users/1
设计原则
数据格式
安全与性能
API 设计检查清单
| 类别 | 检查项 | ✅ |
|---|---|---|
| URL | 使用名词、复数、小写、连字符 | □ |
| 方法 | GET/POST/PUT/PATCH/DELETE 语义正确 | □ |
| 状态码 | 返回精确的 HTTP 状态码 | □ |
| 版本 | API 有明确的版本控制 | □ |
| 认证 | 有安全的认证机制 | □ |
| 分页 | 列表接口支持分页 | □ |
| 过滤 | 支持常用的过滤条件 | □ |
| 排序 | 支持灵活排序 | □ |
| 错误 | 统一的错误响应格式 | □ |
| 文档 | 有完整的 API 文档 | □ |
| 缓存 | 合理使用 HTTP 缓存 | □ |
| 限流 | 实现了 API 限流 | □ |
| CORS | 正确配置跨域策略 | □ |
| HTTPS | 强制使用 HTTPS | □ |
✅ 黄金法则
"好的 API 设计应该是:直觉性的、一致性的、可发现的、文档清晰的、向后兼容的。"
让你的 API 像一本好书一样——读者(开发者)可以凭直觉理解如何使用它。
场景:博客系统 API
以下是一个完整的博客系统 RESTful API 设计示例,包含文章、评论、用户管理等功能。
API 端点总览
基础 URL: https://api.blog-example.com/v1
# 用户管理
POST /auth/register # 用户注册
POST /auth/login # 用户登录
POST /auth/refresh # 刷新 Token
POST /auth/logout # 退出登录
GET /users/me # 获取当前用户信息
PUT /users/me # 更新个人资料
# 文章管理
GET /posts # 获取文章列表
POST /posts # 创建文章
GET /posts/:id # 获取单篇文章
PUT /posts/:id # 更新文章
DELETE /posts/:id # 删除文章
POST /posts/:id/publish # 发布文章
# 评论管理
GET /posts/:id/comments # 获取文章评论
POST /posts/:id/comments # 发表评论
DELETE /comments/:id # 删除评论
# 分类管理
GET /categories # 获取分类列表
POST /categories # 创建分类
GET /categories/:id/posts # 获取分类下的文章
完整请求/响应示例
1. 用户注册
# 请求
POST /v1/auth/register HTTP/1.1
Host: api.blog-example.com
Content-Type: application/json
{
"username": "zhangsan",
"email": "zhangsan@example.com",
"password": "SecurePass123!",
"display_name": "张三"
}
# 成功响应 201
{
"data": {
"id": 456,
"username": "zhangsan",
"email": "zhangsan@example.com",
"display_name": "张三",
"avatar_url": null,
"created_at": "2025-01-15T10:30:00Z"
},
"message": "注册成功,请查收验证邮件"
}
2. 获取文章列表(带分页、过滤、排序)
# 请求
GET /v1/posts?status=published&category=tech&page=1&limit=10&sort=-created_at&fields=id,title,author,created_at HTTP/1.1
Host: api.blog-example.com
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
# 响应 200
{
"data": [
{
"id": 101,
"title": "RESTful API 设计指南",
"author": {
"id": 456,
"display_name": "张三"
},
"created_at": "2025-01-15T10:00:00Z"
},
{
"id": 100,
"title": "微服务架构实践",
"author": {
"id": 789,
"display_name": "李四"
},
"created_at": "2025-01-14T15:30:00Z"
}
],
"meta": {
"total": 56,
"page": 1,
"limit": 10,
"total_pages": 6
},
"links": {
"self": "/v1/posts?status=published&page=1&limit=10",
"next": "/v1/posts?status=published&page=2&limit=10",
"last": "/v1/posts?status=published&page=6&limit=10"
}
}
3. 创建文章
# 请求
POST /v1/posts HTTP/1.1
Host: api.blog-example.com
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
{
"title": "深入理解 RESTful API",
"content": "# RESTful API 设计\n\n本文介绍...",
"category_id": 3,
"tags": ["REST", "API", "Web"],
"status": "draft"
}
# 响应 201 Created
Location: /v1/posts/102
{
"data": {
"id": 102,
"title": "深入理解 RESTful API",
"content": "# RESTful API 设计\n\n本文介绍...",
"status": "draft",
"author": { "id": 456, "display_name": "张三" },
"category": { "id": 3, "name": "技术" },
"tags": ["REST", "API", "Web"],
"view_count": 0,
"like_count": 0,
"comment_count": 0,
"created_at": "2025-01-15T11:00:00Z",
"updated_at": "2025-01-15T11:00:00Z"
},
"_links": {
"self": { "href": "/v1/posts/102" },
"publish": { "href": "/v1/posts/102/publish", "method": "POST" },
"comments": { "href": "/v1/posts/102/comments" }
}
}
4. 错误响应示例
# 请求:创建文章时缺少必填字段
POST /v1/posts HTTP/1.1
Content-Type: application/json
Authorization: Bearer token123
{
"content": "没有标题的文章"
}
# 响应 422 Unprocessable Entity
{
"error": {
"code": "VALIDATION_ERROR",
"message": "请求参数验证失败",
"details": [
{
"field": "title",
"rule": "required",
"message": "标题为必填项"
},
{
"field": "category_id",
"rule": "required",
"message": "分类为必填项"
}
],
"request_id": "req_5f8a3b2c",
"timestamp": "2025-01-15T11:05:00Z"
}
}
Node.js + Express 实现示例
const express = require('express');
const app = express();
app.use(express.json());
// 中间件:认证
const authenticate = (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({
error: { code: 'UNAUTHORIZED', message: '请先登录' }
});
}
// 验证 token...
req.user = { id: 456, username: 'zhangsan' };
next();
};
// 获取文章列表
app.get('/v1/posts', (req, res) => {
const { page = 1, limit = 20, status, sort } = req.query;
const offset = (page - 1) * limit;
// 查询数据库...
const posts = db.getPosts({ status, sort, offset, limit });
const total = db.countPosts({ status });
res.json({
data: posts,
meta: {
total,
page: Number(page),
limit: Number(limit),
total_pages: Math.ceil(total / limit)
}
});
});
// 创建文章
app.post('/v1/posts', authenticate, (req, res) => {
const { title, content, category_id } = req.body;
// 验证
if (!title || !content || !category_id) {
return res.status(422).json({
error: {
code: 'VALIDATION_ERROR',
message: '缺少必填字段'
}
});
}
// 创建...
const newPost = db.createPost({
...req.body,
author_id: req.user.id
});
res.status(201)
.location(`/v1/posts/${newPost.id}`)
.json({ data: newPost });
});
// 错误处理中间件
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({
error: {
code: 'INTERNAL_ERROR',
message: '服务器内部错误',
request_id: req.id
}
});
});
app.listen(3000);
三种技术对比
| 特性 | REST | GraphQL | gRPC |
|---|---|---|---|
| 诞生时间 | 2000年 | 2015年 | 2015年 |
| 创建者 | Roy Fielding | ||
| 数据格式 | JSON/XML | JSON | Protocol Buffers |
| 传输协议 | HTTP | HTTP | HTTP/2 |
| 端点数量 | 多个 | 单个 | 多个 |
| 过度获取 | 可能 | 不会 | 不会 |
| 缓存 | 好(HTTP原生) | 较难 | 较难 |
| 学习曲线 | 低 | 中 | 高 |
| 适用场景 | 公开API、简单CRUD | 复杂前端、移动端 | 微服务内部通信 |
| 浏览器支持 | 原生支持 | 需要客户端库 | 需要代理 |
何时选择 REST?
何时选择 GraphQL?
何时选择 gRPC?
💡 混合使用
实际项目中,这三种技术可以组合使用:
Q1: RESTful API 一定要严格遵循所有约束吗?
严格来说,只有满足所有六大约束的才能称为 RESTful。但实际开发中,大多数 "RESTful API" 其实是 "REST-like" 或 "HTTP API"。关键是遵循一致的约定和业界最佳实践,不必过分追求学术定义。
Q2: PUT 和 PATCH 的区别是什么?
PUT 是完整替换资源的所有字段。如果某些字段没传,可能被置空或删除。 PATCH 是部分更新,只修改传入的字段,其他字段保持不变。 类比:PUT 相当于重新粉刷整面墙,PATCH 相当于只修补一个洞。
Q3: 如何处理批量操作?
# 方案1:自定义批量端点
POST /api/users/batch
{
"action": "delete",
"ids": [1, 2, 3, 4, 5]
}
# 方案2:使用 JSON Patch
PATCH /api/users
Content-Type: application/json-patch+json
[
{"op": "remove", "path": "/1"},
{"op": "remove", "path": "/2"}
]
# 方案3:子资源方式
DELETE /api/users?ids=1,2,3,4,5
Q4: 文件上传如何设计?
# 方式1:直接上传到资源端点
POST /api/users/123/avatar
Content-Type: multipart/form-data
file: (binary data)
# 方式2:先上传获取URL,再关联
POST /api/uploads
Content-Type: multipart/form-data
# 返回: { "url": "https://cdn.example.com/images/abc.jpg" }
PUT /api/users/123
{ "avatar_url": "https://cdn.example.com/images/abc.jpg" }
Q5: 幂等性是什么意思?为什么重要?
幂等性意味着无论执行多少次,结果都是相同的。在网络不可靠的情况下(如请求超时但服务器实际已处理),幂等操作可以安全地重试而不会产生副作用。
Q6: 如何处理异步操作?
# 1. 提交异步任务
POST /api/exports
{
"type": "monthly_report",
"date_range": "2025-01"
}
# 响应 202 Accepted
{
"task_id": "task_abc123",
"status": "processing",
"status_url": "/api/tasks/task_abc123"
}
# 2. 轮询任务状态
GET /api/tasks/task_abc123
# 响应
{
"task_id": "task_abc123",
"status": "completed",
"result_url": "/api/exports/task_abc123/download"
}
# 3. 获取结果
GET /api/exports/task_abc123/download
Q7: API 如何处理多语言?
# 使用 Accept-Language 请求头
GET /api/products HTTP/1.1
Accept-Language: zh-CN, zh;q=0.9, en;q=0.8
# 或者使用查询参数
GET /api/products?lang=zh-CN
# Content-Language 响应头
HTTP/1.1 200 OK
Content-Language: zh-CN
Q8: 如何保证 API 的向后兼容性?
Q9: 如何处理 WebSocket 与 REST 的配合?
REST 处理 CRUD 操作,WebSocket 处理实时推送:
# 1. 通过 REST 建立连接并获取 token
POST /api/ws/auth
# 返回: { "ws_token": "abc123", "ws_url": "wss://..." }
# 2. 使用 token 建立 WebSocket 连接
ws://api.example.com/ws?token=abc123
# 3. 订阅事件
{ "action": "subscribe", "channel": "posts", "post_id": 123 }
# 4. 接收实时推送
{ "event": "post.updated", "data": { ... } }
Q10: 微服务架构中如何管理多个 API?
✅ 总结
设计好的 RESTful API 是一门平衡的艺术。它需要在以下维度取得平衡:
始终以开发者体验为中心,持续迭代改进。
😕 没有找到相关内容,请尝试其他关键词