2. RESTful API设计
REST(Representational State Transfer)是目前微服务间通信最主流的架构风格。它并非协议,而是一组架构约束条件。遵循这些约束设计的 API 具有可缓存、无状态、统一接口、分层系统等关键特性,能显著提升服务间通信的稳定性与可扩展性。
2.1 REST 六大架构约束
Roy Fielding 在博士论文中定义了 REST 的六大约束,任何声称是 RESTful 的 API 都必须满足以下条件:
| 约束 | 核心要求 | 对安全与架构的影响 |
|---|---|---|
| 客户端-服务器 | 分离用户界面与数据存储 | 服务端可独立演进安全策略,客户端无需关心凭证存储细节 |
| 无状态 | 每个请求包含所有必要信息,服务端不保存会话上下文 | 请求可被任意节点处理,便于水平扩展;每次请求必须携带认证令牌 |
| 可缓存 | 响应隐式或显式标记为可缓存或不可缓存 | 减少重复请求,但需注意敏感数据不应被中间缓存存储 |
| 统一接口 | 资源通过 URI 标识,通过表征操作资源,自描述消息,HATEOAS | 接口标准化后,安全网关可统一实施访问控制与审计 |
| 分层系统 | 客户端无法直接感知是否与端服务器或中间层通信 | 可在中间层嵌入安全网关、负载均衡、API 防火墙 |
| 按需代码(可选) | 服务器可向客户端传输可执行代码(如 JavaScript) | 安全风险较高,企业级架构中通常禁用此约束 |
架构要点: 无状态约束是 REST 安全设计的基石。它强制每次请求携带完整的认证与授权信息(如 JWT),使得服务节点无需共享会话存储,既简化了水平扩展,也降低了会话劫持的攻击面。
2.2 资源命名规范
资源是 REST 的核心抽象。命名规范直接影响 API 的可读性与可维护性。以下是经过大量生产验证的命名规则:
- 使用名词复数形式:
/users、/orders、/invoices,避免使用动词如/getUsers。 - 层级关系用斜杠表示:
/users/{userId}/orders/{orderId},深度不超过三级。 - 使用连字符分隔单词:
/order-items而非/orderItems或/order_items。 - 全部小写: URI 区分大小写,统一小写避免歧义。
- 查询参数用于过滤与排序:
/orders?status=active&sort=created_at,desc。 - 避免在 URI 中暴露操作: 使用 HTTP 方法表达语义,而非
/orders/approve。
命名反模式: /api/v1/getUserOrders(动词+混合大小写)应改为 /v1/users/{userId}/orders(名词+层级+小写)。
2.3 HTTP 方法语义
HTTP 方法对应 CRUD 操作,必须严格遵循语义一致性。错误的方法使用会导致安全网关无法正确实施访问控制。
| 方法 | CRUD 映射 | 语义与幂等性 | 安全注意事项 |
|---|---|---|---|
| GET | 读取 | 安全、幂等;不应改变服务端状态 | 敏感数据不应放在查询参数中(日志泄露风险) |
| POST | 创建 | 非幂等;每次请求创建新资源 | 需防重放攻击,建议使用幂等键(Idempotency-Key) |
| PUT | 整体替换 | 幂等;客户端指定完整资源表示 | 需校验请求体完整性,防止部分字段被覆盖 |
| PATCH | 部分更新 | 非幂等(取决于实现);仅传递差异字段 | 需校验字段白名单,防止未授权字段修改 |
| DELETE | 删除 | 幂等;删除后再次 DELETE 返回 404 | 需二次确认(如软删除 + 硬删除分离) |
安全警告: 切勿使用 GET 方法执行状态变更操作(如 GET /deleteUser?id=1)。这会被爬虫、预加载、浏览器缓存等自动触发,造成数据丢失。所有写操作必须使用 POST、PUT、PATCH 或 DELETE。
2.4 状态码设计
状态码是 API 语义的重要组成部分。合理使用状态码能让客户端和服务端快速对齐错误原因,避免在响应体中重复解析错误信息。
- 2xx 成功:
200 OK— GET、PUT、PATCH 成功201 Created— POST 创建成功,响应头包含Location204 No Content— DELETE 成功,响应体为空
- 3xx 重定向:
301 Moved Permanently— 资源 URI 已变更304 Not Modified— 条件请求,资源未变更(配合 ETag)
- 4xx 客户端错误:
400 Bad Request— 请求体格式错误或参数校验失败401 Unauthorized— 未提供或提供了无效的认证凭证403 Forbidden— 已认证但无权限访问资源404 Not Found— 资源不存在409 Conflict— 资源当前状态冲突(如版本冲突)422 Unprocessable Entity— 语义错误(如业务规则校验失败)429 Too Many Requests— 限流触发
- 5xx 服务端错误:
500 Internal Server Error— 未预期的服务端异常502 Bad Gateway— 上游服务不可用503 Service Unavailable— 服务过载或维护中
设计原则: 永远不要在 2xx 响应体中返回业务错误码。使用 4xx 状态码表达客户端错误,使用 5xx 表达服务端错误。错误响应体应包含 error、message、details 字段,便于客户端定位问题。
2.5 版本控制策略
API 版本控制是向后兼容的基石。错误的版本策略会导致客户端断裂式升级。以下是三种主流策略的对比:
| 策略 | 实现方式 | 优点 | 缺点 |
|---|---|---|---|
| URI 路径版本 | /v1/users、/v2/users |
直观、易于缓存、网关路由简单 | URI 膨胀,版本与资源耦合 |
| 请求头版本 | Accept: application/vnd.myapp.v1+json |
URI 干净,版本与资源解耦 | 调试困难,缓存键需包含头信息 |
| 查询参数版本 | /users?version=1 |
实现简单,客户端灵活切换 | 易被缓存混淆,参数污染 URI 语义 |
推荐策略: 对外部公开 API 优先使用 URI 路径版本(/v1/),因为它对客户端最友好,且便于安全网关按版本实施不同的访问策略。内部服务间通信可使用 请求头版本 保持 URI 整洁。
2.6 综合实践:RESTful API 请求与响应示例
以下是一个符合 REST 约束、命名规范、方法语义和版本策略的完整交互示例:
# 创建订单(POST /v1/orders)
POST /v1/orders HTTP/1.1
Host: api.example.com
Authorization: Bearer <jwt_token>
Content-Type: application/json
Idempotency-Key: 7a1b2c3d-4e5f-6789-abcd-ef0123456789
{
"user_id": "usr_abc123",
"items": [
{ "product_id": "prod_001", "quantity": 2 },
{ "product_id": "prod_002", "quantity": 1 }
],
"shipping_address": "北京市朝阳区..."
}
# 成功响应
HTTP/1.1 201 Created
Location: /v1/orders/ord_987654
Content-Type: application/json
{
"id": "ord_987654",
"status": "pending",
"total_amount": 299.98,
"created_at": "2025-03-21T10:30:00Z"
}
关键实践: 使用 Idempotency-Key 头确保 POST 请求的幂等性;响应中返回 Location 头指向新资源;状态码使用 201 Created 而非 200 OK。这些细节共同构成了生产级 RESTful API 的可靠性基础。