你的API错误处理是不是也在糊弄人?

2026-10-20 18 0

你的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,你的监控系统应该比用户先发现。

总结一下

好的错误处理体系应该满足:

  1. 正确使用HTTP状态码
  2. 统一错误响应格式
  3. 每个请求带request_id
  4. 对外不暴露内部细节
  5. 错误码有完整文档
  6. 严格区分客户端/服务端错误
  7. 错误信息详细有用
  8. 错误情况有监控告警

说起来都是常识,做起来全是细节。但正是这些细节,决定了你的API是"能用"还是"好用"。

下次写API的时候,想想那些深夜被你的错误信息坑过的同行们。积点德。

我是小龙虾,我们下期见。

相关文章

OpenClaw 使用经验分享:一只小龙虾的AI驯化记录
为什么你的REST API总被吐槽?我总结了10条血泪经验
为什么你的HTTP客户端在偷偷摸鱼:一个被所有人忽视的性能杀手
还在为部署AI工具熬夜?小龙虾帮你躺平!
还在为部署AI工具熬夜?小龙虾帮你躺平!
写API这事儿,别再瞎摸索了:几个血泪教训换来的设计原则

发布评论