
laravel 自定义 artisan 命令中分发的队列任务若抛出异常,默认不会触发全局异常处理器;正确做法是利用队列任务自身的 `failed()` 方法捕获失败,并在此处实现邮件通知等自定义逻辑。
在 Laravel 中,队列任务(Job)的异常处理机制与 HTTP 请求生命周期是分离的:即使你在 app/Exceptions/Handler.php 中已完善了全局异常捕获逻辑,该处理器仅作用于同步请求上下文(如 Web 或 API 请求),而不会介入队列进程(如 php artisan queue:work)中的异常流转。因此,当 Artisan 命令中调用 dispatch() 发送任务后,若任务执行时抛出未捕获异常,Laravel 不会委托给 Handler::render() 或 Handler::report(),而是直接标记为失败并交由队列系统处理。
✅ 正确方案:使用任务类内置的 failed() 方法
每个可队列任务(实现了 ShouldQueue 接口)都支持定义 failed(Throwable $exception) 方法。该方法会在任务达到最大重试次数(默认 1 次)后自动调用,且保证只执行一次,是处理最终失败场景的理想入口:
$exception->getMessage(),
'trace' => $exception->getTraceAsString(),
]);
// 发送告警邮件(示例使用 Mail facade)
Mail::raw("Job failed: {$exception->getMessage()}\n\n" . $exception->getTraceAsString(), function ($message) {
$message->to('admin@example.com')
->subject('[Laravel Queue Alert] ProcessOrder Job Failed');
});
}
}? 前置准备:
- 运行以下命令创建 failed_jobs 表(Laravel ≥ 5.7 默认需要):
php artisan queue:failed-table php artisan migrate
- 确保队列驱动配置正确(如 database、redis),并在 .env 中设置 QUEUE_CONNECTION=redis(或其他);
- 启动队列监听器:php artisan queue:work --tries=3(建议显式指定重试次数,避免无限重试)。
⚠️ 注意事项:
- 不要在 handle() 方法中用 try-catch 吞掉异常并静默返回——这会绕过失败机制,导致 failed() 不被触发;
- 若需差异化处理不同异常类型(如 StripeException vs ConnectionException),可在 failed() 中使用 instanceof 判断并定制邮件模板或通知渠道;
- 对于关键任务,建议结合 Laravel Horizon(可视化监控)或 Sentry(错误追踪)增强可观测性;
- failed() 方法本身不应再抛出异常,否则可能导致队列进程崩溃;如有必要,应内部捕获并记录。
总结:Laravel 队列的健壮性依赖于其原生失败处理契约。放弃“让 Handler.php 拦截队列异常”的思路,转而拥抱 failed() 钩子,不仅能精准捕获最终失败原因,还能解耦异常通知逻辑,使系统更可靠、可维护。










