Laravel自定义异常渲染_根据请求类型返回响应【操作】
在Laravel的Handler::render()中,利用expectsJson()与is('api/*')判断请求类型,分别返回JSON或HTML异常响应。需单独处理ModelNotFoundException、ValidationException等常见异常,避免在中间件处理。开发环境需强制JSON响应及调试信息。
处理API和网页请求抛异常时的响应形式,这活儿,中间件和控制器都干不了。必须得在 App\Exceptions\Handler::render() 这个核心位置来做判断。
怎么区分API和网页请求
别依赖路由前缀(比如 api/*)或者手动传参来判断。Lara vel 自己就提供了两个靠谱的方式:
$request->expectsJson():这个会检查请求头里是不是带着Accept: application/json,或者是不是 AJAX 请求(带了X-Requested-With: XMLHttpRequest)。现在绝大多数前端框架,像 Axios、Fetch,默认都会带上这些头信息,所以这个方法很推荐。$request->is('api/*'):这个只匹配 URL 路径,适合你明确把所有 API 都放在/api/下的项目。但它有个局限,识别不了子域名或者版本前缀(比如v1.api.example.com这种)。- 稳妥的做法是把两者组合起来用:
if ($request->expectsJson() || $request->is('api/*')),这样既灵活,兼容性也强。
常见异常类型要单独处理
如果直接写 return parent::render($request, $exception),那就走 Lara vel 的默认逻辑了。对于 API 请求来说,这意味着在调试模式下会直接暴露堆栈信息,生产环境则返回一个空白 500 页面。所以,必须提前把几种常见的异常拦截下来:
ModelNotFoundException:对应 404 错误。API 可以返回response()->json(['message' => 'Not found'], 404),网页请求则返回response()->view('errors.404')。ValidationException:这是$request->validate()抛出的异常,Lara vel 会自动捕获。API 返回response()->json(['errors' => $exception->errors()], 422),网页可以重定向回表单并显示错误提示。AuthorizationException:权限不足,统一返回 403。无论是 API 还是网页,都建议用 JSON 格式返回,避免前端去解析一个 HTML 错误页面。- 一个关键注意点:不要为了省事就 catch 所有
Exception或Throwable来做兜底处理。这会吞掉本该触发调试器的致命错误,比如内存溢出、语法错误,到时候排查问题会非常痛苦。
为什么不能在中间件里统一转 JSON
因为异常完全可能在中间件执行之前或之后发生,中间件根本抓不到。来看看几个典型场景:
- 访问一个不存在的路由 → 触发
NotFoundHttpException→ 这时候还没进入任何中间件。 - Blade 模板里写了
{{ $user->name }}但$user是 null → 视图引擎报错 → 中间件早已执行完毕。 - 数据库连接失败,导致 Eloquent 查询抛出
QueryException→ 这发生在模型方法内部,远早于响应构造阶段。

APP_DEBUG=true 时 JSON 响应仍被覆盖?
开发环境下,Lara vel 默认会返回一个带堆栈信息的 HTML 错误页,哪怕请求头是 Accept: application/json。解决办法是在 render() 方法的最开头加一个强制判断:
if ($request->expectsJson()) {
return response()->json([
'message' => 'Server Error',
'debug' => config('app.debug') ? $exception->getMessage() : null
], 500);
}
这里有个重要的安全提醒:在生产环境里,千万别把 $exception->getMessage() 直接返回,这很容易泄露服务器路径、数据库名等敏感信息。config('app.debug') 是唯一安全的开关依据。
最后,还有一个容易被忽略的坑:第三方 SDK 抛出的异常。比如 Stripe、PayPal 的客户端库,它们往往用的是原生的 Exception,不会自动进入你定义的 ValidationException 分支。这类异常需要手动补一层判断,或者在调用处用 try/catch 捕获后,重新 throw new CustomPaymentException(),否则它们就会悄无声息地变成 500 HTML 页面,让你花很多时间去排查。


































