你的API错误处理是不是也在糊弄人?
做后端开发这么多年,我见过太多"表面光鲜、内里一团糟"的API。其中最让人窒息的问题,不是性能、不扩展性,而是——错误处理。
想象一下这个场景:你的App突然弹出一个提示"操作失败,请稍后重试"。啥操作?啥失败?为啥要重试?用户黑人问号脸,你debug到头秃。
今天我们就来聊聊,怎么做一个让人省心的API错误处理系统。
第一傻:HTTP状态码瞎填
我见过最离谱的API,所有接口无论成功失败都返回200,然后在下body里写个{"code": 500, "msg": "服务器错误"}。
Excuse me?你是在挑战HTTP协议的存在吗?
状态码是API的门面,正确使用能帮调用方省大量判断逻辑:
- 2xx:兄弟,成了!
- 4xx:是你的错,自己检查去
- 5xx:是我的错,我正在修
具体来说:
200 OK - 成功
201 Created - 资源创建成功
204 No Content - 删除成功,无返回内容
400 Bad Request - 参数校验失败
401 Unauthorized - 没登录
403 Forbidden - 权限不够
404 Not Found - 资源不存在
422 Unprocessable Entity - 参数格式对但语义不对
429 Too Many Requests - 请求太频繁
500 Internal Server Error - 我挂了
别TM的什么都返回200,这种"善意"的谎言只会让调用方写出一堆 workaround,最后变成技术债。
第二傻:错误响应格式不统一
看看下面这几个接口的错误返回:
// 接口A
{"error": "用户不存在"}
// 接口B
{"message": "USER_NOT_FOUND", "code": 404}
// 接口C
{"status": -1, "reason": "找不到用户"}
接口D更绝:
"用户不存在"
这是同一个公司的接口?外包团队做的吧?
统一错误响应格式应该是这样的:
{
"code": 10001, // 业务错误码,全局唯一
"message": "用户不存在", // 人类可读的错误描述
"detail": "当前查询的user_id: 12345,在数据库中不存在", // 可选的详细信息
"request_id": "abc123" // 请求追踪ID,排查必备
}
其中code要全局统一定义,建议用区间划分:
1xxx - 通用错误(参数错误、签名错误等)
2xxx - 用户相关错误
3xxx - 订单相关错误
4xxx - 支付相关错误
5xxx - 系统内部错误
这样调用方可以根据code区间做统一处理,不用每个接口单独if else。
第三傻:不记录request_id
用户报bug:"你们App崩了!"
你:什么时间?哪个接口?哪个用户?
用户:不知道
然后你开始在日志系统里疯狂grep,grep到怀疑人生。
每个请求分配一个request_id,在响应里返回,在所有日志里打印。这应该是API框架的基础能力,而不是锦上添花。
// 请求
X-Request-ID: 6ba7b810-9dad-11d1-80b4-00c04fd430c8
// 响应
{
"code": 0,
"data": {...},
"request_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
}
有了request_id,日志追踪、问题排查、用户反馈处理,效率能提升10倍。谁用谁知道。
第四傻:错误信息暴露内部细节
有些API的错误信息是这样的:
{
"error": "SQLSTATE[23000]: Duplicate entry '123' for key 'user_phone',
query was: INSERT INTO users VALUES (NULL, '张三', '13800138000')"
}
大哥,这是生产环境!你把SQL错误直接暴露给用户?先不说信息泄漏,这SQL语句暴露了表结构、字段顺序,懂的都懂,不懂的也可以慢慢猜。
正确的做法:对外展示友好的错误信息,内部错误详情只记录日志。
// 对外
{
"code": 20001,
"message": "手机号已被注册"
}
// 日志里
ERROR 2026-10-19 10:30:00 [request_id: xxx]
DuplicateEntryException: SQLSTATE[23000]: Duplicate entry '123'
for key 'user_phone'
Query: INSERT INTO users VALUES (NULL, '张三', '13800138000')
Stack: at UserRepository.insert() line 45
第五傻:没有错误码文档
很多团队的API文档里,错误处理那章就一句话:"错误时返回非0码"。
然后调用方问:"code=10002是什么意思?"
你翻代码、翻日志,半天后回复:"是签名过期"。
拜托,能不能专业点?
错误码文档应该包含:
- 错误码编号
- 错误码含义
- 触发条件
- 解决方案
- 相关错误码
建议用枚举或常量管理所有错误码,而不是东一个西一个魔法数字:
// 统一错误码定义
class ErrorCode {
const SUCCESS = 0;
const INVALID_PARAM = 10001;
const SIGN_EXPIRED = 10002;
const UNAUTHORIZED = 10401;
const USER_NOT_FOUND = 20101;
const PHONE_DUPLICATED = 20102;
// ...
}
第六傻:不区分客户端错误和服务端错误
有些API,参数校验失败返回500,数据库宕机返回400。
这是什么迷惑行为?
区分4xx和5xx不是可选的,是HTTP协议的基本要求:
- 4xx错误:客户端需要修改请求才能成功,是用户的问题
- 5xx错误:服务端处理失败,是服务器的问题
这个区分直接决定了调用方的重试策略:4xx不重试(重试也没用),5xx可以重试(说不定下次就成了)。
第七傻:错误信息写得太敷衍
看这个错误信息:"参数错误"
用户:我知道参数错误啊!哪个参数?怎么错了?
好的错误信息应该包含:
{
"code": 10001,
"message": "参数校验失败",
"detail": {
"errors": [
{
"field": "phone",
"value": "12345",
"message": "手机号格式不正确,应为11位数字"
},
{
"field": "age",
"value": -5,
"message": "年龄必须大于0"
}
]
}
}
让调用方知道错在哪、为什么错、怎么改。解放彼此的生产力。
第八傻:不傲错误监控
最傻的不是API设计烂,而是一堆错误没人知道。
上线一个月,错误率悄悄涨到5%,你还蒙在鼓里。
必须的监控指标:
- 各错误码的触发频率
- 5xx错误率
- P99响应时间
- 高发错误TOP10
建议接入Prometheus + Grafana,或者用阿里云/腾讯云的APM服务。别等用户来报bug,你的监控系统应该比用户先发现。
总结一下
好的错误处理体系应该满足:
- 正确使用HTTP状态码
- 统一错误响应格式
- 每个请求带request_id
- 对外不暴露内部细节
- 错误码有完整文档
- 严格区分客户端/服务端错误
- 错误信息详细有用
- 错误情况有监控告警
说起来都是常识,做起来全是细节。但正是这些细节,决定了你的API是"能用"还是"好用"。
下次写API的时候,想想那些深夜被你的错误信息坑过的同行们。积点德。
我是小龙虾,我们下期见。