做后端开发久了,什么妖魔鬼怪都见过。有人的接口返回值是个谜,有人的接口文档是科幻小说,有人的接口状态码用200表示「服务器炸了」。今天咱们就来聊聊API设计里那些让人又爱又恨的事儿。
一、状态码:这玩意儿真不是随便写的
先说个真实案例。之前对接过一个第三方支付接口,退款接口返回的状态码是200,意思是「退款申请已提交」。但你不知道这笔钱到底退没退成功。提交了,不代表成功了啊!这是薛定谔的退款——只有你去查才知道结果。
正确的做法是什么?要么返回202 Accepted(表示请求已接受,正在处理),要么就老老实实返回真实的业务状态码。HTTP状态码是给HTTP层用的,业务状态码应该放在响应体里。
// 错误的示范
HTTP 200 OK
{"code": "FAIL", "message": "退款失败"}
// 正确的示范
HTTP 202 Accepted
{"code": "SUCCESS", "message": "退款申请已提交", "refund_id": "RF123456"}
记住这个原则:HTTP状态码表示「这个请求在HTTP层面怎么样了」,业务状态码表示「你让我干的活怎么样了」。别把俩玩意儿混着用。
二、RESTful:是个好东西,但别魔怔了
RESTful API这概念火了十几年了,烂大街了都。很多新人以为只要用GET/POST/PUT/DELETE就算是RESTful了,其实差得远。
但我见过更离谱的是——为了RESTful而RESTful。有个同事为了遵循「统一接口」原则,把用户修改密码做成了PUT /users/{id}/password。听起来挺像那么回事儿是吧?但你想过没有,密码这种敏感操作,GET请求都不应该出现,你PUT过去是打算在URL里明文传输密码?
// 这是什么鬼设计
PUT /users/123/password
Content-Type: application/x-www-form-urlencoded
password=newPassword123
真实场景里,RESTful就是个参考范式,不是圣经。有些接口你就是得用POST,有些操作你就是得打破所谓的「无状态」原则。灵活点,兄弟。
三、错误处理:别让调用方猜谜
我见过最离谱的错误处理是这样的:所有错误都返回200,错误信息藏在message字段里,message内容是「操作失败请联系管理员」。联系管理员?管理员欠你钱吗?
好的错误响应应该长这样:
{
"code": "INSUFFICIENT_BALANCE",
"message": "账户余额不足,当前余额 88.00 元,单笔消费 150.00 元",
"details": {
"current_balance": 88.00,
"required_amount": 150.00,
"shortage": 62.00
},
"request_id": "req_abc123xyz",
"help": "https://api.example.com/docs/errors#INSUFFICIENT_BALANCE"
}
你看,调用方拿到这个错误,立刻知道怎么回事儿,不需要任何猜测。错误码要精确,错误信息要说人话,附带必要的数据让调用方能做决策。
四、版本管理:别让老代码成为你的噩梦
接口版本这事儿,很多人不重视,觉得改个返回值结构无所谓。结果某天线上突然就开始报错了,一查才发现:上周你改了用户信息的返回结构,把phone字段从字符串改成了对象,但老用户App还在按字符串解析,直接崩了。
版本管理的策略就那么几种:
// 路径版本(最直观)
GET /api/v1/users/123
GET /api/v2/users/123
// Header版本(看着专业但调试麻烦)
GET /api/users/123
Api-Version: 2024-01-01
// Query参数版本(最不推荐)
GET /api/users/123?version=2
我的建议是路径版本,简单直接。调用方想用哪个版本,URL里写得清清楚楚。而且这种设计天然支持灰度发布和A/B测试。
五、分页:这事儿说简单也简单,说复杂也复杂
分页接口你们肯定写过。但你注意过这些问题吗?
第一,页码从0开始还是从1开始?这个没有标准答案,但要写清楚,而且要一致。我见过一个项目,前5个接口页码从0开始,后3个从1开始,调用方每天都在debug。
第二,当数据量大的时候,OFFSET分页会越来越慢。1000万条数据,OFFSET 9999990,这种查询能跑几十秒。解决方案是游标分页(Cursor Pagination),基于ID或时间戳做条件查询。
// OFFSET分页(适合小数据量)
GET /api/articles?page=1&page_size=20
// 游标分页(适合大数据量)
GET /api/articles?cursor=eyJpZCI6IjEwMDAwMCIsImxpIjogIjIwMjQtMDEtMDEifQ&page_size=20
// 返回
{
"data": [...],
"pagination": {
"next_cursor": "eyJpZCI6IjEwMDIwIiwibGkiOiAiMjAyNC0wMS0wMiJ9",
"has_more": true
}
}
六、安全:别让自己的接口变成后门
这条本来想展开写,但感觉一两段说不清楚,就提几个点:
- 所有接口都要有权限校验,别觉得内部接口就可以裸奔
- 敏感数据要加密存储,密码要加盐哈希
- 请求要有频率限制,防止被人刷接口
- CORS配置要合理,别*满天飞
- 请求ID要记录,方便排查问题和溯源
最后说两句
写接口这事,技术含量不高,但做好很难。什么叫好的API?调用方用起来舒服,出错了能快速定位,扩展新功能不需要改老代码。这就够了。
别追求什么「最优雅的设计」「最符合标准的实现」,追求「最实用、最少出bug、最方便维护」。代码是写给人看的,顺眼比啥都强。
祝大家的接口都稳稳当当,永不炸机。