你写的API是不是一坨屎?——10个让后端开发者崩溃的瞬间
干了这么多年后端,我见过太多辣眼睛的API了。有的接口返回格式诡异得像外星语言,有的错误处理敷衍得让人想提刀,有的安全漏洞低级得能让人笑出声。
今天我们就来好好吐槽一下,顺便聊聊怎么写出不至于被人骂娘的API。
1. 路径参数和查询参数分不清?你的API已经开始扣分了
先来个基础题:什么情况下用路径参数,什么情况下用查询参数?
资源ID这种明确的东西,放路径里:
GET /users/123
DELETE /orders/456
需要筛选、排序、分页的,放查询参数里:
GET /users?status=active&sort=created_at
GET /products?category=electronics&min_price=100
看起来很简单对吧?但我见过有人把筛选条件塞进路径里:
GET /getUserByStatus/active ❌ 这什么玩意儿?
GET /getUsers ❌ get是什么鬼,RESTful不是这么玩的
名词用复数,路径里不要出现动词。这是最最基础的规范,我都懒得展开讲,但我确实见过太多人连这个都做不好。
2. 你的请求体验证,是不是也在敷衍了事?
我见过最离谱的请求体验证,大概是这样的:
// 前端:{ "name": "", "age": -5 }
后台:OK,存进去了
还有这样的:
POST /users
请求体:{ "email": "这根本不是邮箱" }
响应:200 OK
{
"message": "success"
}
兄弟,你是认真的吗?这种验证别说保护系统了,连最基本的用户体验都保证不了。正确的做法是:
POST /users
{
"name": "",
"email": "not-an-email",
"age": -5
}
响应:422 Unprocessable Entity
{
"code": "VALIDATION_ERROR",
"message": "请求参数验证失败",
"errors": {
"name": "用户名不能为空",
"email": "邮箱格式不正确",
"age": "年龄必须大于0"
}
}
每个字段的错误信息清清楚楚,接口调试效率直接翻倍。
3. 状态码选对了没?这不是随便选选的事
200 OK走天下?这是很多后端新手的通病。HTTP状态码是有明确语义的,不是随便返回一个数字。
最常见的几类:
- 200:成功,但细分下去201是创建成功,204是无返回内容的成功
- 400:请求参数有问题,不是服务端错误
- 401:未认证,就是"你谁啊"
- 403:已认证但没权限,就是"你谁啊但你不许动"
- 404:资源不存在
- 422:验证失败,格式对但语义错
- 429:请求太频繁,稍后再试
- 500:服务端抽风了,这个真的要尽快修
我见过有人无论什么错误都返回200,然后在body里写个"error": "not found"。这种设计让客户端根本无法区分是业务错误还是网络错误,头疼死调试的人。
4. 分页这个事,说难听点,大部分人做的一塌糊涂
来,看看以下几个场景你有没有中招:
场景A:返回全量数据,让前端自己截
GET /comments?post_id=123
响应:["comment1", "comment2", ..., "comment1000000"]
场景B:用了offset但没有告诉总数
GET /products?offset=100&limit=10
响应:[10条数据]
前端:所以总共多少页?我怎么知道要不要显示"加载更多"?
场景C(最离谱):把分页信息藏在响应结构最深处
响应:{
"data": [...],
"meta": {
"pagination": {
"total": 1000,
"per_page": 10,
"current_page": 11,
"last_page": 100
}
}
}
这嵌套,三级目录,真有你的
一个合理的分页响应应该是这样的:
GET /products?page=2&per_page=20
{
"data": [...],
"meta": {
"total": 158,
"page": 2,
"per_page": 20
},
"links": {
"prev": "/products?page=1",
"next": "/products?page=3"
}
}
简单直接,一目了然。如果你的分页响应让我要猜,那你已经输了。
5. 错误响应能不能走点心?别就返回一个字符串
这是我在生产环境见过的真实响应(脱敏了,但保证真实):
场景1:
响应:400
"error"
场景2:
响应:400
{}
场景3:
响应:400
{
"message": "参数错误"
}
好的,参数错误,哪个参数?错在哪里?为什么?
一个认真设计的错误响应应该是这样的:
{
"code": "INVALID_PARAMETER",
"message": "请求参数不合法",
"detail": "字段 email 的格式不正确,应为 user@example.com 的格式",
"request_id": "req_abc123xyz"
}
code给程序用,方便做错误码枚举和自动化处理。message给开发者看,调试的时候知道发生了什么。detail给终端用户看(需要的话)。request_id让运维定位日志。
每个字段都有它存在的意义,不要偷懒。
6. 认证方式选对了没?Basic Auth在生产环境真的别用了
我知道有人到现在还在用Basic Auth,觉得"能用就行"。能用是能用,但你真的不担心安全问题吗?
认证方式的选择建议:
- 内部服务间调用:API Key,简单直接
- 给第三方用:OAuth 2.0,这是标准
- 用户身份验证:JWT,配合Refresh Token使用
关于JWT,我要特别说几点:
第一,Access Token过期时间要短,15分钟以内比较合理,长了风险大。
第二,Refresh Token要有机制,被盗了要及时发现和撤销。
第三,不要在Token里存敏感信息,Payload是可以被解码的,存个user_id就够了。
// JWT Payload 示例,别存密码之类的东西
{
"sub": "user_123",
"role": "admin",
"exp": 1696300800
}
7. 限流不做,等着被人打挂
先说个真事:我之前公司有个接口没做限流,被某个前端小哥写了个无限循环调用的代码,直接把服务打挂了。从下午三点一直挂到晚上九点,全公司的人在等数据库连接池恢复。
这种低级错误完全可以避免的好吗?
限流的实现方案:
- 固定窗口:简单,但有临界问题
- 滑动窗口:更精确,推荐
- 令牌桶:允许突发流量,推荐用于API
- Redis:分布式环境下必选
限流了还要告诉客户端:
响应头:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1696300800
触发限流时:
响应:429 Too Many Requests
{
"code": "RATE_LIMIT_EXCEEDED",
"message": "请求过于频繁,请稍后再试",
"retry_after": 60
}
8. REST vs GraphQL,先想清楚你真的需要吗
GraphQL刚出来那会儿,一堆人开始疯狂追捧,好像不用GraphQL你的API就落后了一样。
我来泼盆冷水:GraphQL很好,但它解决的是特定场景的问题,不是所有场景都需要它。
适合用GraphQL的场景:
- 数据关系非常复杂,嵌套层级多
- 不同客户端需要完全不同的数据结构
- 客户端需要精确控制返回字段
但对于大多数项目:
- REST更简单直接,新人上手快
- HTTP缓存天然支持,CDN直接用
- 调试方便,Postman/Curl直接调
- API文档工具成熟,Swagger一键生成
我见过太多公司上了GraphQL,然后发现:团队里没人真正理解它,N+1查询问题一堆,性能反而更差了。技术选型要理性,不要追新追热。
9. API设计最核心的东西,其实和技术无关
说了这么多技术细节,我想聊点更本质的。
一个好的API,它的本质是什么?
我觉得就三句话:
- 让调用者用起来舒服
- 出问题了容易排查
- 边界情况处理得体,不会让人抓狂
做到这三点其实挺难的,需要你对业务有深入理解,需要你愿意花时间打磨细节,需要你始终把"开发者体验"放在心上。
我见过太多后端开发者的心态是"接口能跑就行",从来不考虑调用方的感受。这种态度写出来的API,一定是一坨屎,只是早拉晚拉的区别。
10. 几个我日常坚持的习惯,分享给你
关于缓存:我见过两种极端,一种是完全不用缓存导致数据库压力爆炸,一种是滥用缓存导致数据不一致。我的建议是:读多写少的数据果断缓存,缓存key要有规范,TTL要设置合理。
关于日志:每个请求必须带request_id,这个要贯穿整个调用链路。日志格式用JSON,方便后续检索和解析。敏感信息要脱敏,别把密码日志出来。日志级别要正确,别在生产环境打一堆debug日志。
关于数据库连接池:MySQL默认100连接,PostgreSQL默认10连接,这个数字对于现代微服务来说太少了。我建议用PgBouncer管理连接池,max_connections设置为CPU核心数的2-4倍,监控活跃连接数,设置告警阈值。
最后说几句
后端开发没有捷径,都是踩坑踩过来的。你今天看到的优雅设计,背后可能踩过无数个坑才总结出来的。
重要的是,每次被骂的时候,能不能反思一下,下次能不能做得更好。
API设计这条路,没有终点,只有越来越接近理想中的那个样子。
好了,今天的吐槽就到这里。如果你的API正好中了几条,建议你抽空重构一下,不然哪天被人当面吐槽,那场面真的挺尴尬的。
祝你的接口,稳定长寿命。