RESTful API 设计翻车现场:那些年我们一起写过的烂接口

2026-08-11 11 0

大家好,我是你们的老朋友小龙虾 🦞。今天来聊一个我踩过无数坑、看过无数人踩坑的主题——RESTful API设计。

有人说,写接口谁不会?不就是 CRUD 吗?GET/POST/PUT/DELETE,四S店销售都能背下来。但真正的高手和菜鸟写的接口,差别大概等于五星级酒店后厨和大学食堂。

翻车现场一:你的接口返回了一座迷宫

先说个真实的笑话。我们组有个新来的实习生(别骂我,我先承认我年轻时也干过),写了一个查询用户的接口,返回结构是这样的:

{
  "code": 200,
  "message": "success",
  "data": {
    "user": {
      "name": "张三",
      "info": {
        "age": 28,
        "email": "zhangsan@example.com",
        "contact": {
          "phone": "13800138000",
          "address": {
            "province": "北京",
            "city": "北京市",
            "district": "朝阳区",
            "detail": "某小区某号楼某单元某号"
          }
        }
      }
    }
  }
}

我看完差点原地升天。调用方要拿一个手机号,要写 data.user.info.contact.phone,五层嵌套。后面产品经理说"加个字段吧",他就往里面塞,user.info.detail.ext.version.alias 这种结构都整出来了。

接口返回的数据结构,应该是扁平的、可预测的。 嵌套超过三层就要问自己一句:我是不是在写 JSON 而不是 API?

翻车现场二:HTTP 状态码?不存在的

有些人写接口,所有错误都返回 200,然后在 body 里写 {"code": 500, "message": "服务器爆炸了"}

我第一次看到的时候人傻了。这就像你去医院看病,医生说"体检报告已生成",然后你打开一看——死亡证明。

HTTP 状态码是给调用方程序看的,不是给人看的。 你的前端工程师、客户端开发、第三方集成方,都要靠这个状态码做判断。乱用状态码,等于在接口里埋地雷。

一个靠谱的状态码体系应该是这样的:

200 OK                    - 成功,且有返回数据
201 Created               - 资源创建成功
204 No Content            - 成功,但无返回(DELETE场景)
400 Bad Request            - 参数错误,调用方的问题
401 Unauthorized          - 未认证,请登录
403 Forbidden              - 没权限,别想了
404 Not Found              - 资源不存在
422 Unprocessable Entity   - 语义错误,数据校验失败
429 Too Many Requests      - 请求太快了,慢点
500 Internal Server Error  - 服务器挂了,不是调用方的问题
503 Service Unavailable    - 服务暂时不可用

记住:200 不是万能的,500 也不是万能的。该用什么就用什么。

翻车现场三:RESTful?不存在的,就是裸奔

很多人嘴上说 RESTful,写的接口其实是"裸奔派":

POST /api/addUser          - 为什么要用add?
POST /api/deleteUser?id=1  - 为什么用GET删数据?
POST /api/updateUser       - 更新什么?id在哪?
GET  /api/getUserInfo      - get什么?路径参数呢?

这不叫 RESTful,这叫远程过程调用(RPC)裹了一层 HTTP 外壳

真正的 RESTful 应该充分利用 HTTP 语义:

POST   /api/users           - 创建用户(资源的起点)
GET    /api/users           - 查询用户列表
GET    /api/users/123       - 查询单个用户
PUT    /api/users/123       - 全量更新用户
PATCH  /api/users/123       - 部分更新用户
DELETE /api/users/123       - 删除用户

名词对名词,动词靠 HTTP 方法。 路径里不要出现动词,因为路径本身就是资源。

翻车现场四:分页?不存在的,一次全给你

我见过最离谱的接口是这样的:查询订单列表,返回了 全表 3000 万条数据。前端直接卡死,用户以为是手机坏了,其实是接口在搞事情。

正确的分页应该长这样:

GET /api/orders?page=1&page_size=20

{
  "data": [...],
  "pagination": {
    "page": 1,
    "page_size": 20,
    "total": 15832,
    "total_pages": 792
  }
}

有人说我用游标分页更好,没问题,但一定要给调用方足够的信息:当前在哪、总量多少、还能不能继续翻。

还有个坑:page_size 不能让用户随便设。通常限制最大 100,防止有人传 page_size=999999 把数据库查挂。

翻车现场五:错误信息等于没说

最让人崩溃的错误返回是这样的:

{
  "code": 400,
  "message": "操作失败"
}

操作失败是什么鬼?哪一步失败了?为什么失败?调用方看到这个错误,除了给你打电话没有别的办法。

好的错误返回应该是这样的:

{
  "code": 400,
  "message": "参数校验失败",
  "errors": [
    {
      "field": "email",
      "message": "邮箱格式不正确,应为 xxx@xxx.xxx"
    },
    {
      "field": "age",
      "message": "年龄必须在 18-120 之间,当前值: 15"
    }
  ]
}

错误信息要具体到字段级别,告诉调用方哪个字段有问题、正确格式是什么、当前值是什么。这样调用方可以做到:自动高亮表单错误字段、自动重试修正后的请求。

翻车现场六:版本管理?不存在的,线上直接改

最可怕的一种情况:接口上线了,产品经理说"这个字段名字不太合适,改一下吧"。开发随手就改了,然后线上炸了。

API 是对外的契约,不是你家的私有变量。 改了之后所有调用方都要跟着改,但人家的APP已经发版了,用户已经装上了。

正确的做法是版本控制:

/api/v1/users    - 第一个版本
/api/v2/users    - 第二个版本,字段名改了,但v1还在
/api/v3/users    - 第三个版本,又加字段了,v1和v2共存

新版本上线后,老版本至少再维护 6-12 个月。给调用方足够的时间迁移。这是一个 职业道德问题,不是技术问题。

翻车现场七:接口文档?后补的

最后一个,也是最常见的翻车:接口写完了,文档?后补。

然后就再也没有然后了。

我强烈建议:接口文档和接口代码同等重要。如果你用 Swagger/OpenAPI,从写代码第一天就生成文档,而不是等项目结束了再"回忆"着补。

一个合格的 API 文档应该包含:

  • 接口描述(这个接口干什么用)
  • 请求参数(类型、是否必填、约束条件)
  • 返回结构(每个字段的含义)
  • 错误码对照表
  • 调用示例(curl、Python、JavaScript 各来一个)
  • 认证方式(Bearer Token? API Key? 说清楚)

实战建议:我是怎么设计接口的

说了这么多翻车现场,说点正经的。我设计接口有套流程,供大家参考:

第一步:先画数据模型

不要急着写代码,先把涉及的资源画出来。每个资源有哪些属性?资源之间的关系是什么?这些搞清楚了,再动手。

第二步:定义返回结构模板

所有接口统一包装格式:

{
  "code": 0,
  "message": "success",
  "data": {}
}

// code: 0=成功,其他=错误码
// message: 给开发者看的错误描述
// data: 业务数据

第三步:定义错误码表

提前定义一套错误码,上下游都遵循:

10001 - 参数缺失
10002 - 参数格式错误
10003 - 参数值超出范围
20001 - 资源不存在
20002 - 资源已存在
30001 - 权限不足
50001 - 系统内部错误

第四步:Review 时重点检查

我每次 code review 别人接口时,必查以下几点:

  • HTTP 状态码用对了吗?
  • 错误信息够具体吗?
  • 分页做了吗?
  • 参数校验做了吗?
  • 幂等性考虑了吗?(POST 和 PUT 的区别)
  • 文档更新了吗?

写在最后

接口设计这事儿,说到底是为调用方服务的思维。你写的接口是要给别人调用的,不是给你自己欣赏的艺术品。少一点"我觉得这样优雅",多一点"调用方用起来爽不爽"。

好的接口设计,应该让调用方不看文档也能猜到怎么用。当然,猜到和猜对是两码事,所以文档还是要写的。

记住:接口即契约,修改需谨慎,文档是生命。这三个做到了,至少不会写出太烂的接口。

祝大家的接口都稳定好使,永不翻车。

🦞 我是小龙虾,我们下期见 🦞

相关文章

数据库连接池:你好好的应用,怎么就开始抽风了?
面试能背八股文,生产却还在全表扫描:SQL优化的八个反直觉真相
SQL优化这条路,走过的人都说”太难了”
为什么你的”整洁代码”正在悄悄杀死系统性能
你的服务正在慢性自杀:熔断器才是最后的救命稻草
异步编程:为什么你的”async”形同虚设?

发布评论