You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel Excel队列导出链式任务中捕获异常的正确方法

问题:Laravel Excel 导出为空时如何发送不同通知?

背景

我需要在导出文件存储完成后,向目标收件人发送邮件通知,该逻辑在Job类中实现:

public function handle(): void
{
    (new ReportExport($this->report, $this->date))->queue($this->report->path)
        ->chain([
            new NotifyReportRecipients($this->report),
        ]);
}

导出类使用了FromGenerator特性:

public function generator(): Generator
{
    $query = DB::query()->select(...)->from(...)->where(...);

    return $query->cursor()->getIterator();
}

当前逻辑已正常运行。

需求与问题

现在我希望在导出内容为空时发送不同的通知,尝试通过抛出异常在Job类中处理:

use Path\To\MyException;

public function generator(): Generator
{
    $query = DB::query()->select(...)->from(...)->where(...);

    throw_if($query->count() === 0, MyException::class);

    return $query->cursor()->getIterator();
}

但Laravel-excel的QueuedExport类未实现catch方法,调用时报错:

public function handle(): void
{
    (new ReportExport($this->report, $this->date))->queue($this->report->path)
        ->chain([
            new NotifyReportRecipients($this->report),
        ])
        ->catch(function (MyException $e) {
            new NotifyReportRecipientsAboutEmptyReport($this->report),
        });
        // 错误:Call to undefined method Maatwebsite\Excel\Jobs\QueueExport::catch()
}

尝试使用try...catch块也无效:

public function handle(): void
{
    try {
        (new ReportExport($this->report, $this->date))->queue($this->report->path)
            ->chain([
                new NotifyReportRecipients($this->report),
            ]);
    } catch (MyException $e) {
        NotifyReportRecipientsAboutEmptyReport::dispatch($this->report);
    }
}

按照Laravel Excel文档给导出类添加failed方法同样无效:

use Path\To\MyException;

public function failed(Throwable $e)
{
    if ($e instanceof MyException) {
        NotifyReportRecipientsAboutEmptyReport::dispatch($this->report);
    }
}

public function generator(): Generator
{
    $query = DB::query()->select(...)->from(...)->where(...);

    throw_if($query->count() === 0, MyException::class);

    return $query->cursor()->getIterator();
}

更严重的是,无论导出是否为空,链式任务中的通知都会被分发。请问正确的处理方式是什么?


解决方案

1. 提前判断数据是否为空(推荐)

在触发导出的Job里先查询数据量,直接分支处理,避免进入队列后处理异常:

public function handle(): void
{
    $query = DB::query()->select(...)->from(...)->where(...);

    if ($query->count() === 0) {
        NotifyReportRecipientsAboutEmptyReport::dispatch($this->report);
        return;
    }

    (new ReportExport($this->report, $this->date))->queue($this->report->path)
        ->chain([
            new NotifyReportRecipients($this->report),
        ]);
}

这种方式逻辑简单直接,不需要依赖队列异常机制,还能节省空数据场景下的队列任务资源。

2. 利用导出类生命周期钩子处理

在导出类中通过beforeExport钩子判断数据,再用afterExport触发对应通知,同时去掉原有的链式任务:

use Maatwebsite\Excel\Events\BeforeExport;
use Illuminate\Contracts\Queue\ShouldQueue;

class ReportExport implements FromGenerator, ShouldQueue
{
    public $report;
    public $date;
    public $hasData = true;

    public function __construct($report, $date)
    {
        $this->report = $report;
        $this->date = $date;
    }

    public function registerEvents(): array
    {
        return [
            BeforeExport::class => function (BeforeExport $event) {
                $query = DB::query()->select(...)->from(...)->where(...);
                
                if ($query->count() === 0) {
                    $this->hasData = false;
                    // 可以给空文件写入提示内容
                    $event->writer->getSheet()->getDelegate()->setCellValue('A1', '暂无数据');
                }
            },
        ];
    }

    public function generator(): Generator
    {
        if (!$this->hasData) {
            yield; // 返回空生成器
        }

        $query = DB::query()->select(...)->from(...)->where(...);
        return $query->cursor()->getIterator();
    }

    public function afterExport()
    {
        if ($this->hasData) {
            NotifyReportRecipients::dispatch($this->report);
        } else {
            NotifyReportRecipientsAboutEmptyReport::dispatch($this->report);
        }
    }
}

修改触发Job:

public function handle(): void
{
    (new ReportExport($this->report, $this->date))->queue($this->report->path);
}

这种方式把通知逻辑和导出流程绑定,符合Laravel Excel的生命周期设计。

3. 用统一Job统筹流程

创建一个单独的Job来管理整个导出+通知的流程,替代原有的链式任务:

class ProcessReportExport implements ShouldQueue
{
    public $report;
    public $date;

    public function __construct($report, $date)
    {
        $this->report = $report;
        $this->date = $date;
    }

    public function handle(): void
    {
        $query = DB::query()->select(...)->from(...)->where(...);

        if ($query->count() === 0) {
            NotifyReportRecipientsAboutEmptyReport::dispatch($this->report);
            return;
        }

        // 同步执行导出(如果不需要队列导出可改用此方式)
        (new ReportExport($this->report, $this->date))->store($this->report->path);
        
        // 导出完成后发送正常通知
        NotifyReportRecipients::dispatch($this->report);
    }
}

调度这个Job即可:

ProcessReportExport::dispatch($report, $date);

这种方式把所有逻辑集中在一处,便于调试和维护。


内容的提问来源于stack exchange,提问作者IGP

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 10:04:54