API异常未返回自定义JSON格式,因默认render()不处理API请求,需在Handler::render()中显式拦截并构造JSON响应;应使用$request->expectsJson()准确识别API请求,并为不同异常类型返回对应HTTP状态码的标准JSON。

API请求抛出异常时,为什么返回的不是你定义的 JSON 格式?
因为默认的 render() 方法对 API 请求不做特殊处理,ValidationException、ModelNotFoundException 等直接走 Laravel 内置逻辑:前者可能返回 HTML 表单错误页,后者返回空白 404 响应体,根本不会套上 code/message/data 结构。
必须在 App\Exceptions\Handler::render() 中显式拦截并构造 JSON 响应,否则前端永远收不到统一格式。
- 别指望中间件兜底——异常可能发生在路由匹配前或视图渲染时,中间件根本没机会执行
- 别在控制器里手动封装——500 错误、模型未找到等压根不会进控制器
- 开发环境开启
APP_DEBUG=true时,Laravel 默认返回 Ignition 调试页,要强制 JSON 得加判断条件
怎么用 $request->expectsJson() 准确识别 API 请求?
$request->expectsJson() 比 $request->is('api/*') 更可靠,它检查请求头是否含 Accept: application/json 或 X-Requested-With: XMLHttpRequest,Axios、Fetch、Postman 默认都带,浏览器地址栏直访问则不匹配。
实操建议组合使用,兼顾兼容性:
-
if ($request->expectsJson() || $request->is('api/*')) { ... }—— 覆盖绝大多数场景 - 不要只依赖 URL 前缀,子域名(如
v1.api.example.com)或无前缀的 GraphQL 接口会漏判 - 测试时用
curl -H "Accept: application/json" http://localhost:8000/user,避免被重定向或返回 Blade 页面
常见异常类型必须单独处理,不能全交给 parent::render()
直接 return parent::render($request, $exception) 对 API 是灾难性的:调试模式下暴露完整堆栈,生产环境返回空白 500 页面,完全脱离你的统一结构。
关键异常必须提前拦截并返回标准 JSON:
-
ValidationException:用$exception->errors()提取字段级错误,状态码固定 422,别用$exception->getMessage() -
ModelNotFoundException:返回response()->json(['code' => 404, 'message' => '资源不存在'], 404),别留英文默认消息 - 业务自定义异常(如
InsufficientBalanceException):靠$exception->getCode()取业务码,$exception->getMessage()取提示语 -
Throwable(兜底):生产环境只返回['code' => 500, 'message' => 'Server Error'],禁止带堆栈或文件路径
response()->json() 的第三个参数不是可选项,是 REST 合规关键
很多人写 response()->json(['data' => $user]),结果创建资源后仍是 200 状态码,违反 REST 规范。HTTP 状态码必须明确传递,且和业务语义匹配。
不同场景对应不同状态码:
- 创建成功:
response()->json(['data' => $user], 201) - 删除成功:
response()->json(['message' => 'OK'], 204)(注意:204 不允许带响应体,别传数组) - 验证失败:
response()->json(['code' => 422, 'message' => '参数校验失败', 'errors' => $exception->errors()], 422) - 认证失败:
response()->json(['message' => 'Unauthenticated.'], 401)
业务错误码(如 1002)和 HTTP 状态码(如 400)是两回事,别混用;前者进 JSON body 的 code 字段,后者是响应头里的真实状态码。


















