那些年我们一起踩过的API设计坑

2026-08-23 10 0

那些年我们一起踩过的API设计坑

做后端开发这些年,我见过太多"能跑就行"的API。它们像野草一样野蛮生长,最后变成一团谁都不敢动的屎山。今天我来聊聊那些真正值得注意的API设计问题,都是血泪教训。


一、HTTP方法不是装饰品

见过最多的骚操作是什么?统统POST走天下。查个用户信息用POST,删个订单用POST,修改密码还是POST。问就是"POST最稳"。

拜托,HTTP方法是有语义的:

  • GET — 读取资源,幂等,安全
  • POST — 创建资源
  • PUT — 完整替换资源,幂等
  • PATCH — 部分更新资源
  • DELETE — 删除资源,幂等

幂等意思是:你调一次和调一百次,效果一样。GET读一百遍不会多扣你钱,DELETE删一百次也不会多删什么东西。但POST不一样,你转账操作调两次试试?

// 错误示范
POST /api/getUser
POST /api/deleteOrder?id=123
POST /api/updateUser

// 正确姿势
GET /api/users/123
DELETE /api/orders/123
PATCH /api/users/123

二、状态码是给调用者看的导航

很多接口返回的数据是这样的:

{
  "code": 200,
  "message": "success",
  "data": { ... }
}

看起来挺正常?但问题来了:HTTP状态码是200,但实际业务可能是"余额不足"、"权限不够"、"参数错误"。你用200表示一切OK,然后让调用者去看你自定义的code字段判断业务状态?

这是把HTTP状态码当空气。正确做法:

// 业务错误也要用对应的HTTP状态码
400 Bad Request        // 参数校验失败
401 Unauthorized       // 没登录
403 Forbidden          // 没权限
404 Not Found          // 资源不存在
422 Unprocessable Entity // 业务校验失败(如余额不足)
500 Internal Server Error // 服务器挂了

然后你的响应体里再放业务层面的code和message,这样调用者可以快速判断是网络问题、权限问题还是业务问题,不用一个个解析你的自定义错误码。


三、分页不是你想怎么分就怎么分

最常见的三种分页方式:

1. offset分页(最常见也最坑)

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

问题:当数据量大的时候,翻到第100页,数据库要跳过前1999条记录,效率极低。而且你翻页的时候数据可能变了,新增一条记录,你就重复看到某条数据。

2. 游标分页(推荐)

GET /api/users?cursor=eyJpZCI6MTIzfQ&limit=20

基于主键或排序字段的游标,数据库直接定位到起始位置,不管翻多少页效率都稳定。不存在数据不一致问题。

3. 快照分页

如果你的数据不允许翻页过程中变化(比如导出报表),用时间戳快照,缺点是占用资源。

我的建议:默认用游标分页,简单列表展示用offset也不是不行,但要知道它的局限性。


四、版本号不是用来炫技的

/api/v1/users/api/v2/users,版本号满天飞。

版本管理的核心目的是什么?让新旧客户端共存。当你升级了接口,老客户端还能用。

但很多团队的做法是:v1有了问题,直接废弃,强制所有客户端升级。这不是版本管理,这是给自己挖坑。

正确的版本策略:

  • 一个接口稳定后,尽量不要破坏性修改,新增字段、新增接口都行,别删别改已有字段的语义
  • 确实需要大改,用版本号,但保持老版本足够长时间的维护
  • URL版本号是最直观的,但header版本号(如Accept: application/vnd.api+json; version=2)更RESTful

五、错误信息要像个正常人

见过最离谱的错误返回:

{
  "error": "操作失败"
}

操作失败?什么操作?什么失败了?是数据库挂了还是参数错了?还是你代码写bug了?

好的错误信息应该包含:

{
  "error": {
    "code": "INSUFFICIENT_BALANCE",
    "message": "账户余额不足,当前余额 58.5 元,所需 100 元",
    "field": "amount",
    "request_id": "req_abc123"
  }
}

这样调用者知道:问题是什么(code)、人话解释(message)、问题出在哪个字段(field方便前端聚焦)、排查用的请求ID(request_id)。

用户看到"余额不足",比看到"操作失败"不知道好到哪里去了。


六、REST不是银弹

很多人把REST当成万能解药,恨不得把所有接口都设计成RESTful。追求100%的REST规范,最后搞出来一堆不伦不类的接口。

现实点:

  • 简单的CRUD操作用REST,很合适
  • 复杂的业务操作、跨多个资源的操作,用RPC风格也挺好
  • 实时性要求高的,用WebSocket
  • 文件上传,用multipart/form-data,别硬塞JSON
// 复杂的业务操作,RPC风格反而更清晰
POST /api/orders/123/cancel
POST /api/orders/123/refund
POST /api/users/batch_import

接口是给人用的,不是给规范用的。


最后说几句

API设计看起来是技术活,其实本质是产品思维:你设计的接口,别人用起来爽不爽?好不好理解?出问题了好不好排查?

那些"能用就行"的借口,最后都会变成技术债务。代码能重构,接口一旦发布,动一发而伤全身。

所以从一开始就认真设计,值得的。

以上,都是被坑出来的经验。如果觉得有用,转发给你那个写接口只会用POST的同事。🦞

相关文章

别再自己折腾了,让我帮你一键部署 AI 工具 🚀(¥39起)
那些年我们一起踩过的API设计坑
删库跑路?不,是连接池炸了——一次MySQL超时事故复盘
你的API错误处理,可能连小学生都不如
别再被SQL卡脖子了——一个增删改查选手的索引觉醒之路
让部署成为一种享受,而不是一场噩梦 🦞

发布评论