写API一时爽,维护火葬场:我踩过的那些RESTful天坑

2026-04-07 11 0

做了这么多年后端,我有一个特别深刻的体会:写API的时候觉得自己是个艺术家,接口设计行云流水,路径命名优雅至极。等后人接手维护的时候才发现——这哪是艺术,这是行为艺术。

一、URL路径:我看不懂你的浪漫

先说个真实案例。我曾经接手一个项目,看到这么一个接口:

GET /api/v2/internal/user/123/profile/address/primary

我数了一下,7层嵌套。当时我的反应是:写这个接口的人,是不是觉得自己在写诗?而且这首诗还押韵了。

RESTful API的核心理念是什么?资源导向。名词复数形式,路径描述资源层级关系。但是很多人理解偏了,觉得嵌套越深越"REST"。其实嵌套超过2层,你就该考虑是不是设计有问题了。

正确的做法是什么?

GET /users/123/addresses?primary=true
GET /addresses?user_id=123&primary=true

简洁、清晰、一眼看懂。少一点嵌套,多一点真诚。

二、HTTP方法乱用:POST和GET傻傻分不清

这个问题在国内项目里极其普遍。我见过太多人用POST干所有的事情:查列表用POST、分页用POST、搜索用POST。问就是"POST安全"。

安全你个头啊!HTTP方法不是随便选的,它们有语义!

  • GET:读取资源,安全、幂等、可缓存
  • POST:创建资源,不安全、不幂等
  • PUT:完整替换资源,幂等
  • PATCH:部分更新,不一定幂等
  • DELETE:删除资源,幂等

有人会说:"我用POST查数据是因为GET有长度限制,参数太长发不进去。"兄弟,那是你的参数设计有问题。正常查个列表、搜个关键词,需要传几十KB数据吗?

正确的打开方式:

# 查询 - GET
GET /articles?status=published&page=1&per_page=20&category=backend

# 搜索 - GET(带复杂查询条件)
GET /articles/search?q=RESTful&tags=api,design&sort=created_at:desc

# 创建 - POST
POST /articles
{"title": "...", "content": "..."}

# 更新 - PUT/PATCH
PUT /articles/123
{"title": "新标题"}

# 删除 - DELETE
DELETE /articles/123

三、状态码:200 OK走天下

这是我最受不了的。有人写API,所有响应都返回200 OK,然后自己在body里加个code字段表示业务状态。400、401、403、404、500,统统不存在的。

# 这是什么鬼?
{
  "code": 1001,
  "message": "用户不存在",
  "data": null
}

# 正确的是这样的:
HTTP 404 Not Found
{
  "error": "user_not_found",
  "message": "用户不存在",
  "details": {
    "user_id": 123
  }
}

你用HTTP状态码,是把职责还给协议层。前端可以根据状态码统一做错误处理:4xx是客户端错误要提示用户,5xx是服务端错误要上报监控。你搞个code=1001,前端怎么判断这是网络问题还是业务问题?

四、版本管理:v1、v2、v3,版本即人生

接口版本怎么管理?这也是个重灾区。常见的三种方式:

# 1. URL路径(最推荐)
/api/v1/users
/api/v2/users

# 2. Query参数(不推荐)
/api/users?version=1

# 3. Header(过度设计)
Accept: application/vnd.api+json; version=1

URL路径是最直观的方式,调试方便,缓存友好。很多人吐槽说URL包含版本不优雅,但实际上这就是最实用的方案。别为了"优雅"把简单的事情搞复杂。

版本切换的时候要注意什么?

  1. 旧版本要有明确的废弃时间和公告
  2. 新旧版本并行期间,旧的也要同步修复bug
  3. 不要在v1里加新功能然后让v2变成摆设

五、分页:你的limit offset还活着吗?

数据库数据量大了之后,OFFSET分页就是性能杀手。你以为OFFSET 1000000是跳过了100万条?数据库说:"不好意思,我得先数100万零十条,然后扔掉前100万条。"

正确的方式是游标分页(Cursor Pagination):

# 游标分页
GET /articles?limit=20&cursor=eyJpZCI6MTIzfQ

# 响应
{
  "data": [...],
  "pagination": {
    "next_cursor": "eyJpZCI6MTAzfQ",
    "has_more": true
  }
}

游标分页的好处是什么?无论翻到第几页,性能都是稳定的。不像OFFSET,越往后越慢。当然,如果你的产品经理坚持要用"跳到第XX页"功能,那你只能祈祷数据量别太大了。

六、幂等性:你确定你的接口可以重试?

网络是不稳定的,重试是必然的。如果你的接口不支持幂等,重试就会出问题。最典型的例子就是支付接口:

# 没有幂等的扣款接口
POST /orders/123/pay
{"amount": 100}

# 重试一次 = 扣200块
# 用户:???

# 幂等的扣款接口
POST /orders/123/pay
{"amount": 100, "idempotency_key": "order_123_pay_unique_key"}

# 服务端根据idempotency_key做幂等判断
# 重复请求直接返回之前的响应

对于创建类的POST请求,一定要支持幂等key。查询和删除天生幂等,GET和DELETE可以放心重试。PUT和PATCH要确保业务逻辑支持幂等。

七、写在最后

好的API设计不是什么高深莫测的东西,就是把简单的事情做对:

  • 路径清晰,层级不超过2层
  • 方法语义正确,不要用POST干所有事
  • 状态码用对,别200 OK走天下
  • 版本管理实用为主,别为了优雅过度设计
  • 分页要考虑性能,游标优于OFFSET
  • 幂等性是救命稻草,支持重试是基本素养

你要是问我为什么知道这么多坑,那都是血和泪的教训。不过话说回来,每次踩坑都是成长的机会。等你踩完了这些坑,你就是团队里最懂API设计的那个人了。

祝你的API好维护,好维护,真的好维护。

相关文章

连接超时设置成30秒,我收获了一个愤怒的CTO
你的 SQL 为什么慢?数据库不想让你知道的 6 个真相
AI圈最近太热闹了!Gemma 4 开源、Perplexity 翻车、游戏大厂裁 AI 团队…这波资讯有点猛
别让你的API成为同事的噩梦:RESTful设计踩坑实录
你的后端正在被”超时”慢慢杀死:80%的人都在犯同一个致命错误
上线五分钟,排查两小时:你需要分布式链路追踪

发布评论