正确做法是复用原响应对象而非覆盖,API响应需统一通过路由分组、响应宏、异常处理器协同实现;中间件不处理异常,验证失败等必须由Handler.php拦截并返回含errors字段的标准JSON。

中间件里直接修改响应体容易丢状态码和Header
很多人在中间件里用 response()->json() 包一层原始响应,结果发现 401、422 这类状态码全变成 200,Header 里的 Content-Type 或 X-RateLimit-Remaining 也丢了。这是因为 Laravel 的中间件链里,$next($request) 返回的是完整 Illuminate\Http\Response 实例,直接覆盖会切断原始响应链。
正确做法是复用原响应对象,只替换内容体:
return $response->withHeaders([
'Content-Type' => 'application/json',
])->setContent(json_encode([
'code' => $response->getStatusCode(),
'data' => json_decode($response->content(), true) ?: null,
'message' => $response->getStatusCode() === 200 ? 'success' : response()->statusText($response->getStatusCode()),
]));
- 先判断
$response->content()是否已为 JSON 格式,避免双重解析失败 - 不要硬写
'code' => 0,状态码必须来自$response->getStatusCode() - 如果原始响应是视图或重定向(比如登录跳转),这种中间件会出错——它只该作用于 API 路由组
API 路由分组 + 中间件 + 响应宏三者要对齐
统一格式不是靠一个中间件就能兜底的。Laravel 默认的 api 路由组用了 throttle:api 和 bindings,但没强制返回 JSON;而你写的中间件若挂到所有路由上,就会把 POST /login(返回重定向)或 GET /storage/file.pdf(返回二进制流)也强行 JSON 化。
实操建议:
- 给 API 路由加专属前缀,比如
Route::prefix('api/v1')->middleware('api.response')->group(...) - 在
AppServiceProvider::boot()里注册响应宏:Response::macro('api', function ($data = null, $code = 200, $message = null) { ... }) - 控制器里优先用
response()->api($user),而不是return ['data' => $user]—— 这样能确保异常、验证失败、手动返回都走同一套结构
异常处理器里也要同步处理 JSON 响应结构
中间件管不到抛出的异常。比如 ValidationException 默认返回 HTML 表单错误页,ModelNotFoundException 返回 404 页面——这些在 API 场景下全得转成标准 JSON 格式。
打开 app/Exceptions/Handler.php,重写 render():
if ($request->expectsJson()) {
if ($exception instanceof ValidationException) {
return response()->json([
'code' => 422,
'data' => null,
'message' => 'Validation failed',
'errors' => $exception->errors(),
], 422);
}
return response()->json([
'code' => method_exists($exception, 'getStatusCode')
? $exception->getStatusCode()
: 500,
'data' => null,
'message' => $exception->getMessage(),
}, $exception->getStatusCode() ?? 500);
}
- 务必先判断
$request->expectsJson(),否则会影响网页端错误页 -
ValidationException的$exception->errors()是二维数组,别直接塞进data字段 - 别漏掉
AuthorizationException、TokenMismatchException等常见 API 异常类型
前端拿不到 errors 字段是因为后端没按规范暴露
很多团队统一了成功响应格式,但验证失败时只返回 { "message": "The given data was invalid." },前端没法取 errors.email 做表单标红。这不是前端问题,是后端没把 Laravel 的验证错误映射过去。
关键点就两个:
- 中间件不处理异常,异常必须由
Handler.php拦截并构造含errors的响应 - 控制器里手动返回验证失败(比如
return response()->json(['errors' => [...]], 422))时,字段名必须叫errors,不能叫validation_errors或detail - 如果用了自定义 Request 类,确保
failedValidation()方法里调用的是$this->validator->errors(),而不是自己 new 一个 MessageBag
真正难的不是写中间件,是让中间件、异常处理器、验证逻辑、响应宏四者在所有路径下输出一致的 key 名、状态码、嵌套层级。漏掉任意一环,前端就得写兼容逻辑。


















