干过后端的都知道,代码写完只是开始,真正的考验是别人开始用你的 API。一旦 API 设计得像前任的心一样难以捉摸,你将收获成吨的工单、深夜的告警、和产品经理的灵魂拷问。
今天我就来吐槽一下,那些看起来「没什么大不了」但迟早会让你还债的 API 设计问题。纯属实战经验,不吹不黑。
作死一:用 HTTP POST 干所有事情
POST 是万能的——这是多少后端新手的信仰。反正功能能跑就行,管它 GET 还是 POST。
然后你的接口文档就变成了:
POST /getUserInfo
POST /deleteOrder
POST /listProducts
POST /updateConfig
产品经理看了想打人,前端看了想骂娘,调试的时候 curl 都分不清哪个是查哪个是改。
正确的姿势:用 GET 查,用 POST 增,用 PUT/PATCH 改,用 DELETE 删。REST 不是硬性规定,但它是一套让所有人都能猜对你意图的约定。猜都猜不到,设计个锤子?
作死二:分页全靠前端硬撑
「分页?」你说,「让前端自己算 offset 不就行了?」
于是:
GET /orders?page=3&size=20
看起来很合理。然后用户删了一条订单,列表多了一页或者少了一页,用户体验直接裂开。
更离谱的是,当你的订单数据量上了百万级,SELECT OFFSET 1000000 LIMIT 20 直接让你的数据库开始算命——慢查询就此诞生。
正确做法:用游标分页(Cursor-based Pagination),基于上一页最后一条记录的 ID 做起点。查询变成:
GET /orders?after=cursor_id&limit=20
不管数据怎么变,体验永远丝滑,查询永远是主键索引范围扫描,速度起飞。
作死三:错误处理就是返回一个 error 字符串
你的错误响应可能是这样的:
{
"error": "Invalid parameter"
}
然后前端同学对着这个字符串写了一百个 if-else 分支判断,脆弱得像纸糊的。你哪天把文案改成「参数不合法」,整个判断逻辑当场报废。
成熟的错误处理应该长这样:
{
"code": 10001,
"message": "Invalid parameter",
"detail": "order_id must be a positive integer",
"trace_id": "abc123def456"
}
code 是给程序看的,message 是给用户看的,detail 是给调试看的,trace_id 是给运维和排障看的。四件套,缺一不可。
哦对了,HTTP 状态码也要用对。404 不是万能的——没权限是 403,参数错误是 400,服务器炸了是 500。别什么都返回 200 然后在 body 里写 error,这种掩耳盗铃的操作我见过不止一次。
作死四:不做版本管理,改 API 全靠直接上
「这个接口简单,直接改就行了,反正没人用。」
然后在线上跑了半年的接口,被你一个字段改名,直接导致二十个客户端同时崩溃。产品经理在群里发「?」的表情,你心跳开始加速。
API 是契约,改了就要通知所有调用方。所以从第一天起就要有版本意识:
GET /api/v1/users
GET /api/v2/users
或者通过 header 做版本协商。关键是:新旧版本要能共存一段时间,给调用方足够的迁移时间。别学某些团队,v1 还在大量调用的时候直接下线,然后群里一片哀嚎。
作死五:N+1 查询问题视而不见
你写了个列表接口,返回用户和他们的订单:
GET /users → [
{"id": 1, "name": "张三", "orders": [...]},
{"id": 2, "name": "李四", "orders": [...]}
]
实现方式:先查 100 个用户,然后 for 循环里逐个查订单。数据库内心 OS:我谢谢你了,100 次连接,100 次查询,这就是传说中的 N+1 问题。
数据量少的时候感觉不出来,数据量上来之后,数据库 CPU 直接原地升天,你的接口响应时间从 50ms 变成 5s,用户以为网络坏了狂点,你的接口雪崩了。
正确做法:用 JOIN 查询或者批量 IN 查询,把次数压到 1-2 次。数据量真的大的时候,考虑用专门的列表查询接口 + 详情缓存,而不是在列表里嵌套 N 个子查询。
作死六:不做幂等性设计
前端:「支付接口超时了,我重试一下。」
后端:「好的,这是您的第 3 次支付扣款。」
用户:「什么?扣了我三次钱?!」
这不是段子,这是我见过的真实事故。超时重试是网络层面的家常便饭,你的接口必须能扛住。
关键接口(支付、下单、充值)必须做幂等。常用方案:
生成唯一幂等键:client_nonce + user_id + operation_type
存储在 Redis 中:SETnx idempotent:{key} {value} EX 86400
扣款前检查:if exists then return cached_result
简单说就是:同一个操作你执行一次和执行一百次,结果是一样的。做不到这一点,你的接口在生产环境里迟早会爆雷。
作死七:安全意识约等于零
最后一个,也是最严重的一个:安全。
我见过把用户密码明文存在数据库的,见过所有接口都不做权限校验的,见过把管理员 token 直接返给前端的。这些问题轻则用户数据泄露,重则公司直接上新闻。
几个基本的安全规范:
- 所有敏感操作必须校验登录态和权限
- 禁止在 URL 中传递敏感参数(密码、token、身份证号)
- 接口要做频率限制,防刷防爬
- 数据库密码、AK/SK 绝不能硬编码在代码里
- 对外 API 必须有完整的日志记录,方便溯源
很多人觉得「我这小破站没人来攻击」,结果被爬虫薅了一晚上,用户数据打包出现在黑市上。安全这事,平时不烧香,出事哭爹娘。
写在最后
API 设计这事,说难也难,说简单也简单。难在需要提前想清楚业务边界,简单在只要遵循一些基本规范,就能少踩 80% 的坑。
最重要的心态是:API 是给调用方用的,不是给自己炫技用的。不要为了显示自己技术牛逼把接口设计得花里胡哨,多考虑下游同学的体验。
毕竟,写接口的人迟早也要去调用别人的接口。己所不欲,勿施于人,这句话在 API 设计里同样适用。
好了,吐槽完毕。祝大家的接口都稳稳当当,永远不半夜被告警叫醒。