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

Laravel多PDF同时下载失败问题排查求助

多文件PDF下载仅单个成功,其余请求被Canceled的问题排查与解决

已确认传入数据正确、浏览器开启多文件下载设置,但批量生成PDF后仅一个能被浏览器下载,其余请求状态为canceled。所有PDF已在存储中生成,仅下载环节出现问题。以下是问题分析和解决方案:

核心原因分析

  1. 浏览器并发下载限制:多数浏览器对同一域名下的并发下载请求数有限制(通常为2-6个),短时间内批量发起下载请求会被浏览器主动拦截,标记为canceled。
  2. 请求触发时机不合理:控制器循环触发事件、Job中循环发送下载通知,导致多个下载请求几乎同时发起,超出浏览器处理能力。
  3. 模板语法问题:Blade模板中@elseif(in_array(...))分支的<tbody>标签未正确缩进闭合,虽不影响PDF生成,但可能引发渲染隐患。

解决方案

方案一:控制下载请求发起节奏

修改后端逻辑,先批量生成所有PDF,再将所有下载路径统一返回给前端,由前端控制逐个发起下载请求(添加间隔时间)。

修改Job的handle方法,收集所有下载路径后统一通知

public function handle(): void
{
    $idsToProcess = is_array($this->data['id']) ? $this->data['id'] : [$this->data['id']];
    Log::info('IDs to process: ' . json_encode($idsToProcess));
    
    $downloadPaths = []; // 收集所有生成好的PDF路径

    foreach ($idsToProcess as $productId) {
        // 数据查询逻辑保持不变
        $this->data['store_item_product'] = StoreItemProduct::where('id', $productId)->first();
        $this->data['store_item'] = $this->data['store_item_product']->store_item()->first();
        $this->data['supplier'] = $this->data['store_item']->suppliers()->first();
        $this->data['materialproduct'] = $this->data['store_item_product']->materialproduct()->first();
        $this->data['material'] = $this->data['materialproduct'] ? $this->data['materialproduct']->material()->first() : null;

        $this->data['date'] = date('d/m/Y');
        $this->data['time'] = date('H:i:s');

        if (!empty($this->data)) {
            $pathInfo = pathinfo($this->data['path']);
            $filename = $pathInfo['filename'] . '_product_' . $productId;
            $this->data['path'] = $pathInfo['dirname'] . '/' . $filename . '.' . $pathInfo['extension'];
            $downloadPaths[] = $this->data['path']; // 保存当前PDF路径

            $this->setOptionsAndTemplate();
        }
        CreatePDF::dispatchSync($this->data['path'], $this->data, $this->template, $this->options, 'hobs');
        Log::info('CreatePDF job dispatched for ID ' . $productId);
    }

    // 统一发送包含所有下载路径的通知
    if (!empty($downloadPaths)) {
        Bus::chain([
            new UserNotice(
                $this->user,
                "success",
                __("purchases.documents_were_generated"), // 替换为多文件生成提示文案
                null,
                ["object" => "purchase", "downloads" => $this->download ? $downloadPaths : null, "appID" => ($this->appID ?? null)]
            )
        ])->dispatch();
    }
}

修改控制器,避免循环触发事件

public function viewAllProductInfo(Request $request)
{
    $all_ids = $request->id;
    // 直接将所有ID传入单个Job处理,避免循环触发事件
    StoreViewItemProductLabelJob::dispatch($request->all(), auth()->user(), $request->get('appID'));
}

前端处理示例(逐个发起下载)

// 收到包含downloads数组的通知后,间隔300ms逐个发起下载
function batchDownload(paths) {
    paths.forEach((path, index) => {
        setTimeout(() => {
            const link = document.createElement('a');
            link.href = path;
            link.download = path.split('/').pop();
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
        }, index * 300);
    });
}

方案二:合并多PDF为单个文件(可选)

如果业务允许,可将多个PDF合并为单个文件后提供下载,彻底避免浏览器并发限制。需引入PDF合并类库(如setasign/fpdi)实现合并逻辑。

修复Blade模板语法问题

修正<tbody>标签的缩进和闭合,避免潜在渲染异常:

<body>
    <table cellspacing="2" width="100%">
        @if($content->store_item->store_item_type_id == 1)
        <tbody>
            <tr>
                <td>{{ $content->store_item->name }}</td>
            </tr>
            <tr>
                <td>
                    {{ $content->material->name }} <br>
                </td>
            </tr>
            <tr>
                <td><b>Breite:</b> {{ $content->materialproduct->webwidth }}</td>
            </tr>
        </tbody>
        @elseif($content->store_item->store_item_type_id == 2)
        <tbody>
            <tr>
                <td>{{ $content->store_item->name }}</td>
            </tr>
            <tr>
                <td>
                    {{ $content->material->name }} <br>
                </td>
            </tr>
            <tr>
                <td><b>Anzeigename:</b> {{ $content->materialproduct->display_name }}</td>
            </tr>
        </tbody>
        @elseif(in_array($content->store_item->store_item_type_id, [3, 4, 5, 6]))
        <tbody>
            <tr>
                <td>{{ $content->store_item->name }}</td>
            </tr>
        </tbody>
        @endif
    </table>
</body>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 19:35:56