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

Laravel队列任务无法生成Zip文件问题求助

Laravel 10 队列批量任务后Zip打包失败问题

我使用Laravel 10的批量队列任务生成XML文件,文件会保存到指定文件夹。待所有XML文件生成完成后,通过CreateExportacaoEsusZip任务将这些文件打包为Zip并删除原文件夹。

XML文件及文件夹的生成均正常,但执行Zip打包任务时,看似运行正常却未生成Zip文件。同步执行(不使用QUEUE_CONNECTION=database)时功能正常,推测问题与后台队列执行有关:本地Windows 11环境下生成的Zip文件损坏,Linux服务器环境下则完全不生成Zip,且两种情况均无报错或警告信息。

任务调用代码

// 批量任务和Zip打包任务的调用代码
Bus::chain([
    Bus::batch($jobs_batched),
    new CreateExportacaoEsusZip($fold['cryptFolder'], $fold['path'], $file_name),
    function () use ($data, $loteExportacao, $user, $file_name) {
        $loteExportacao->arquivo = "exportacoes_esus/$file_name.zip";
        $loteExportacao->em_processamento = false;
        $loteExportacao->save();

        $user->notify(new ExportFichasEsusNotification('after', $user));
        Log::notice(logMessage('Arquivos XML E-sus', 'Geração de Arquivo XML', $user->id));
    }
])->catch(function (Batch $batch, Throwable $e) use ($user, $fold) {
    if ($fold['cryptFolder']) {
        Storage::deleteDirectory("exportacoes_esus/{$fold['cryptFolder']}");
    }

    $user->notify(new ExportFichasEsusNotification('error', $user));
    Log::error(logMessage('Arquivos XML E-sus', 'Geração de Arquivo XML', $user->id), ['error' => $e->getMessage()]);
})->dispatch();

CreateExportacaoEsusZip任务实现

<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use League\Flysystem\MountManager;
use ZipArchive;

class CreateExportacaoEsusZip implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    private string $folder;
    private string $path;
    private string $file_name;

    /**
     * Create a new job instance.
     */
    public function __construct(string $folder, string $path, string $file_name)
    {
        $this->folder = $folder;
        $this->path = $path;
        $this->file_name = $file_name;
    }

    /**
     * Execute the job.
     */
    public function handle(): void
    {
        try {
            $zip = new ZipArchive;
            $fileName = storage_path('app/exportacoes_esus') . '/' . $this->file_name . '.zip';

            if (!File::exists($this->path)) {
                throw new \Exception("Directory does not exist: $this->path");
            }

            if ($zip->open($fileName, ZipArchive::CREATE) === true) {
                $files = File::files($this->path);
                if (empty($files)) {
                    throw new \Exception("No files found in directory: $this->path");
                }

                foreach ($files as $value) {
                    $relativeNameInZipFile = basename($value);
                    $zip->addFile($value, $relativeNameInZipFile);
                }

                $zip->close();
                Log::info("ZIP file created successfully: $fileName");
            } else {
                throw new \Exception("Could not create zip file at $fileName");
            }

            if (env('FILESYSTEM_DISK') == 's3') {
                $mountManager = new MountManager([
                    'local' => Storage::disk('local')->getDriver(),
                    's3' => Storage::disk('s3')->getDriver(),
                ]);

                $mountManager->copy("local://exportacoes_esus/$this->file_name.zip", "s3://exportacoes_esus/$this->file_name.zip");
                Storage::disk('local')->delete("exportacoes_esus/$this->file_name.zip");
                Log::info("ZIP file copied to S3 and deleted locally: $this->file_name.zip");
            }

            Storage::disk('local')->deleteDirectory("exportacoes_esus/$this->folder");
            Log::info("Temporary directory deleted: $this->folder");
        } catch (\Exception $e) {
            Log::error('Error creating ZIP file', [
                'folder' => $this->folder,
                'path' => $this->path,
                'file_name' => $this->file_name,
                'error' => $e->getMessage(),
            ]);
            Storage::delete("exportacoes_esus/$this->file_name.zip");
            throw $e;
        }
    }
}

问题排查与解决方案

1. 队列进程的文件权限问题

Linux环境下,队列进程(如php artisan queue:work)的运行用户可能和Web服务器用户不一致,导致没有写入storage/app/exportacoes_esus目录的权限:

  • 执行ls -l storage/app/exportacoes_esus检查目录所属用户和权限;
  • 调整权限:chmod -R 775 storage/app/exportacoes_esus,或设置正确用户组:chown -R www-data:www-data storage/app/exportacoes_esus(根据服务器实际用户调整)。

2. 路径一致性问题

队列进程的工作目录可能和Web请求环境不同,导致传递的$path失效:

  • 避免传递相对路径,改用Laravel Storage获取绝对路径:在生成XML时,用Storage::disk('local')->path("exportacoes_esus/{$fold['cryptFolder']}")获取路径并传递给任务;
  • 在任务的handle方法中,重新通过Storage获取目录路径,替代构造函数传递的$path:
$actualPath = Storage::disk('local')->path("exportacoes_esus/{$this->folder}");
if (!File::exists($actualPath)) {
    throw new \Exception("Directory does not exist: $actualPath");
}

3. Windows下Zip文件损坏问题

Windows环境下,ZipArchive关闭后可能未立即释放文件句柄,导致文件损坏:

  • 在$zip->close()后添加强制内存回收:
$zip->close();
gc_collect_cycles();
Log::info("ZIP file created successfully: $fileName");

4. S3同步时机问题

如果使用S3,需确保本地Zip文件完全写入后再执行复制:

if (env('FILESYSTEM_DISK') == 's3') {
    // 等待文件写入完成
    while (!File::exists($fileName)) {
        usleep(100000); // 等待100ms
    }
    // 后续S3复制逻辑不变
}

5. 强化日志调试

在任务中添加更多日志,确认每个步骤的执行状态:

  • 在获取文件列表后添加:Log::info('Files found for zipping', ['count' => count($files), 'files' => $files]);
  • 实时查看队列日志:tail -f storage/logs/laravel.log,排查隐藏的错误信息。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 16:23:13