统一错误码和错误信息必须做,关键在于规范定义、分层处理与结构统一:集中管理带语义前缀的错误码(如user_001),区分http状态码与业务码,固定响应结构,全程保留上下文并强化日志与测试。

直接说结论:统一错误码和错误信息不是“要不要做”的问题,而是“怎么做才不翻车”的问题。硬编码字符串、随意返回 200 套错误体、把 500 当万能兜底——这些做法在小项目里能跑,在联调或上线后必然被前端拉清单、被运维查日志、被自己半夜改 bug。
错误码必须集中定义,且带语义分段
把所有错误码塞进一个数组或常量类里,是底线要求;但更关键的是结构要有可读性。比如 USER_001 比 1001 更容易定位模块和问题类型。
- 前缀(如
USER、ORDER、DB)明确归属域,避免跨模块冲突 - 后缀编号(如
001)按错误严重程度或发生频率递增,方便排序和文档索引 - 禁止用纯数字码(如
1001)直接写死在控制器里,否则改一个要 grep 全局 - 每个码必须对应一条可读的
message,且支持占位符(如"用户 <code>{id}不存在"),便于动态填充
HTTP 状态码不能和业务码混为一谈
很多 PHP 项目习惯全用 200 + 自定义 code 字段返回所有结果,这会让 Nginx 日志、CDN 缓存、前端 fetch 的 response.ok 判断全部失效。
-
400用于参数校验失败(如缺失字段、格式错误) -
401仅用于 token 过期或未携带认证头,不是“登录态失效”的万能码 -
403表示权限不足(有身份但无操作权),不是“接口不存在”的替代品 -
404必须真实对应资源未找到(如 GET /users/9999),而不是用来掩盖路由错误 -
500只留给未捕获异常,业务逻辑里的可预期错误(如余额不足)绝不能扔给它
错误响应体结构必须固定,且兼容 RFC7807
前端最怕的不是报错,而是每次都要重写解析逻辑。一个字段名从 msg 变成 message,就能让 JS 报 undefined。
- 顶层字段强制三选一:
code(业务码)、message(人类可读提示)、data(空对象或 null,不可省略) - 拒绝出现
error、errmsg、status等非标字段,历史包袱用中间件转换,不放行到新接口 - 推荐引入
phpro/api-problem,它生成的 JSON 自动带type、title、detail,且状态码直透 HTTP 层,不用手动http_response_code() - 如果不用第三方包,至少确保所有
error()方法最终调用同一个封装函数,例如api_error($code, $message, $details = [])
别忽略错误码的传播路径和调试成本
一个 ORDER_CANCEL_FAILED 错误从数据库层抛出,经过 service → controller → response,中间每层都可能丢失上下文或覆盖原始码。
- 数据库异常不要直接转成
DB_ERROR就完事,应结合 PDO 的$e->getCode()和 SQLSTATE 做细分(如唯一约束失败 vs 连接超时) - 中间件里捕获异常时,优先从异常对象里提取预设的
getErrorCode()方法,而不是靠get_class()匹配字符串 - 日志记录必须同时打:HTTP 状态码、业务错误码、trace_id、原始异常 message,缺一不可
- 测试时用 PHPUnit 断言响应体的
code和message,而不仅是 HTTP 状态码
真正难的不是定义一百个错误码,而是在新增接口时,下意识去查 errors.php 而不是随手写个 "操作失败,请重试"。这个动作是否发生,决定了你的 API 是“能用”,还是“敢交出去”。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











