我见过太多后端写的API,返回格式乱得像打翻了的调色盘。有的接口返回 {code: 0, msg: "success"},下一个接口又返回 {status: "ok", message: "成功"},再下一个直接裸奔 "操作完成"。
作为一个被迫在无数个项目里和这些API共存的后端开发者,今天我要把那些年踩过的坑全倒出来。希望你能少走点弯路,毕竟你的接手者(或者三个月后的你自己)会感谢我的。
一、HTTP状态码:别啥都用200
我知道你懒,但能不能别所有响应都返回200?
HTTP/1.1 200 OK
Content-Type: application/json
{"message": "用户不存在"}
用户不存在,你给我返回200?这是几个意思?200的意思是"大哥,这事儿成了",不是"事儿没成但我还是告诉你一声"。
正确的姿势:
HTTP/1.1 404 Not Found
Content-Type: application/json
{
"error": {
"code": 40401,
"message": "用户不存在",
"field": "user_id"
}
}
常见状态码的正确使用场景:
- 400:客户端参数有问题,比如必填字段缺失、格式错误
- 401:没登录或token过期,不是你的错但你得重新登录
- 403:登录了但没权限,比如普通用户想删管理后台的数据
- 404:资源不存在,你找的那个用户/订单/商品数据库里没有
- 409:冲突了,比如重复提交、版本号不对
- 422:语义错误,参数格式都对但业务上说不通,比如删除已删除的东西
- 429:请求太快了,被限流了,稍后再试
- 500:服务器炸了,这个锅后端背
记住了,200只给"操作成功且有数据返回"的情况。如果没数据,返回个空数组 [] 也比返回 null 强,至少前端好处理。
二、统一的响应结构:这是合同,不能随便改
我见过最离谱的一个项目,12个接口有8种不同的响应格式。接手的时候我感觉自己在玩找不同。
强烈建议所有API统一响应结构:
{
"success": true,
"data": { ... },
"error": null,
"meta": {
"request_id": "req_abc123",
"timestamp": 1725225600
}
}
或者更RESTful一点,把状态码和data分开:
{
"data": { ... },
"meta": {
"code": 200,
"message": "OK",
"request_id": "req_abc123"
}
}
无论哪种方式,整个项目保持一致是底线。你可以选一种,然后写进你们的开发规范里,违反的人请他喝奶茶作为惩罚。
三、错误信息:说人话,别打哑谜
这种错误信息见过没?
{
"code": -1,
"msg": "操作失败"
}
操作失败?什么操作?为啥失败?用户看了想骂人,调试的时候你想扔键盘。
好的错误信息长这样:
{
"error": {
"code": 10003,
"message": "订单金额不能小于0,当前值:-50.00",
"field": "amount",
"help": "https://api.example.com/docs/errors#10003"
}
}
说清楚三件事:什么错了、哪里错了、为什么可能错了。如果有帮助文档链接更好,省得前端同学追着后端问。
四、分页:这对前端同学很重要
如果你的API返回列表数据,不做分页迟早出事。数据量大了OOM、接口超时、前端渲染卡死,一整套套餐给你安排上。
标准分页响应:
{
"data": [ ... ],
"pagination": {
"page": 1,
"page_size": 20,
"total": 1024,
"total_pages": 52,
"has_next": true,
"has_prev": false
}
}
注意,total 字段别省。前端做分页器需要知道总页数,没这个你让前端同学怎么算?
另外,分页参数建议用 page + page_size 而不是 offset + limit。offset分页在数据频繁变动的场景下会有重复和遗漏的问题,page分页更稳定。当然,如果你的列表要支持跳页,page更合适;如果只是下拉加载更多,用cursor分页体验更好。
五、版本控制:别让旧版本突然暴毙
你的API v1跑了两年,突然产品说要改数据结构。你说直接改,行,反正没人反馈问题。然后某天突然一堆老用户炸了——因为他们App半年没更新。
API版本控制的基本素养:
GET /api/v1/users/123
GET /api/v2/users/123
新版本上线后,旧版本至少再维护6-12个月(看你的业务周期)。在旧版本上加deprecation header提醒:
Deprecation: true
Sunset: Thu, 31 Dec 2026 23:59:59 GMT
Link: <https://api.example.com/v2>; rel="successor-version"
让调用方知道该迁移了,也给你自己留条活路。
六、幂等性:这事儿做不好迟早要还
用户网差,连点了两下支付按钮,扣了两次钱。客服电话打爆了。
所有写操作接口(POST、PUT、PATCH、DELETE)都要考虑幂等性。最简单的方案:客户端生成唯一请求ID,服务端做个幂等表:
POST /api/orders
Idempotency-Key: client-generated-uuid-12345
服务端收到请求先查这个key是否处理过,处理过就返回原结果,没处理过就执行并缓存结果。key的有效期根据业务来定,支付类至少24小时。
七、安全:基本功,别丢人
最后说个老生常谈但永远有人踩的:
- 所有接口鉴权,别裸奔
- 敏感数据返回前脱敏,手机号、身份证、银行卡别明文
- 防止SQL注入,参数化查询用起来
- 限流熔断,高并发来了别连数据库一起带走
- 日志别记密码和token,出了事你担不起
总结
好的API设计本质是给别人减负,也给未来的自己减负。统一响应格式、合理用状态码、写清楚错误信息、做好分页和版本控制,这几点做好已经能甩开80%的垃圾API了。
剩下的20%靠经验积累,多踩坑、多看别人踩的坑。希望下次我接手别人代码的时候,少看到一些让人血压飙升的设计。
共勉。