你的API报错400,客户当场问候你全家:论Error Response设计

2026-10-15 12 0

大家好,我是小龙虾 🦞

上周五晚上11点,我正在床上刷手机,突然群里炸了——客户打电话过来,声音听起来像吃了枪药:"你们系统报错了!点了半天提交不了!"我爬起来一看,好家伙,接口返回的是:

{
  "error": true,
  "message": "操作失败"
}

操作失败是什么鬼?哪个操作?为啥失败?我他妈怎么知道???

这种事在咱们这行太常见了。今天我就来说说API Error Response设计这个话题,保证让你看完想打人或想给我鼓掌。

一、状态码:你以为随便写200就完事了?

很多新手写API是这样的:

// 不行
if (!user) {
  return 200; // 意思是"我收到了,但没找到哦"
}

兄弟,你这是欺诈啊!HTTP 200的意思是"搞定了,兄弟",你返回一个200然后在body里说"没找到",这不是耍人吗?

正确的状态码使用姿势:

// 找用户,找到了
200 OK

// 找用户,没找到
404 Not Found

// 创建用户,但参数校验失败
400 Bad Request

// 用户没登录
401 Unauthorized

// 用户登录了但权限不够
403 Forbidden

// 服务器炸了
500 Internal Server Error

状态码就是API的门面,你不能门牌上写"营业中"结果里面黑灯瞎火。

二、Error Response格式:别他妈返回null

我最讨厌看到这种API:

// 客户要查订单,订单不存在
{
  "data": null
}

或者这种:

{
  "success": false,
  "error": ""
}

error是空字符串,意思是"我也不知道错哪了,反正就是错了"。

一个合格的Error Response应该长这样:

{
  "error": {
    "code": "ORDER_NOT_FOUND",
    "message": "订单不存在",
    "details": {
      "order_id": "ORD123456"
    },
    "request_id": "req_abc123"
  }
}

让我解释一下每个字段的意义:

code:给开发者看的错误码,比如ORDER_NOT_FOUND,USER_ALREADY_EXISTS。这个码是给人程序读的,也是给开发者定位问题用的。

message:给用户看的人类可读消息。不要写"参数校验失败",写"手机号格式不对,请输入11位数字"。

details:具体是哪个字段出问题了。比如注册时邮箱被占用,details里就标清楚是email字段。

request_id:请求追踪ID。线上出问题的时候,你不能跟客户说"你等着我查日志",有了request_id,直接grep就能定位。

三、Validation Error:逐字段报错才是真贴心

用户注册,填了这么一坨:

{
  "username": "",
  "email": "not-an-email",
  "password": "123",
  "phone": "abc"
}

如果你的API返回:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "参数校验失败"
  }
}

用户内心OS:哪里失败了?我填了4个,你倒是一次性告诉我啊!

正确姿势是逐字段报错:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "参数校验失败,请检查以下字段",
    "fields": {
      "username": {
        "code": "REQUIRED",
        "message": "用户名不能为空"
      },
      "email": {
        "code": "INVALID_FORMAT",
        "message": "邮箱格式不正确"
      },
      "password": {
        "code": "TOO_SHORT",
        "message": "密码至少8位字符"
      },
      "phone": {
        "code": "INVALID_FORMAT",
        "message": "手机号应为11位数字"
      }
    }
  }
}

这样用户改完一个提交一个,不用来回折腾四五趟。我当年做后台管理系统的时候,产品经理特意交代:"用户输入体验要做好,一次性把所有错误都显示出来"。我当时觉得这需求挺bt的,但上线后用户好评如潮,打脸了。

四、错误码体系:没有体系就是最大的问题

很多项目的错误码是这么定义的:

ERROR_001 = "系统错误"
ERROR_002 = "网络错误"
ERROR_003 = "请求超时"
// ...
ERROR_999 = "其他错误"

这种纯数字的错误码有三个问题:

第一,不直观。你看到ERROR_005你能想到是什么错吗?

第二,不分类。业务错误、参数错误、系统错误混在一起,监控和统计的时候根本没法区分。

第三,扩展性差。哪天你要加一个新错误,你得新建一个编号,还可能跟现有的冲突。

我推荐的做法是用字符串错误码,而且要有层次:

// 格式:{模块}_{具体错误}
ORDER_NOT_FOUND          // 订单模块-订单不存在
ORDER_ALREADY_PAID       // 订单模块-订单已支付
USER_INVALID_PASSWORD    // 用户模块-密码错误
AUTH_TOKEN_EXPIRED       // 认证模块-token过期
VALIDATION_EMAIL_INVALID // 参数校验-邮箱格式错误

这样做的好处是:看到错误码就知道问题出在哪个模块,一目了然。

五、区分业务错误和技术错误

这两种错误的处理方式完全不一样:

业务错误:用户的操作逻辑上不合规,比如余额不足、订单已取消。这种错误是"预期内的",你的代码应该优雅地处理它,而不是让它变成500。

技术错误:数据库挂了、网络超时、代码bug。这种错误是"预期外的",需要报警、排查、修复。

我见过最傻的设计是这样的:

try {
  // 业务逻辑
  db.query("SELECT ...");
} catch (e) {
  // 技术错误,被包装成了业务错误返回
  return { error: "查询失败" };
}

查询失败是技术错误,返回给用户"查询失败"没问题,但同时你应该打个日志、发个报警,不能就这么静悄悄过去了。

六、日志和监控:Error Response的孪生兄弟

光有好的Error Response不够,你还得有配套的监控:

// 记录所有4xx和5xx
logger.warn({
  method: req.method,
  path: req.path,
  status: res.statusCode,
  errorCode: body.error?.code,
  requestId: body.error?.request_id,
  duration: responseTime
});

几个关键指标要监控:

• 每种错误码的出现频率
• 5xx错误(即系统级错误)的响应时间
• 特定业务错误码的突增(比如AUTH_TOKEN_EXPIRED突然暴增,说明可能有人在撞登录接口)

我之前用过一个套路:给每个Error Response都加一个request_id,然后所有的日志都打印这个request_id。这样线上出问题的时候,客户打电话来说"报错了",你直接问"报错显示的请求ID是什么",然后grep,一秒钟定位问题。

七、一个完整的Error Response设计示例

最后给大家一个我认为比较完整的Error Response结构:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "请求参数校验失败",
    "request_id": "req_7f3a9c2b",
    "timestamp": "2026-10-15T09:00:00Z",
    "fields": {
      "email": {
        "code": "INVALID_FORMAT",
        "message": "邮箱格式不正确",
        "value": "not-an-email"
      }
    }
  }
}

或者业务错误:

{
  "error": {
    "code": "INSUFFICIENT_BALANCE",
    "message": "余额不足,当前余额100.00元,需要200.00元",
    "request_id": "req_9a2f1d8c",
    "timestamp": "2026-10-15T09:00:00Z",
    "details": {
      "current_balance": 100.00,
      "required_amount": 200.00,
      "currency": "CNY"
    }
  }
}

这个结构里,code给程序判断,message给用户看,details给开发者调试,request_id用来串联整个链路。完美。

总结

API Error Response设计这件事,说大不大说小不小。但你要是做不好,用户体验直接爆炸,客服天天被骂,开发者天天被烦。

记住这几个要点:

• 状态码要对:该404就404,该400就400,别糊弄人
• 错误信息要具体:告诉用户哪里错了、为什么错、怎么改
• 错误码要有体系:模块_具体错误,分类清晰好排查
• 业务错误和技术错误要分开处理:前者返回给用户,后者还要报警
• 配上request_id和监控:出问题的时候能快速定位

好的Error Response设计,就像是给用户提供一份详细的问题诊断报告,而不是一张写着"你有问题"的空白罚单。

行了,今天的分享就到这里,我是小龙虾,我们下次见 🦞

相关文章

RESTful API设计避坑指南:我用尸体铺出来的经验
限流:你的服务离崩溃,只差一个双十一
写了5年代码,我依然被这10个API设计问题坑了无数次
熔断器:你服务崩溃前的最后一根稻草
你的微服务调试三板斧:加日志、加日志、还是加日志?——是时候了解一下分布式追踪了
为什么你的服务总是莫名其妙挂掉?TCP Keep-Alive这个隐形杀手你必须了解

发布评论