引言

大家好,我是码农刚子。在过去几年里,GraphQL、gRPC、tRPC 等技术层出不穷,但在大多数业务系统中,REST 仍然是最常见、最容易落地、最容易协作的接口风格。原因很简单:REST 足够朴素——它基于 HTTP,不要求客户端和服务端共享复杂协议,前端、移动端、后端、测试、运维都能理解它。

但也正因为 REST 看起来简单,很多项目最后会写成“披着 HTTP 外衣的随意 RPC”:所有接口都是 POST /doSomething,状态码永远是 200,错误都塞在 message 里。短期能跑,长期则难以维护。

本文将从 RESTful 的核心原则出发,系统讲解 URI 设计、HTTP 方法规范、状态码使用、版本控制、安全认证等关键环节,帮助开发者构建规范、可维护的 RESTful API。

一、REST 的核心原则

REST(Representational State Transfer,表述性状态转移)由 Roy Fielding 博士在其论文中提出,其核心在于六个架构约束:

  1. 客户端-服务器架构:客户端与服务器各自独立开发与演进
  2. 无状态性:每个请求必须包含所有必要信息,服务器不保存客户端状态
  3. 可缓存性:支持 HTTP 缓存机制,提升性能
  4. 统一接口:通过标准化的 HTTP 方法操作资源
  5. 分层系统:支持代理、网关等中间层,提升可扩展性
  6. 按需代码(可选):服务器可向客户端返回可执行代码

其中,资源导向无状态通信是 RESTful API 设计中最核心的两个原则。

二、URI 设计:让资源一目了然

2.1 使用名词复数形式

RESTful API 的核心在于将系统功能抽象为可操作的资源(Resources),每个资源通过唯一 URI 标识。资源名称应使用名词复数形式,避免动词。

✅ 正确:
GET    /users          # 获取用户列表
GET    /users/123      # 获取单个用户
POST   /users          # 创建用户
PUT    /users/123      # 更新用户
DELETE /users/123      # 删除用户

❌ 错误:
GET    /getUserList
POST   /createUser
POST   /updateUser

2.2 层级结构表达关联关系

使用 /resource/{id}/subresource 表示资源间的关联:

GET /users/123/orders        # 获取用户 123 的所有订单
GET /users/123/orders/456    # 获取用户 123 的订单 456

2.3 查询参数用于过滤、排序和分页

复杂的查询条件不应嵌入 URI 路径,而应使用查询参数:

GET /products?category=electronics&page=2&size=20&sort=price,desc

2.4 命名规范要点

  • 使用小写字母,单词间用**连字符(-)**分隔
  • 避免使用下划线(_)或驼峰命名法
  • 保持命名简洁且一致

三、HTTP 方法:用对动词,让接口自解释

RESTful API 通过标准 HTTP 方法明确操作类型。以下是各方法的语义规范:

HTTP 各方法的语义规范图示

幂等性是指:同一个请求执行一次和执行多次,最终结果一致。例如 DELETE /orders/1001 执行一次是删除订单,再执行一次仍然是“订单不存在”,最终状态没有变化,因此它是幂等的。而 POST /orders 每执行一次都可能创建一个新订单,所以不是幂等的。

实践建议

  • 避免用 POST 替代 PUT/DELETE,这会破坏语义清晰性
  • 部分更新优先使用 PATCH 而非 PUT
  • 批量操作可通过 POST /api/batch 实现,但需明确文档说明

四、状态码:让 HTTP 协议发挥应有作用

很多系统喜欢这样返回:

{
  "code": 500,
  "message": "server error",
  "data": null
}

但 HTTP 状态码却永远是 200 OK。这种做法对调试、网关、监控、SDK 都不友好。

更合理的方式是:让 HTTP 状态码表达协议层结果,让响应 body 表达业务细节。

4.1 常用状态码分类

常用状态码分类表

关键原则:永远不要在参数错误时返回 500,也不要在成功时返回 200 但 body 里塞个 code: 500

五、响应体设计

5.1 成功响应

{
  "data": {
    "id": 123,
    "username": "coder log",
    "email": "manonggangzi@coderlog.net"
  },
  "meta": {
    "timestamp": "2026-06-29T12:00:00Z"
  }
}

5.2 错误响应

{
  "error": {
    "code": "INVALID_INPUT",
    "message": "Email format is invalid",
    "details": [
      {
        "field": "email",
        "issue": "must be a valid email address"
      }
    ]
  }
}
  • 成功响应包含 datameta 字段
  • 错误响应采用统一错误码体系,包含详细的错误定位信息
  • 字段命名建议采用小写蛇形命名法(如 user_id

六、版本控制:让 API 平滑演进

API 会随着业务发展而演进,新版本必须与旧版本共存,以防止破坏现有的集成。

6.1 常见版本控制策略

策略 示例 优缺点
URI 路径 /v1/users/v2/users 最直观,易于理解和调试
请求头 Accept: application/vnd.api+json;version=1 保持 URI 干净,但对客户端不够直观
查询参数 /users?version=1 简单但不符合 REST 语义

推荐使用 URI 路径方式,因为其最直观,且便于通过浏览器、curl 等工具直接测试。

6.2 语义化版本

  • 主版本号(v1 → v2):破坏性变更
  • 次版本号:向后兼容的功能增强
  • 补丁版本号:向后兼容的错误修复

七、安全认证

7.1 认证方案

现代 API 已淘汰简单的 HTTP Basic 认证,普遍采用:

  • JWT(JSON Web Token) :无状态、可扩展,适合分布式系统
  • OAuth 2.0:适合第三方授权场景

7.2 最佳实践

  • Token 通过 Authorization: Bearer <token> Header 传递,严禁放在 URL 参数中(会被记录在服务器日志和浏览器历史中)
  • 设置严格的过期时间(访问令牌建议不超过 1 小时)
  • 永远不要在 JWT payload 中存储敏感数据
  • 敏感接口必须实现认证,关键操作(如支付、删除)需增加二次确认机制
  • 始终使用 HTTPS 加密通信

八、分页、过滤与排序

对于可能返回大量数据的集合接口,应从一开始就支持服务端分页。

8.1 分页参数

GET /orders?page=2&size=20

或使用 limit/offset 方式:

GET /orders?limit=20&offset=40

8.2 过滤与排序

GET /orders?status=paid&createdSince=2026-01-01&sort=-createdAt
  • 过滤:field=value 形式,如 status=paid
  • 排序:sort=field 升序,sort=-field 降序

九、API 文档

9.1 使用 OpenAPI 规范

OpenAPI 规范(原 Swagger)是 RESTful API 文档的行业标准。一个标准的 OpenAPI 文档应包含:

  • 接口路径与 HTTP 方法
  • 请求/响应参数示例
  • 状态码说明
  • 调用频率限制
  • 示例代码(cURL、Python、JavaScript 等)

9.2 文档的价值

采用 OpenAPI 规范可使接口联调周期缩短 40% 以上。通过 Swagger UI 等工具,可以将 YAML/JSON 格式的规范文件渲染为可交互的 Web 文档,前端、后端、测试人员可以实时校验接口设计。

十、常见设计陷阱与避坑指南

❌ 陷阱一:所有接口都用 POST

POST /getOrderList
POST /createOrder
POST /updateOrderStatus
POST /deleteOrder

问题:HTTP Method 失去语义,接口少时问题不大,一旦系统变复杂,命名会越来越混乱。

正确做法:用 HTTP 方法表达动作,用 URI 表达资源。

❌ 陷阱二:状态码永远 200

问题:对调试、网关、监控、缓存都不友好。

正确做法:HTTP 状态码表达协议层结果,响应 body 表达业务细节。

❌ 陷阱三:业务动作硬拧成资源操作

PUT /orders/1001   # 试图用 PUT 完成“支付订单”

问题:有些动作很难完全资源化。

正确做法:将动作建模为子资源:

POST /orders/1001/payment
POST /orders/1001/cancellation
POST /approval-tasks/2001/submission

工程设计要讲语义,也要讲可读性,不要为了追求形式上的 REST 而牺牲可读性。

结语

REST 之所以经久不衰,不是因为它在技术上最前沿,而是因为它足够朴素、足够通用。一个好的 RESTful API 设计,不需要复杂的框架和工具链——一个接口文档写清楚 URL、Method、参数、返回值和状态码,团队就能顺畅协作。

REST 之稳,不在复杂,而在朴素与坚持。遵循本文梳理的设计原则和最佳实践,相信你能构建出清晰、一致、易于维护的 RESTful API。