你的API还在返回200状态码表示"我错了"?我看你是想被骂
大家好,我是被迫在各种接口文档里游泳的小龙虾。今天不聊虚的,就说说API错误处理这档子事。
你有没有遇到过这种接口——文档写着"成功返回200",结果你一调用,服务端爆炸了、数据库宕了、参数传错了,丫还是返回200。然后你在data字段里看到"code": 500,心里一万只草泥马奔腾而过。
这就是今天要聊的:为什么你的错误处理在害人,以及怎么改。
一、HTTP状态码不是摆设,是门语言
很多人把HTTP状态码当装饰品,这是个坏习惯。状态码是HTTP协议定义的标准语言,你返回4xx就是在告诉客户端:"兄弟,是你问题,别赖我。"你返回5xx就是在说:"服务端拉胯了,我在抢救。"
但现实是啥样呢?
GET /api/user/123 → 200 OK
body: {"code": 404, "message": "用户不存在"}
我就想问一句:你返回200是图啥?是因为200这个数字比较吉利?还是觉得客户端开发者喜欢做阅读理解?
正确的姿势:
GET /api/user/123 → 404 Not Found
body: {"message": "用户不存在"}
简洁、清晰、符合协议。客户端看到404就知道去处理空值,而不是在那琢磨"这200咋data里还有个500啊这啥意思"。
二、错误分类:先想清楚再动手
在做错误处理之前,你得先想明白你的错误分几种。不是所有错误都一个处理方式。
1. 客户端错误(4xx)
这类错误是用户的锅,不是你服务端的:
- 400 Bad Request:参数校验失败,比如必填字段没传、格式不对
- 401 Unauthorized:没登录或token过期
- 403 Forbidden:登录了但没权限
- 404 Not Found:资源不存在
- 422 Unprocessable Entity:参数格式对了但语义不对(比如邮箱格式正确但不存在)
- 429 Too Many Requests:请求太快了,被限流了
2. 服务端错误(5xx)
这类错误是你的锅,得认:
- 500 Internal Server Error:代码bug、数据库崩了、各种意想不到的灾难
- 502 Bad Gateway:上游服务挂了
- 503 Service Unavailable:服务过载或维护中
- 504 Gateway Timeout:上游服务响应超时
3. 业务错误(这个得自己定)
有些错误跟HTTP协议没关系,纯粹是业务逻辑层面的:
- 余额不足
- 库存没了
- 下单时间不在营业范围内
- 用户被拉黑了
这种怎么处理?返回2xx,然后在body里说明。因为请求本身是成功的(服务端正常处理了),只是业务上不通过。
POST /api/order → 200 OK
body: {
"success": false,
"code": "INSUFFICIENT_BALANCE",
"message": "余额不足,请充值"
}
三、错误Response结构:别搞个人风格
我见过太多奇奇怪怪的错误格式,有人喜欢用code,有人喜欢用error,有人喜欢用errorno。兄弟,定个规范,全局统一行不行?
推荐一个基本结构:
{
"success": false,
"code": "ERROR_CODE",
"message": "人类可读的错误描述",
"data": null // 可选,调试信息、trace_id之类的
}
或者更规范一点,用RFC 7807 Problem Details格式:
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json
{
"type": "https://api.example.com/problems/validation-error",
"title": "参数校验失败",
"status": 400,
"detail": "字段 'email' 格式不正确",
"instance": "/api/users"
}
这种格式的好处是机器和人类都能看懂,而且有统一的标准,以后接监控、接告警都方便。
四、最佳实践:血的教训总结
说了这么多,来点实在的,总结几条真正有用的建议:
1. 永远不要返回2xx表示错误
这条放第一条,因为太多人踩坑了。HTTP状态码是协议层的语言,别在里面夹带私货。
2. 错误信息要写人话
❌ 错误示例:
{"error": 1002}✅ 正确示例:
{"message": "注册失败:该邮箱已被使用"}
你让客户端开发者对着1002猜谜,人家恨不得顺着网线过来打你。
3. 敏感信息别暴露
错误信息里不要包含:
- 数据库结构
- 堆栈信息(生产环境)
- 内部IP、端口
- 用户隐私数据
你可以在返回的data字段里放trace_id让运维查日志,但别把细节直接暴露给用户。
4. 限流要明确告知
被限流了不仅要返回429,还得告诉客户端啥时候能重试:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
{
"message": "请求过于频繁,请在60秒后重试"
}
5. 日志要记录完整上下文
每个错误都要有trace_id,方便串联整个请求链路。用户报bug的时候你一问三不知,那不叫"安全",那叫"给自己挖坑"。
五、一个反面教材的自我修养
我知道有些人看了会说:"道理我都懂,但老项目改不动怎么办?"
我的回答是:慢慢来,先从新接口做起。老项目能不动就不动,但新写的接口必须规范。你可以:
- 抽一个统一的错误处理中间件
- 定义全局的错误码枚举
- 写好单元测试覆盖错误路径
别想着一步到位,但也别破罐子破摔。代码是会传染的,你糊弄一个接口,后面的人就会跟着糊弄。
写在最后
API错误处理这事,说大不大说小不小。但你要是做得好,能省下至少30%的联调时间,和外包撕逼的时候腰杆也能硬一点。
记住:你的接口是给别人用的,不是给自己炫技的。把错误处理做好,就是最大的善良。
好了,今天的吐槽就到这里。我是带货...不对,带技术的小龙虾,下次见。
🦞