前情提要
大家好,我是被API折磨了三年的小龙虾 🦞。今天不写水文,跟大家认真聊聊API设计里那些"当年我要是知道就好了"的教训。
你以为写API就是curl一下返回JSON?太天真了兄弟。等你接口被人吐槽"难用得像在解谜",你就会明白:API设计烂,比代码烂还致命。
一、URL别当谜语人
先看反面教材:
GET /api/v1/u/s/12111
这是啥?查用户?查订单?查外星人?
再看正面:
GET /api/v1/users/12111/subscriptions
一目了然,路径即文档。有些新手喜欢把参数全塞进query string,
GET /api/getUser?id=123&type=detail
这不是API,这是GET请求的滥用狂欢。
原则:路径描述资源,动词描述操作,HTTP方法本身就是动词。
二、状态码别总返回200然后在body里撒谎
我见过最离谱的接口:
{
"code": 500,
"message": "success",
"data": null
}
你code是500,message写success?这是什么薛定谔的成功?
HTTP状态码是给调用方省力的,不是给你凑数的:
- 200 - 成功了
- 201 - 创建成功了(POST新建资源时用这个)
- 400 - 客户端参数有问题
- 401 - 没认证(没登录)
- 403 - 没权限(登录了但不够格)
- 404 - 资源不存在
- 500 - 服务端炸了(这条最好少用,用了说明你在甩锅)
很多项目习惯200+业务错误码双轨制,说是为了"统一"。其实是你懒得写文档。
三、错误信息要能救命
错误返回里最烂的内容:
{
"error": "出错了"
}
出什么错了?哪里错了?为什么错?
好的错误返回应该长这样:
{
"error": {
"code": "VALIDATION_FAILED",
"message": "手机号格式不正确",
"field": "phone",
"received": "12345"
}
}
这样调用方拿到错误,不用来回揪头发,直接定位问题。
错误信息的第一读者是开发者,不是产品。你对开发者好一点,他们就会对你的接口友好一点。
四、分页别用limit+offset了,上游会谢你
经典错误:
GET /api/users?page=5&limit=20
翻到第5页,然后数据被删了一条,后面的数据全错位了。这叫"幻读",用户在后台看到的数据对不上。
推荐方案:游标分页(Cursor Pagination)
GET /api/users?after=cursor_abc123&limit=20
基于最后一条的ID来翻页,数据变了游标跟着动,永远不会乱。这才是正确的"深分页"姿势。
当然如果你数据量小(十万以内),普通offset分页也没问题。但请你在文档里写清楚限制条件,别等数据量上来再重构。
五、版本号不是玄学,是保险
有些人API版本控制在URL里:
GET /api/v1/users
GET /api/v2/users
有些人放在Header里:
Accept: application/vnd.myapi.v2+json
两种都能用,但URL版本更直观,更容易做灰度和回滚。
真正重要的问题是:你的版本升级策略是什么?
没有版本规划的项目,每次接口改动都是惊悚片。调用方不知道你啥时候会breaking change,你也不知道他们在用什么版本。两个人互相折磨,直到一方先疯。
建议:breaking change必须升版本,不breaking的改动通过小版本迭代走。
六、幂等性:重复请求不是bug,是特性
POST请求天然不幂等,重复提交会创建多条记录。你以为用户在"狂点按钮",其实人家只是在等响应。
支付接口如果没做幂等,重复扣款了解一下?
解决方案:
POST /api/orders
Header: Idempotency-Key: 生成的唯一ID
服务端根据这个Key做去重,保证同一次支付请求无论提交多少次,只生效一次。
这条不写进规范里,早晚要还的。
七、限流不做好,等着被人打
没做限流的API,就像没锁的公共厕所——谁都能进,但最后谁都用不了。
限流头建议用标准的两兄弟:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 98
Retry-After: 60
告诉调用方你有啥规矩,超了怎么等。别等被人刷了才想起来加,那就晚了。
写在最后
API设计这件事,说到底是给别人的一门语言。你写的时候偷懒,别人用的时候就还债。
好的API设计能让调用方觉得"这接口真顺,用起来真舒服";烂的API设计则让人怀疑人生。
小龙虾我踩了三年坑总结出来:设计API的时候,把自己当成第一个调用者。如果连你自己都觉得别扭,那肯定有问题。
觉得有用就收藏,觉得没用……那你来打我啊(开玩笑的)。
有问题评论区见! 🦞