必须用response::setsendcallback()在send()内部拦截重写,而非after中间件;需按content-type过滤仅处理json响应,并确保封装中间件优先级高于jsonresponse。

想在 ThinkPHP 8.0 中用中间件统一改写响应结构(比如统一封装成 {"code":0,"msg":"ok","data":{...}}),关键不是“在 after 中间件里改 $response”,而是必须在响应真正发出前介入——因为 after 阶段的 Response 已发送,修改无效。
别在 after 中间件里直接改 response
after 中间件的 handle 方法中,$response = $next($request) 返回的是已构建完毕、即将输出的 Response 对象。此时框架内部已调用过 send() 或至少已准备发送 header 和 body。你调用 $response->withJson() 或 $response->withBody() 不会生效,前端仍收到控制器原始返回。
常见表现:日志里看到中间件执行了、var_dump($response->getContent()) 能打印内容,但浏览器收不到新结构。
用 Response::setSendCallback() 拦截并重写
这是 ThinkPHP 官方支持的、唯一可靠的方式。它在 Response::send() 内部触发,早于 header 发送和 body 输出,属于临界可控点。
- 在中间件 handle 方法中注册回调:
\think\Response::setSendCallback(function ($response) { ... }); - 回调函数内可读取原始响应体:
$content = $response->getContent(); - 按需解析、封装、再写回:
$newData = ['code' => 0, 'msg' => 'ok', 'data' => json_decode($content, true) ?: []]; - 注意同步更新 header:
$response->header('Content-Length', strlen(json_encode($newData))); - 最后返回新 Response:
return \think\Response::create(json_encode($newData), 'json');
确保中间件优先级高于 JsonResponse
ThinkPHP 默认有 think\middleware\JsonResponse 中间件,它会把数组自动转 JSON。如果你的封装中间件排在它后面,就会被覆盖。
- 在
app/middleware.php中注册时,用数字键指定优先级,越小越靠前:10 => \app\middleware\ResponseWrapper::class - 或在路由/控制器绑定时显式声明顺序:
->middleware(['response_wrapper', 'json_response']) - 验证方式:在中间件中
dump(\think\Pipeline::getMiddleware());查看实际执行队列
统一结构要兼容非 JSON 响应
控制器可能返回视图、文件流、XML 或纯字符串。硬编码 json_decode 会出错。
- 先判断原始 Content-Type:
$type = $response->getHeader('Content-Type')[0] ?? ''; - 只对
application/json类型做封装,其他类型原样放行 - 若需支持 HTML 视图,可改用 layout 模板或控制器层统一 return view('common/wrapper', [...])
- 异常响应(如 404、500)通常走
HttpResponseException,其 render 方法不受 sendCallback 影响,需单独在 exception handler 中处理
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











