Laravel+Inertia+Vue3多文件压缩分片上传避内存溢出方案咨询
可行方案建议
方案1:使用zip.js实现流式压缩+分片上传
zip.js专为浏览器环境设计了流式压缩能力,无需在内存中生成完整Zip包,可边压缩边输出流数据,完美解决大文件内存溢出问题。
实现步骤:
- 安装zip.js:
npm install @zip.js/zip.js
- 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>
- 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分片的方式,避免一次性加载所有文件到内存,最后由服务器合并分片。
实现步骤:
- 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>
- Laravel后端需先存储各批次的分片,合并每个批次为完整小Zip,再将所有小Zip合并为一个完整Zip(或直接遍历每个小Zip处理内部文件)。
关键注意事项
- 内存管理:每次处理完批次或分片后,手动清理JSZip实例、Blob对象,避免内存泄漏。
- 断点续传:前端可记录已上传的分片/批次,上传失败时从断点继续。
- 压缩平衡:根据需求调整压缩级别,平衡压缩率和客户端CPU占用。
- 兼容性:zip.js支持现代浏览器(Chrome 60+、Firefox 55+),需兼容旧浏览器优先选择方案2。
内容的提问来源于stack exchange,提问作者Akash Kumar
相关产品推荐
相关产品推荐

