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

Laravel+Inertia+Vue3多文件压缩分片上传避内存溢出方案咨询

可行方案建议

方案1:使用zip.js实现流式压缩+分片上传

zip.js专为浏览器环境设计了流式压缩能力,无需在内存中生成完整Zip包,可边压缩边输出流数据,完美解决大文件内存溢出问题。

实现步骤:

  1. 安装zip.js:
npm install @zip.js/zip.js
  1. Vue3组件核心代码示例:
<script setup>
import { BlobWriter, BlobReader, ZipWriter } from '@zip.js/zip.js'

async function handleFileUpload(files) {
  const CHUNK_SIZE = 10 * 1024 * 1024; // 10MB分片大小
  let chunkIndex = 0;
  const uploadId = crypto.randomUUID(); // 生成唯一上传标识,供服务器拼接分片

  // 创建ZipWriter,开启缓冲写入适配流式处理
  const zipWriter = new ZipWriter(new BlobWriter('application/zip'), {
    bufferedWrite: true,
    compression: 'DEFLATE' // 启用压缩减少带宽占用
  });

  // 逐个添加文件到Zip,避免一次性加载所有文件到内存
  for (const file of files) {
    await zipWriter.add(file.name, new BlobReader(file));
  }

  // 获取Zip的Blob流,分片读取并上传
  const zipBlob = await zipWriter.close();
  let offset = 0;

  const readNextChunk = () => {
    if (offset >= zipBlob.size) return;
    const chunk = zipBlob.slice(offset, offset + CHUNK_SIZE);
    const fileReader = new FileReader();

    fileReader.onload = async (e) => {
      // 上传分片到Laravel后端
      await fetch('/api/upload-zip-chunk', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/octet-stream',
          'X-Upload-Id': uploadId,
          'X-Chunk-Index': chunkIndex,
          'X-Total-Chunks': Math.ceil(zipBlob.size / CHUNK_SIZE)
        },
        body: e.target.result
      });

      chunkIndex++;
      offset += CHUNK_SIZE;
      readNextChunk();
    };

    fileReader.readAsArrayBuffer(chunk);
  };

  readNextChunk();
}
</script>

<template>
  <input type="file" multiple @change="handleFileUpload($event.target.files)" />
</template>
  1. Laravel后端分片处理示例:
// routes/api.php
Route::post('/upload-zip-chunk', [ZipUploadController::class, 'handleChunk']);

// app/Http/Controllers/ZipUploadController.php
use Illuminate\Support\Facades\File;

public function handleChunk(Request $request) {
    $uploadId = $request->header('X-Upload-Id');
    $chunkIndex = $request->header('X-Chunk-Index');
    $totalChunks = $request->header('X-Total-Chunks');
    
    // 创建临时目录存储分片
    $tempDir = storage_path('app/temp-zips/' . $uploadId);
    if (!File::exists($tempDir)) {
        File::makeDirectory($tempDir, 0755, true);
    }
    
    // 写入当前分片
    File::put($tempDir . '/' . $chunkIndex, $request->getContent());
    
    // 检查是否所有分片上传完成,合并为完整Zip
    if ((int)$chunkIndex === (int)$totalChunks - 1) {
        $finalZipPath = storage_path('app/uploads/' . $uploadId . '.zip');
        $output = fopen($finalZipPath, 'wb');
        
        for ($i = 0; $i < $totalChunks; $i++) {
            $chunkPath = $tempDir . '/' . $i;
            $chunk = fopen($chunkPath, 'rb');
            stream_copy_to_stream($chunk, $output);
            fclose($chunk);
            File::delete($chunkPath);
        }
        
        fclose($output);
        File::deleteDirectory($tempDir);
        
        // 这里添加后续处理逻辑(如解压、文件存储等)
        return response()->json(['status' => 'completed', 'zip_path' => $finalZipPath]);
    }
    
    return response()->json(['status' => 'chunk_received']);
}

方案2:分批次压缩+分片上传(兼容旧环境)

如果无法使用zip.js,可通过分批处理文件+生成小Zip分片的方式,避免一次性加载所有文件到内存,最后由服务器合并分片。

实现步骤:

  1. Vue3组件核心代码示例:
<script setup>
import JSZip from 'jszip'

async function handleFileUpload(files) {
  const BATCH_SIZE = 50; // 每批处理50个文件
  const CHUNK_SIZE = 10 * 1024 * 1024; // 10MB分片大小
  const uploadId = crypto.randomUUID();
  let batchIndex = 0;

  // 分批处理文件
  for (let i = 0; i < files.length; i += BATCH_SIZE) {
    const batchFiles = Array.from(files).slice(i, i + BATCH_SIZE);
    const zip = new JSZip();
    
    // 添加当前批次文件到Zip
    for (const file of batchFiles) {
      zip.file(file.name, file);
    }

    // 生成当前批次的Zip Blob
    const zipBlob = await zip.generateAsync({ type: 'blob', compression: 'DEFLATE' });
    let offset = 0;
    let chunkIndex = 0;

    // 分片上传当前批次的Zip
    while (offset < zipBlob.size) {
      const chunk = zipBlob.slice(offset, offset + CHUNK_SIZE);
      await fetch('/api/upload-zip-batch-chunk', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/octet-stream',
          'X-Upload-Id': uploadId,
          'X-Batch-Index': batchIndex,
          'X-Chunk-Index': chunkIndex,
          'X-Total-Chunks': Math.ceil(zipBlob.size / CHUNK_SIZE)
        },
        body: chunk
      });

      offset += CHUNK_SIZE;
      chunkIndex++;
    }

    batchIndex++;
    // 手动清理内存,避免泄漏
    zip.remove(Object.keys(zip.files));
    URL.revokeObjectURL(zipBlob);
  }

  // 通知服务器所有批次上传完成,开始合并
  await fetch('/api/complete-zip-upload', {
    method: 'POST',
    headers: { 'X-Upload-Id': uploadId },
  });
}
</script>
  1. Laravel后端需先存储各批次的分片,合并每个批次为完整小Zip,再将所有小Zip合并为一个完整Zip(或直接遍历每个小Zip处理内部文件)。

关键注意事项

  • 内存管理:每次处理完批次或分片后,手动清理JSZip实例、Blob对象,避免内存泄漏。
  • 断点续传:前端可记录已上传的分片/批次,上传失败时从断点继续。
  • 压缩平衡:根据需求调整压缩级别,平衡压缩率和客户端CPU占用。
  • 兼容性:zip.js支持现代浏览器(Chrome 60+、Firefox 55+),需兼容旧浏览器优先选择方案2。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 11:21:25