第3章 设计RESTful API的最佳实践

3.1 为什么需要一套API设计规范?

说实话,我早年刚接手后端项目时,团队里连个统一的API风格都没有。有人用/getUser,有人用/users/1,还有人搞/api/v1/queryUser?id=1。前端同学对接接口基本靠问,文档写出来都认不全。这种混乱,本质上就是缺少一套RESTful的设计规范。

RESTful API不是银弹,但它提供了一套清晰的语言。资源用名词,动作用HTTP方法,状态用标准状态码。团队一聊就懂,前端收到响应就能处理。我在项目中见过很多团队因为API设计不统一,后期联调耗费了大量精力。所以从第一行代码开始,我建议你定好规则。

核心原则:把API当作资源系统的门面来设计。路径只表示资源,动作交给HTTP动词。

3.2 资源命名与URL结构

URL是API的名片。你想想看,一个阅读体验好的API,路径一眼就能看懂它在干什么。

  • 使用名词复数:/users、/orders,而不是/getUser或/user。
  • 层级结构用斜杠:/users/1/orders表示用户1的订单列表。
  • 避免动词:不需要/createOrder,直接用POST /orders。
  • 用连字符代替下划线:/order-items比/order_items更友好。
  • 查询参数用于过滤和排序:不要塞进路径里,比如/users?status=active&sort=created_at。
常见命名对比
不推荐推荐说明
/getUser?id=1/users/1HTTP GET 已包含“获取”语义
/createOrderPOST /ordersPOST 就是创建
/deleteUser/1DELETE /users/1DELETE 就是删除
/updateUser/1PUT /users/1PUT 或 PATCH 用于更新

我个人习惯在路径中全部小写,用连字符分隔单词。比如/system-logs,而不是/systemLogs。这样在大小写敏感的服务器上少踩坑。

3.3 HTTP方法与幂等性

HTTP方法不是随便选的。它们背后有幂等性的逻辑。你想想看,如果前端因为网络重试发出了两次POST /orders,结果生成了两个订单,这谁能忍?

  • GET:查询,幂等。多次调用结果不变。
  • POST:创建,不幂等。每次调用都可能产生新资源。
  • PUT:完整替换,幂等。客户端发全量数据。
  • PATCH:部分更新,不幂等(取决于实现)。
  • DELETE:删除,幂等。第一次返回200,第二次返回404,但资源状态已变。
# 示例:用户订单API
GET    /users/1/orders          # 获取用户1的订单列表
POST   /users/1/orders          # 为用户1创建新订单
GET    /users/1/orders/5        # 获取订单5详情
PUT    /users/1/orders/5        # 完整替换订单5
PATCH  /users/1/orders/5        # 部分更新订单5
DELETE /users/1/orders/5        # 删除订单5

我曾经在一个项目里,团队用POST做幂等的资源替换,导致前端重试后数据被覆盖了多次。后来我们统一改用PUT,并在请求头加上Idempotency-Key,才解决了这个问题。

3.4 状态码的正确使用

很多新手喜欢所有成功都返回200,错误全部返回500。这样前端得自己解析body才知道具体发生了什么——其实完全没必要。

  • 200 OK:GET、PUT、PATCH成功。
  • 201 Created:POST成功创建资源。
  • 204 No Content:DELETE成功,或不需要返回body的操作。
  • 400 Bad Request:参数非法、格式错误。
  • 401 Unauthorized:未认证或认证过期。
  • 403 Forbidden:已认证但无权限。
  • 404 Not Found:资源不存在。
  • 409 Conflict:资源冲突(如重复创建、版本冲突)。
  • 422 Unprocessable Entity:语义错误,比如校验失败。
  • 429 Too Many Requests:限流。
  • 500 Internal Server Error:服务器内部错误,不要滥用。
常见场景状态码速查
操作成功失败
GET /users200 + 用户列表400/401/500
POST /users201 + 新用户数据400/409/422
DELETE /users/1204 无内容401/404/500

状态码是API的语义边界。前端只需要看状态码就能决定走哪个分支,不需要解析body里的error code。这样客户端代码会简洁很多。

3.5 版本管理

API上线后一定会变。不加版本管理,升级就是灾难。我见过直接把旧路由改掉,结果客户端全部报404的惨案。

  • URL路径版本:/v1/users、/v2/users。最直观,方便调试。
  • 请求头版本:Accept: application/vnd.myapi.v2+json。更干净,但调试工具未必好设。
  • 查询参数版本:/users?version=2。容易与普通参数混淆,不推荐。

我个人偏爱URL路径版本。虽然有人觉得URL里带版本号不RESTful,但它对前端和客户端最友好。打开浏览器就能测试,curl也方便。

# URL路径版本示例
GET /v1/users          # 老版接口
GET /v2/users          # 新版接口,可能新增字段或改变行为

3.6 错误响应体统一

错误发生时,只返回一个状态码是不够的。前端需要知道哪里错了、怎么改。我建议统一一个错误结构:

{
  "error": {
    "code": "USER_NOT_FOUND",
    "message": "未找到该用户",
    "details": "用户ID: 42 不存在",
    "timestamp": "2025-03-25T10:30:00Z"
  }
}
  • code:机器可读的错误码,客户端用来做逻辑分支。
  • message:人类可读的错误描述,可显示给用户。
  • details:调试信息,生产环境可酌情隐藏。
  • timestamp:便于追踪问题发生时间。

记住:错误响应也要遵循相同的Content-Type。别在成功时返回JSON,失败时返回纯文本,前端解析会炸。

3.7 分页、过滤与排序

列表接口如果不用分页,一次返回10万条数据,服务端和客户端都扛不住。我在项目中习惯用一种设计:

  • 分页:使用?page=2&per_page=20,返回中附带总条数和总页数。
  • 过滤:使用查询参数,如?status=active。
  • 排序:使用?sort=created_at或?sort=-created_at(负号表示降序)。
// 响应示例
{
  "data": [...],
  "pagination": {
    "page": 2,
    "per_page": 20,
    "total": 150,
    "total_pages": 8
  }
}

3.8 认证与授权

RESTful API通常无状态,所以认证信息得放在每个请求里。常见的做法:

  • Bearer :放在Authorization: Bearer <>头里。
  • :放在Header或查询参数中,适合服务间调用。
  • OAuth 2.0:适合第三方授权。

千万别把密码直接放在URL或body里。我之前接手过一个老项目,他们用/login?user=admin&pass=123456,我这个汗啊……

警告:不要在GET请求的body中放认证信息。GET请求可能被浏览器缓存、被日志记录,直接泄漏。

RESTful API设计核心知识体系 RESTful API规范 资源命名与URL HTTP方法 & 幂等性 状态码规范 版本管理 统一错误响应 分页 / 过滤 / 排序 认证与授权 Bearer / OAuth 核心 保持一致性

3.9 总结一下

设计RESTful API没有的对错,但遵守一套共识能让你少踩很多坑。我从资源命名、HTTP方法、状态码、版本管理、错误响应、分页认证几个维度讲了核心要点。你把这些规则定好,写进团队的API规范文档里,联调效率至少提升一倍。

说白了,好的API设计是让调用方感觉“这接口就该这么用”。用最短的时间讲清楚业务,用最少的字段完成任务,这就是我的追求。