2. RESTful API设计

REST(Representational State Transfer)是目前微服务间通信最主流的架构风格。它并非协议,而是一组架构约束条件。遵循这些约束设计的 API 具有可缓存、无状态、统一接口、分层系统等关键特性,能显著提升服务间通信的稳定性与可扩展性。

2.1 REST 六大架构约束

Roy Fielding 在博士论文中定义了 REST 的六大约束,任何声称是 RESTful 的 API 都必须满足以下条件:

约束 核心要求 对安全与架构的影响
客户端-服务器 分离用户界面与数据存储 服务端可独立演进安全策略,客户端无需关心凭证存储细节
无状态 每个请求包含所有必要信息,服务端不保存会话上下文 请求可被任意节点处理,便于水平扩展;每次请求必须携带认证令牌
可缓存 响应隐式或显式标记为可缓存或不可缓存 减少重复请求,但需注意敏感数据不应被中间缓存存储
统一接口 资源通过 URI 标识,通过表征操作资源,自描述消息,HATEOAS 接口标准化后,安全网关可统一实施访问控制与审计
分层系统 客户端无法直接感知是否与端服务器或中间层通信 可在中间层嵌入安全网关、负载均衡、API 防火墙
按需代码(可选) 服务器可向客户端传输可执行代码(如 JavaScript) 安全风险较高,企业级架构中通常禁用此约束

架构要点: 无状态约束是 REST 安全设计的基石。它强制每次请求携带完整的认证与授权信息(如 JWT),使得服务节点无需共享会话存储,既简化了水平扩展,也降低了会话劫持的攻击面。

REST 架构约束与安全分层关系 REST 架构约束与安全分层关系 客户端 / 消费者 安全网关(认证 / 授权 / 限流) 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 创建成功,响应头包含 Location
    • 204 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 整洁。

API 版本控制策略对比 API 版本控制策略对比 URI 路径版本 GET /v1/users GET /v2/users ✅ 直观、易缓存、易路由 请求头版本 Accept: app/vnd.myapp.v1+json Accept: app/vnd.myapp.v2+json ⚠️ 调试复杂、缓存键需定制 查询参数版本 GET /users?version=1 GET /users?version=2 ❌ 缓存混淆、语义污染 推荐:外部 API 用 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 的可靠性基础。