http状态码与业务状态码必须分层:http码(如200/400/403)表通信合法性,业务码(如1001/2001)须置于响应体中,用枚举管理并确保异常被框架接管。

HTTP 状态码和业务状态码必须分层处理,不能混用。ThinkPHP 默认不拦截 Exception,直接抛 new Exception() 会导致 500 白屏或堆栈泄露,根本进不了你写的异常处理器。
HTTP 状态码只管通信是否合法
它由协议约定,不是你定义的——200 表示请求已抵达服务端并完成处理(不管业务成不成功),400 表示参数格式错、401 表示 Token 无效、403 表示权限不足、404 表示路由/资源不存在、204 表示操作成功但无返回体。
- ThinkPHP 的
ValidateException默认触发422,但它只是“验证失败”的协议信号,不代表业务上是“用户名重复”还是“手机号已被注册” - 不要写
return response(['code' => 403], 403)—— 把业务语义塞进状态码,前端无法区分“接口没权限”和“用户被封禁” - 网关或 Nginx 日志里看到大量
200但前端报“操作失败”,大概率是业务码逻辑在200响应体里返回了code: 1003这类值,而非用403
业务状态码必须放在响应体里,且要可扩展
它由你定义,比如 1001 表示“账号密码错误”,1003 表示“操作未授权”,2001 表示“库存不足”。这些值不参与 HTTP 协议协商,只用于前后端约定和日志归因。
- 统一结构推荐:
{"code": 1001, "msg": "登录失败", "data": []},其中code永远是整数,不与 HTTP 状态码重复 - ThinkPHP 6/8 中,只有继承
think\exception\HttpException的异常才能被app\common\exception\Handler::render()捕获并格式化;throw new Exception()会跳过它 - 验证失败想带业务码,不能靠改提示文字,得在控制器里手动捕获:
if (true !== $this->validate($data, $rule)) { throw new ValidateException('验证失败', 400, 1002); }
PHP 8.1+ 推荐用背书枚举管理业务码
别再用类常量或配置数组硬编码 const USER_NOT_FOUND = 1004。枚举能防错、可序列化、支持 tryFrom() 安全解析外部输入。
- 定义:
enum BizCode: int { case USER_NOT_FOUND = 1004; case INSUFFICIENT_BALANCE = 2002; } - 返回时:
['code' => BizCode::USER_NOT_FOUND->value, 'msg' => '用户不存在'] - 接收查询参数时:
$status = BizCode::tryFrom((int)input('code')) ?? BizCode::USER_NOT_FOUND;,避免from()因非法值崩掉整个请求 - 注意:纯枚举(
enum Foo { case Bar; })没有->value,不能 JSON 输出,也不适合存库或传给前端
最易被忽略的一点:ThinkPHP 的 404 不是异常,Route::miss() 是唯一可控入口;而所有自定义业务码的兜底逻辑,必须建立在异常被框架真正接管的前提下——先确保 throw new HttpException(400, '', 1001) 能正常输出 JSON,再谈业务码设计。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











