2. API设计原则:RESTful设计核心原则、资源命名规范、HTTP方法语义与状态码选择

RESTful API 设计并非一套僵化的协议,而是一组基于 Web 架构风格的约束与最佳实践。本章聚焦于三个核心维度:资源建模(将业务实体抽象为资源)、统一接口(通过标准 HTTP 方法操作资源)以及无状态通信(每个请求包含完整上下文)。掌握这些原则是构建可维护、可演进 API 的基础。

2.1 RESTful 设计核心原则

REST(Representational State Transfer)由 Roy Fielding 在其博士论文中提出,其核心约束包括:

  • 资源标识:每个资源(如用户、订单)通过唯一的 URI 标识。
  • 通过表征操作资源:客户端通过资源的表征(如 JSON、XML)与服务端交互。
  • 自描述消息:每个请求和响应包含足够的信息(如媒体类型、缓存指令)以理解如何处理。
  • 超媒体作为应用状态引擎(HATEOAS):响应中包含链接,引导客户端发现后续操作。
实践要点:在工程落地中,前三条原则应作为硬性约束严格执行;HATEOAS 可根据团队成熟度逐步引入,不必强求一步到位。

下图展示了 RESTful 架构中客户端、资源与表征之间的核心交互流程:

RESTful 核心交互流程 客户端 资源标识 (URI) 资源表征 (JSON) GET /users/123 返回 自描述消息 HATEOAS 链接 核心约束 • 无状态通信 • 统一接口 • 缓存能力 • 分层系统

2.2 资源命名规范

资源命名是 API 设计的“第一印象”,直接影响可读性和可发现性。以下是经过大量工程验证的命名规范:

规范类别 推荐做法 反例 说明
名词复数 /users, /orders /getUser, /userList 资源集合使用复数名词,避免动词
小写字母 /api/v2/products /API/V2/Products 统一小写,避免大小写敏感问题
连字符分隔 /order-items /order_items 或 /orderItems 使用连字符 - 提升可读性
层级关系 /users/{userId}/orders /getOrdersByUser?uid=123 通过路径层级表达资源从属关系
查询参数过滤 /orders?status=active /orders/active 筛选条件放在查询参数,而非路径
常见陷阱:避免在 URI 中使用动词(如 /createUser),操作语义应由 HTTP 方法表达。同时,避免过深的嵌套层级(超过三级),这会增加客户端耦合度。

2.3 HTTP 方法语义

HTTP 方法定义了客户端对资源期望执行的操作。RESTful API 应严格遵循每种方法的语义:

HTTP 方法 CRUD 映射 幂等性 安全性 请求体 响应体
GET 读取 (Read) 是 是 无 资源表征
POST 创建 (Create) 否 否 资源表征 新资源表征或操作结果
PUT 全量替换 (Update) 是 否 完整资源表征 更新后的资源表征
PATCH 部分更新 (Partial Update) 否 否 差异描述(如 JSON Patch) 更新后的资源表征
DELETE 删除 (Delete) 是 否 无 通常为空
关键区分:PUT 要求客户端发送完整的资源状态,服务端用此状态完全替换现有资源;PATCH 仅发送需要变更的字段。误用 PUT 进行部分更新是常见的设计缺陷。

2.4 状态码选择

状态码是服务端向客户端传达操作结果的“第一语言”。选择正确的状态码能显著提升 API 的自描述能力。以下是 RESTful API 中最常用的状态码分类:

分类 状态码 含义 典型场景
2xx 成功 200 OK 请求成功 GET 获取资源、PUT 更新成功
201 Created 资源创建成功 POST 创建新资源,响应应包含 Location 头
204 No Content 操作成功,无返回内容 DELETE 删除成功、某些 PUT/PATCH 操作
4xx 客户端错误 400 Bad Request 请求格式错误或参数无效 JSON 解析失败、必填字段缺失
401 Unauthorized 未提供身份认证或认证失败 缺少 Token 或 Token 过期
403 Forbidden 已认证但无权限 普通用户尝试访问管理员接口
404 Not Found 资源不存在 请求的 URI 对应的资源未找到
5xx 服务端错误 500 Internal Server Error 服务端内部错误 数据库连接失败、未捕获异常
503 Service Unavailable 服务暂时不可用 系统过载、维护中
状态码选择原则:优先使用标准状态码,避免自定义状态码。对于业务逻辑错误(如余额不足),使用 400 Bad Request 或 422 Unprocessable Entity,并在响应体中提供详细的错误信息,而非滥用 200 OK 包裹错误。

下图展示了 API 请求从发起到响应的完整状态码决策流程:

HTTP 状态码决策流程 客户端发起请求 认证检查 401 权限检查 403 业务逻辑处理 2xx 4xx 服务端异常 → 5xx

2.5 综合示例:用户资源 API

以下示例综合展示了资源命名、HTTP 方法和状态码的协同使用:

# 创建用户
POST /api/v2/users
Content-Type: application/json
{
  "name": "张三",
  "email": "zhangsan@example.com"
}
→ 201 Created
Location: /api/v2/users/12345

# 获取用户列表
GET /api/v2/users?page=1&size=20
→ 200 OK
{
  "data": [...],
  "total": 100,
  "page": 1
}

# 获取单个用户
GET /api/v2/users/12345
→ 200 OK
{
  "id": "12345",
  "name": "张三",
  "email": "zhangsan@example.com"
}

# 全量更新用户
PUT /api/v2/users/12345
Content-Type: application/json
{
  "name": "李四",
  "email": "lisi@example.com"
}
→ 200 OK

# 部分更新用户邮箱
PATCH /api/v2/users/12345
Content-Type: application/json
{
  "email": "newemail@example.com"
}
→ 200 OK

# 删除用户
DELETE /api/v2/users/12345
→ 204 No Content

# 权限不足示例
GET /api/v2/admin/settings
→ 403 Forbidden
{
  "error": "FORBIDDEN",
  "message": "当前用户无管理员权限"
}

# 资源不存在示例
GET /api/v2/users/99999
→ 404 Not Found
{
  "error": "NOT_FOUND",
  "message": "用户 99999 不存在"
}
设计检查清单:每次设计新 API 端点时,对照以下问题自检:
1. URI 是否使用名词复数且全小写?
2. HTTP 方法是否准确表达了操作语义?
3. 成功时是否返回了正确的 2xx 状态码?
4. 错误时是否返回了对应的 4xx/5xx 状态码并附带了可读的错误信息?
5. 是否避免了在 URI 中使用动词或查询参数表达操作?