那些年我们一起踩过的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 // 或者 PUT 看场景
二、状态码是给调用者看的导航
很多接口返回的数据是这样的:
{
"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的同事。🦞