别让你的API变成”薛定谔的接口”——RESTful设计避坑指南

2026-04-12 10 0

做后端开发这么多年,我见过最离谱的事情之一,就是一个团队同时维护着三套风格完全不一样的API:

第一套用GET查、POST改、DELETE删,但返回码永远是200;第二套用POST统天下,GET用来删数据(别问为什么,问就是"安全");第三套——呃,第三套没人能看懂。

这不是故事,这是真实发生的。如果你也是那个被迫接手这些"遗产"的倒霉蛋,你应该懂我在说什么。

今天不吐槽了,咱们来聊聊怎么设计一套"活着的时候能看懂,死了之后别人接手不骂你"的RESTful API。


一、先搞清楚什么是"好的"API

很多人觉得RESTful就是:

  • 用HTTP方法代替CRUD
  • URL里带资源名词
  • 返回JSON

如果你也这么认为,那恭喜你——你已经具备了一个"资深新手"的所有特征。

真正好的API设计,核心只有三个字:可预期

一个前端开发者在完全不了解你后端实现的情况下,能不能通过API的URL、HTTP方法、状态码,就猜到这次请求会干什么、返回什么、成功或失败是什么样子?如果能,你这API就成功了。

就这么简单,但做到的人很少。


二、URL设计:名词是朋友,复数是规矩

URL的本质是什么?是地址。地址的作用是什么?是让人(和机器)能精准定位到一个资源。

所以URL里放名词,不放动词,这是基本常识。但我见过这样的API:

GET /api/getUserInfo
POST /api/deleteUser
PUT /api/updateUserData

这是把URL当成了函数名来用。拜托,HTTP方法本身就是动词啊!

正确的姿势:

GET /users/123           # 获取用户
POST /users              # 创建用户
PUT /users/123           # 更新用户
DELETE /users/123        # 删除用户

资源用复数,这是社区约定俗成的规矩。别跟我杠说"单数也可以",单数没问题,但你得全团队统一。

嵌套资源怎么处理?

如果一个资源天然从属于另一个资源,嵌套是合理的:

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

但记住:嵌套不要太深。超过两级,你就该考虑是不是设计有问题了。三级嵌套以上的URL,读起来就像在读一串乱码:

GET /orgs/1/teams/2/members/3/projects/4/tasks/5

这玩意儿谁维护谁疯。遇到这种情况,老老实实用查询参数:

GET /tasks/5?include=member,project,team,org

三、状态码:别说谎

这是最容易翻车的地方,没有之一。

很多人为了"省事",所有接口一律返回200,然后在body里自己定义一个code字段:

{
  "code": 500,
  "message": "服务器内部错误",
  "data": null
}

我就想问一下:那你还要HTTP状态码干什么?

状态码是HTTP协议给我们的礼物,它让各种中间件(网关、CDN、监控)都能读懂你的响应。如果你不按规矩来,这些基础设施全废了一半。

最常用的状态码清单,背下来:

  • 200 OK — 成功,而且是我预期内的成功
  • 201 Created — 资源创建成功,响应当带Location头
  • 204 No Content — 成功,但返回为空(常用于DELETE)
  • 400 Bad Request — 请求参数有问题,别废话直接说哪有问题
  • 401 Unauthorized — 没认证,去登录
  • 403 Forbidden — 认证了但没权限,别白费力气
  • 404 Not Found — 资源不存在
  • 409 Conflict — 状态冲突,常见于重复创建
  • 422 Unprocessable Entity — 格式对但语义错(比如邮箱格式正确但不存在)
  • 500 Internal Server Error — 服务器挂了,这不是你的错但是我的问题

还有一个很多人不知道的:429 Too Many Requests。做限流的时候记得用,别用200然后在body里说"请求太频繁"——那叫此地无银三百两。


四、错误响应:说人话

错误响应是API的门面,也是最能体现设计者有没有良心的部分。

我见过最敷衍的错误响应长这样:

{
  "error": "Invalid parameter"
}

哪个参数?invalid是什么意思?应该传什么?

正确的错误响应应该长这样:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "请求参数校验失败",
    "details": [
      {
        "field": "email",
        "message": "邮箱格式不正确",
        "rejected_value": "not-an-email"
      },
      {
        "field": "age",
        "message": "年龄必须在18到150之间",
        "rejected_value": 3
      }
    ]
  }
}

你看,前端开发者看到这个,连文档都不用查,直接知道该怎么改。

错误码(code)用业务层面的错误码,不要直接用HTTP状态码。HTTP状态码用来分类,code字段用来精确识别。这是两码事。


五、版本控制:早做早好

你的API不可能永远不变。业务在变,需求在变,一年后的你嘲笑一年前的你是常态。

所以从第一天起就给API加版本号:

GET /v1/users/123
GET /v2/users/123

有人喜欢用Header做版本:

Accept: application/vnd.myapi.v2+json

怎么说呢,能用,但不如URL直观。我个人的偏好是URL版本,简单粗暴,调试方便。

还有一点:同一个版本的API,行为不能变。如果v1的某个接口要改逻辑,那就发v2。旧版本要维持足够长的生命周期,给调用方足够的迁移时间。我一般建议至少维护两个稳定版本。


六、分页:别一股脑全倒出来

这条我必须单独说,因为踩坑的人太多了。

永远不要做一个没有分页的列表接口。永远不要。

你以为"才几千条数据,全返回没问题"——然后某天数据量过了百万,你的服务器内存先替你领了工伤证明。

标准分页方案:

GET /users?page=1&per_page=20

响应里带上元数据:

{
  "data": [...],
  "meta": {
    "current_page": 1,
    "per_page": 20,
    "total": 1347,
    "total_pages": 68
  }
}

另外,分页如果数据集很大,用游标分页(Cursor Pagination)比偏移量分页更靠谱。原因很简单:偏移量分页在数据有新增或删除时,会出现条目重复或遗漏。游标分页不存在这个问题。


七、一个反直觉的建议

很多人以为API设计的关键是"规范"——用什么格式、什么命名、什么结构。

但做了这么多年,我发现最重要的其实是一致性

一个团队用同一套规范,哪怕那套规范不完美,也比每个人各玩各的强一百倍。

所以,去制定你们的API设计规范,不用一开始就完美,但一定要有。有了规范之后,用工具强制检查,lint也好,契约测试也好,总之不能让规范成为纸空文。

推荐几个工具:

  • OpenAPI/Swagger — 写文档,顺便生成代码和测试
  • JSON Schema — 校验请求和响应的结构
  • 好看的错误响应 — 团队里谁敢返回{"code": -1}就请喝奶茶

写在最后

API设计这事儿,说难不难,说简单也不简单。难的地方不在于用什么技术,而在于——团队的沟通、共识、纪律。

一个好的API就像一个好的物业:平时感觉不到它的存在,但你需要什么的时候,它总能给你预期的回应。

而一个糟糕的API呢?就像那个永远不接电话的物业——你只能祈祷别出事儿。

从今天起,做那个让人安心的物业。

有问题欢迎来comck.com找小龙虾聊聊。

相关文章

不想折腾了?让小龙虾帮你一键部署AI神器,省心又省力 🦞
AI厂商画的饼,我替你们全部尝了一遍
连接池:那个让无数人吃亏的隐形性能杀手
MySQL查询慢得像蜗牛?十个有九个是索引的锅
别再把API设计成一坨屎了兄弟:RESTful设计避坑指南
你以为连接池配好了?那些年我踩过的坑,够你吃一壶的了

发布评论