3. API 设计规范:从命名到响应,把规矩立在前头

API 设计这事儿,说白了就是定规矩。规矩定得好,前后端吵架就少;规矩定得烂,联调的时候你就等着哭吧。我做了这么多年 API,见过太多「随缘设计」的项目——URL 一会儿用复数一会儿用单数,状态码永远返回 200 然后 body 里塞个 error 字段。嗯,这种项目最后无一例外都重构了。

这一章我们就把 API 设计的几个核心规范掰开揉碎讲清楚。你照着做,至少能保证你的接口看起来像个正经系统。

3.1 URI 命名规范:让 URL 自己会说话

一个好的 URI,应该像一份清晰的快递单——收件人、物品、操作一目了然。我个人习惯遵循以下几个原则:

  • 全小写 + 连字符:/user-orders 而不是 /userOrders 或 /user_orders。大小写敏感带来的坑我踩过不止一次。
  • 用名词复数表示资源:/users、/orders、/products。动词应该交给 HTTP 方法去表达。
  • 层级关系用斜杠:/users/{id}/orders 表示「某个用户的订单」,别搞成 /getUserOrders 这种 RPC 风格。
  • 查询参数用于过滤/排序/分页:/orders?status=paid&page=1&size=20,别把过滤条件塞进路径里。

推荐 vs 不推荐

推荐不推荐原因
/users/{id}/getUserById动词冗余,资源语义不清晰
/orders?status=paid/orders/paid过滤条件不应作为路径层级
/user-addresses/userAddresses驼峰在 URL 中可读性差

我的小习惯:设计 URI 时先画资源关系图。比如「用户有订单,订单有商品」,对应的 URI 就是 /users/{uid}/orders/{oid}/items/{iid}。层级别超过三级,超过就该考虑拆分接口了。

3.2 请求方法选择:GET 不是万能的

我记得刚带团队那会儿,有个同事所有接口都用 POST,理由是「GET 有长度限制」。我当时就笑了——长度限制是表象,语义混乱才是大问题。

HTTP 方法的选择其实很简单,记住这张表就够了:

方法语义幂等安全典型场景
GET查询资源是是获取用户列表、查询订单详情
POST创建资源 / 提交数据否否创建用户、提交订单
PUT全量替换资源是否更新用户全部信息
PATCH部分更新资源否否修改用户手机号
DELETE删除资源是否删除用户、取消订单

避坑指南:我曾经在一个项目中看到有人用 GET 方法删除资源,理由是「前端方便」。结果爬虫一跑,数据库差点被清空。记住:有副作用的操作,永远不要用 GET。

为什么会这样?因为 GET 请求会被浏览器缓存、会被预加载、会被搜索引擎索引。你想想看,如果删除操作能被缓存,那后果有多可怕。

3.3 状态码设计:别让调用方猜谜

状态码是 API 和调用方之间的「暗号」。暗号要对得上,双方才不累。我见过最离谱的设计是——所有请求都返回 200,然后在 body 里放一个 {"code": 500, "message": "服务器炸了"}。这不是脱裤子放屁吗?

HTTP 状态码分五大类,我们实际开发中常用的就这几个:

  • 2xx 成功:
    • 200 OK:GET、PUT、PATCH 成功
    • 201 Created:POST 创建成功,记得在响应头里返回 Location
    • 204 No Content:DELETE 成功,或者更新操作不需要返回 body
  • 3xx 重定向:API 里用得少,但 301/302 在资源迁移时有用
  • 4xx 客户端错误:
    • 400 Bad Request:参数校验失败,请求格式不对
    • 401 Unauthorized:没登录或 过期
    • 403 Forbidden:登录了但没权限
    • 404 Not Found:资源不存在
    • 409 Conflict:资源冲突,比如重复创建
    • 422 Unprocessable Entity:语义错误,比如邮箱格式不对
  • 5xx 服务端错误:
    • 500 Internal Server Error:未捕获的异常
    • 502 Bad Gateway:网关或代理层问题
    • 503 Service Unavailable:服务过载或维护中

状态码使用铁律:

  1. 不要用 200 包装错误,该 4xx 就 4xx,该 5xx 就 5xx
  2. 不要滥用 500,参数校验失败返回 400 而不是 500
  3. 不要返回 200 + 空 body 表示删除成功,用 204

3.4 请求与响应结构设计:统一就是最好的设计

API 的请求和响应结构,最怕的就是「百花齐放」。同一个系统里,有的接口返回 {"data": {...}},有的返回 {"result": {...}},还有的直接裸返回数组。调用方每次都要看文档才知道怎么取数据,你说累不累?

我个人习惯用一套统一的响应结构:

{
  "code": 0,           // 业务状态码,0 表示成功
  "message": "success", // 可读的消息
  "data": {            // 真正的业务数据
    "id": 123,
    "name": "张三"
  },
  "traceId": "abc-123" // 链路追踪 ID,排查问题用
}

为什么不用 HTTP 状态码直接表示业务状态?因为有些场景 HTTP 状态码不够用。比如「订单已支付,不能取消」,HTTP 层面是 200,但业务层面需要告诉调用方「操作不允许」。这时候业务状态码就派上用场了。

请求结构建议:

  • GET 请求:参数放 query string,复杂过滤条件用 q 参数传 JSON
  • POST/PUT/PATCH:body 统一用 JSON,不要混用 form-data 和 JSON
  • 分页参数统一:page(从 1 开始)、size(默认 20,最大 100)

3.5 知识体系总览

下面这张图把本章的核心知识点串起来了。你照着这个框架去设计 API,基本不会出大问题。

API 设计规范知识体系 API 设计规范 URI 命名规范 请求方法选择 状态码设计 请求/响应结构 全小写 + 连字符 名词复数 + 层级 统一 JSON 格式 业务状态码 2xx/4xx/5xx 💡 核心原则 资源驱动设计 · 语义化命名 · 统一错误格式 · 幂等性保障 · 版本控制

3.6 版本控制:给 API 留条后路

API 一旦上线,就不可能永远不变。但你不能让调用方跟着你一起改。所以版本控制是必须的。

我推荐两种方式:

  • URL 路径版本:/v1/users、/v2/users。最直观,调用方一眼就知道用哪个版本。
  • 请求头版本:Accept: application/vnd.myapp.v1+json。更优雅,但调用方需要额外配置。

我曾经踩过的坑:有个项目用 query 参数传版本号 ?version=1,结果缓存系统把 v1 和 v2 的响应混在一起了。从那以后我再也不用 query 参数做版本控制。

版本号从 v1 开始,不要搞什么 v0.1、v1.0.0。小版本迭代用兼容性设计,大版本变更才升版本号。记住:向后兼容是 API 设计者的基本修养。