后端常见错误码总结
按 HTTP 状态码和业务错误码两个层次,总结后端接口常见错误码的含义、触发场景和处理建议。
后端错误码通常分为两层:一层是 HTTP 状态码,用来表达请求在协议层面的处理结果;另一层是业务错误码,用来表达具体业务规则失败的原因。
一个可维护的接口错误设计,应该做到三件事:HTTP 状态码语义正确,业务错误码稳定可检索,错误信息对前端和排查问题都有帮助。
一、HTTP 状态码
HTTP 状态码描述的是“这次请求在 Web 协议层面发生了什么”。它不应该承载过细的业务含义,而应该告诉调用方请求是否成功、是否需要重新登录、是否没有权限、是否请求参数错误,或者服务端是否异常。
1xx:信息响应
这类状态码在日常业务接口中不常直接处理,更多由 HTTP 协议、网关或底层框架完成。
| 状态码 | 含义 | 常见场景 |
|---|---|---|
100 Continue | 请求头已接收,客户端可以继续发送请求体 | 大文件上传前的预确认 |
101 Switching Protocols | 协议切换 | HTTP 升级到 WebSocket |
2xx:请求成功
2xx 表示请求已被服务端成功接收、理解并处理。
| 状态码 | 含义 | 常见场景 |
|---|---|---|
200 OK | 请求成功 | 查询、普通提交、修改成功 |
201 Created | 创建成功 | 新增用户、创建订单、上传资源成功 |
202 Accepted | 已接受但未处理完成 | 异步任务、导出任务、消息投递 |
204 No Content | 成功但无响应体 | 删除成功、只需要返回成功状态的更新操作 |
实际接口中最常见的是 200。如果想让 REST 语义更清晰,可以在创建资源时返回 201,在删除成功且不需要正文时返回 204。
3xx:重定向
3xx 表示客户端需要进一步操作才能完成请求。后端 API 中不算高频,但在登录跳转、资源迁移、文件下载地址重定向中会遇到。
| 状态码 | 含义 | 常见场景 |
|---|---|---|
301 Moved Permanently | 永久重定向 | 老接口或老域名永久迁移 |
302 Found | 临时重定向 | 登录页跳转、临时下载地址 |
304 Not Modified | 资源未变化 | 浏览器缓存、静态资源缓存 |
307 Temporary Redirect | 临时重定向,保持原请求方法 | 保留 POST 方法的临时跳转 |
308 Permanent Redirect | 永久重定向,保持原请求方法 | 保留请求方法的永久迁移 |
对前后端分离接口来说,未登录时一般不建议直接返回 302 跳登录页,更推荐返回 401,由前端决定跳转逻辑。
4xx:客户端错误
4xx 表示请求本身存在问题,或者当前调用方没有完成这次请求的条件。
| 状态码 | 含义 | 常见场景 |
|---|---|---|
400 Bad Request | 请求格式或参数错误 | JSON 格式错误、参数类型不对、缺少必填字段 |
401 Unauthorized | 未认证 | 未登录、Token 缺失、Token 过期 |
403 Forbidden | 已认证但无权限 | 普通用户访问管理员接口、权限不足 |
404 Not Found | 资源不存在 | 用户不存在、订单不存在、接口路径不存在 |
405 Method Not Allowed | 请求方法不允许 | 用 GET 调用了只支持 POST 的接口 |
409 Conflict | 资源冲突 | 重复创建、版本冲突、状态已变更 |
410 Gone | 资源已永久删除 | 老资源被下线且不再恢复 |
413 Payload Too Large | 请求体过大 | 上传文件超过限制 |
415 Unsupported Media Type | 媒体类型不支持 | 接口要求 JSON,却传了表单或错误的 Content-Type |
422 Unprocessable Entity | 参数语义校验失败 | 格式正确,但业务校验不通过 |
429 Too Many Requests | 请求过于频繁 | 限流、验证码频繁发送、登录尝试过多 |
400 和 422 的区别容易混淆。简单理解:请求结构都不合法时用 400;结构合法,但字段值不符合业务规则时可用 422。如果团队不想区分这么细,也可以统一用 400,但业务错误码要足够清楚。
401 和 403 也要分清楚:401 是“你是谁还没确认”,403 是“知道你是谁,但你不能做这件事”。
5xx:服务端错误
5xx 表示服务端处理请求时出现异常,调用方通常无法通过修改请求参数直接解决。
| 状态码 | 含义 | 常见场景 |
|---|---|---|
500 Internal Server Error | 服务端内部错误 | 未捕获异常、空指针、代码缺陷 |
501 Not Implemented | 功能未实现 | 路由存在但方法未完成 |
502 Bad Gateway | 网关收到上游无效响应 | Nginx、API 网关调用后端失败 |
503 Service Unavailable | 服务暂不可用 | 服务维护、依赖不可用、实例过载 |
504 Gateway Timeout | 网关等待上游超时 | 后端响应太慢、依赖超时 |
线上接口应尽量避免把底层异常、SQL、堆栈信息直接返回给客户端。客户端只需要稳定的错误码和可理解的信息,详细原因应该进入日志和链路追踪系统。
二、业务错误码
HTTP 状态码只能表达大方向,业务错误码才适合表达具体失败原因。例如同样是 400,可能是手机号格式错误、验证码错误、库存不足,也可能是订单状态不允许取消。
常见响应结构可以设计成这样:
{
"code": "USER_NOT_FOUND",
"message": "用户不存在",
"requestId": "req_20260622103000123",
"data": null
}
也可以使用数字编码:
{
"code": 100404,
"message": "用户不存在",
"requestId": "req_20260622103000123",
"data": null
}
字符串错误码可读性更好,适合跨团队协作和日志检索;数字错误码更短,适合已有编码规范的系统。无论选择哪一种,关键是稳定、唯一、可文档化。
常见业务错误码分类
| 分类 | 示例错误码 | 含义 | 建议 HTTP 状态码 |
|---|---|---|---|
| 通用错误 | UNKNOWN_ERROR | 未知错误 | 500 |
| 通用错误 | PARAM_INVALID | 参数不合法 | 400 或 422 |
| 通用错误 | PARAM_MISSING | 缺少必填参数 | 400 |
| 通用错误 | REQUEST_TOO_FREQUENT | 请求过于频繁 | 429 |
| 认证授权 | UNAUTHORIZED | 未登录或 Token 无效 | 401 |
| 认证授权 | TOKEN_EXPIRED | Token 已过期 | 401 |
| 认证授权 | FORBIDDEN | 无访问权限 | 403 |
| 用户模块 | USER_NOT_FOUND | 用户不存在 | 404 |
| 用户模块 | USER_DISABLED | 用户被禁用 | 403 |
| 用户模块 | PASSWORD_INCORRECT | 密码错误 | 400 或 401 |
| 验证码 | CAPTCHA_INVALID | 验证码错误 | 400 |
| 验证码 | CAPTCHA_EXPIRED | 验证码已过期 | 400 |
| 订单模块 | ORDER_NOT_FOUND | 订单不存在 | 404 |
| 订单模块 | ORDER_STATUS_INVALID | 当前订单状态不允许操作 | 409 |
| 支付模块 | PAYMENT_FAILED | 支付失败 | 400 或 502 |
| 支付模块 | PAYMENT_TIMEOUT | 支付超时 | 504 |
| 库存模块 | STOCK_NOT_ENOUGH | 库存不足 | 409 |
| 文件模块 | FILE_TOO_LARGE | 文件过大 | 413 |
| 文件模块 | FILE_TYPE_UNSUPPORTED | 文件类型不支持 | 415 |
| 外部依赖 | UPSTREAM_UNAVAILABLE | 上游服务不可用 | 503 |
| 外部依赖 | UPSTREAM_TIMEOUT | 上游服务超时 | 504 |
业务错误码不要设计成只给机器看的编号,也不要让同一个错误码在不同场景下表达不同含义。错误码一旦被前端、客户端、第三方系统依赖,就应该像接口字段一样谨慎变更。
三、错误码设计建议
1. HTTP 状态码不要全部返回 200
有些系统会把所有响应都包装成 200 OK,然后只依赖业务 code 判断成功或失败:
{
"code": 401001,
"message": "Token 已过期"
}
这种方式实现简单,但会削弱网关、监控、缓存、客户端 SDK 对 HTTP 语义的利用。更推荐的做法是:
HTTP/1.1 401 Unauthorized
Content-Type: application/json
{
"code": "TOKEN_EXPIRED",
"message": "登录状态已过期,请重新登录"
}
这样协议层和业务层各司其职,排查问题也更直观。
2. 错误信息分为用户信息和排查信息
返回给用户的 message 应该清楚、克制,不暴露内部实现:
{
"code": "ORDER_STATUS_INVALID",
"message": "当前订单状态不支持取消",
"requestId": "req_20260622103000123"
}
日志里则应该记录更完整的排查信息,例如用户 ID、订单 ID、接口路径、堆栈、依赖耗时等。
不要把下面这些内容直接返回给客户端:
SQL 语句
数据库表名和字段名
服务器绝对路径
异常堆栈
密钥、Token、手机号、身份证号等敏感信息
3. 错误码要有命名规则
字符串错误码可以采用模块前缀:
AUTH_TOKEN_EXPIRED
USER_NOT_FOUND
ORDER_STATUS_INVALID
PAYMENT_TIMEOUT
数字错误码可以采用分段规则:
100000-199999 通用错误
200000-299999 用户与认证
300000-399999 订单
400000-499999 支付
500000-599999 文件与资源
900000-999999 系统与外部依赖
不要今天返回 USER_NOT_FOUND,明天改成 USER_NOT_EXISTS。错误码名称可以不完美,但必须稳定。
4. 同一类错误保持一致
同样的错误在不同接口中应该返回同样的结构。例如用户不存在,不要在 A 接口返回:
{
"code": "USER_NOT_FOUND",
"message": "用户不存在"
}
在 B 接口又返回:
{
"errorCode": 40401,
"errorMsg": "查无此用户"
}
统一结构可以降低前端判断成本,也方便日志聚合、告警和接口文档维护。
5. 给每次错误响应带上 requestId
生产环境排查问题时,requestId 很重要。用户或前端只要提供这个 ID,后端就可以在日志、链路追踪、网关记录中定位到对应请求。
推荐结构:
{
"code": "UPSTREAM_TIMEOUT",
"message": "服务响应超时,请稍后重试",
"requestId": "req_20260622103000123"
}
四、常用速查表
| 场景 | HTTP 状态码 | 业务错误码示例 |
|---|---|---|
| 请求成功 | 200 | OK |
| 创建成功 | 201 | CREATED |
| 删除成功且无返回体 | 204 | 无需响应体 |
| 参数格式错误 | 400 | PARAM_INVALID |
| 未登录 | 401 | UNAUTHORIZED |
| Token 过期 | 401 | TOKEN_EXPIRED |
| 无权限 | 403 | FORBIDDEN |
| 资源不存在 | 404 | RESOURCE_NOT_FOUND |
| 请求方法错误 | 405 | METHOD_NOT_ALLOWED |
| 资源状态冲突 | 409 | RESOURCE_CONFLICT |
| 文件过大 | 413 | FILE_TOO_LARGE |
| 媒体类型不支持 | 415 | FILE_TYPE_UNSUPPORTED |
| 参数语义校验失败 | 422 | VALIDATION_FAILED |
| 请求过于频繁 | 429 | REQUEST_TOO_FREQUENT |
| 服务端异常 | 500 | INTERNAL_ERROR |
| 上游服务异常 | 502 | UPSTREAM_BAD_RESPONSE |
| 服务不可用 | 503 | SERVICE_UNAVAILABLE |
| 上游超时 | 504 | UPSTREAM_TIMEOUT |