thinkphp 统一 api 返回格式需分层拦截:重写 json 类 output() 方法、用中间件兜底非 json 响应、trait 封装 success/error、cli 环境特殊处理,四者缺一不可。

ThinkPHP 无法靠改配置或加个函数就真正统一 API 返回格式——return json($data)、throw new ValidateException()、$this->success()、CLI 环境下的响应,走的是四条完全不同的路径。必须分层拦截,缺一不可。
重写 think\response\Json 类控制主动 json() 调用
这是最干净的起点,只影响你显式写的 return json($data),不碰异常流。关键不是“大改类”,而是精准重写 output() 方法,把原始数据包进标准结构里。
- 在
app\common\response\Json.php中定义新类,namespace app\common\response;,继承think\response\Json - 只重写
protected function output($data): string,构造['code' => $this->code, 'msg' => $this->message, 'data' => $data]后json_encode(, JSON_UNESCAPED_UNICODE) - 在
AppServiceProvider::register()里绑定:$this->app->bind('think\response\Json', \app\common\response\Json::class) - 别动
__construct或init——$this->code和$this->message是框架根据json($data, 200, [], ['code' => 1])第四个参数自动设的,破坏它会导致return json($data, 400)失效
用中间件兜底所有非 Json 响应(异常、验证失败等)
上面只管住了 return json(),但 throw new ValidateException()、未捕获异常、HttpException 仍走原生 think\response\Json 或 Html,结构不一致。必须在响应发出前做一次“强制统一封装”。
- 新建中间件
app\middleware\UniformJsonResponse,handle()中检查$response instanceof \think\Response且不是Json实例 - 若响应体是数组(如异常默认结构),提取
['code' => , 'msg' => , 'data' => ];若不是,统一包装为['code' => 500, 'msg' => 'server error', 'data' => null] - 用
json($wrapped)->header(['Content-Type' => 'application/json; charset=utf-8'])替换原响应 - 注册为全局中间件,确保它在
ResponseTrace之后、输出之前执行
用 Trait 封装业务层 success()/error() 方法
控制器里写 return $this->success($user) 比反复调用 json() 更直观,也更容易统一字段(比如加 timestamp、version)。但注意:Trait 只是语法糖,底层仍要走你重写的 Json 类或中间件。
- 在
app\traits\ApiResponse.php中定义Trait,提供success()、error()方法,参数统一为$data = []、$msg = ''、$code = 0 - 内部一律调用
return json(...),避免依赖控制器上下文或手动构造数组 - 对
$data做预检:遇到Paginator实例先调用$paginator->toArray();遇到模型对象强制走$model->toArray(),避开隐藏字段干扰 - 不要在
Trait里自动调用visible()或hidden(),会和 ORM 内部逻辑冲突
必须处理 CLI 环境,否则 header() 报错
所有封装层(Json 类、中间件、Trait)都可能在命令行下执行,比如队列任务、定时脚本。一旦尝试发 header(),就会触发 headers already sent 错误。
- 在
output()开头加判断:if (php_sapi_name() === 'cli') { return json_encode(...); } - 中间件中检查
!app()->isConsole()再执行封装逻辑 -
Trait中的success()方法也应先判断 CLI,直接返回数组而非json()实例 - 别依赖
env('APP_DEBUG')判断——CLI 下也可能开 debug,但 header 依然不能发
最容易被忽略的是 CLI 场景和关联模型的字段一致性。前者导致定时任务崩掉,后者让前端拿到混着 user_id 和 userId 的响应,解析逻辑直接失效。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











