如何用客户端JS结合CompressionStream API压缩用户上传的文件夹?
问题核心
你现在的问题在于gzip本身是单文件压缩格式,没法直接保留多文件的目录结构。你当前的代码只是把每个文件单独压缩后简单拼接,得到的不是合法的带目录结构的压缩包,自然会丢失目录信息。要实现带目录的压缩,得先把文件和目录结构归档成tar格式(tar是专门的归档格式,负责保留文件路径和目录结构),再用gzip压缩这个tar归档,最终生成标准的tar.gz文件。
解决方案:Tar归档 + Gzip压缩
下面是基于浏览器内置API的完整实现,不用引入任何外部库,同时保留目录结构:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Folder to Tar.GZ Compressor</title> </head> <body> <input type="file" id="fileInput" multiple webkitdirectory> <button onclick="compressFolder()">Compress Folder</button> <script> async function compressFolder() { const fileInput = document.getElementById('fileInput'); const files = Array.from(fileInput.files); if (!files.length) { alert('请选择至少一个文件或文件夹'); return; } // 生成Tar归档流,再通过gzip压缩 const tarStream = await createTarStream(files); const compressedStream = tarStream.pipeThrough(new CompressionStream('gzip')); // 转换为Blob并触发下载 const blob = await new Response(compressedStream).blob(); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'compressed_folder.tar.gz'; a.click(); URL.revokeObjectURL(url); } // 生成Tar格式的ReadableStream async function createTarStream(files) { const encoder = new TextEncoder(); const parts = []; for (const file of files) { const relativePath = file.webkitRelativePath || file.name; const fileSize = file.size; // 生成Tar文件头(符合Ustar标准格式) const header = createTarHeader(relativePath, fileSize); parts.push(encoder.encode(header)); // 添加文件内容流 parts.push(file.stream()); // 补充Tar块对齐:每个文件内容必须是512字节的倍数,不足补0 const paddingLength = (512 - (fileSize % 512)) % 512; if (paddingLength > 0) { parts.push(new Uint8Array(paddingLength)); } } // Tar归档结束标记:两个空的512字节块 parts.push(new Uint8Array(1024)); // 合并所有流和数据块为一个ReadableStream return new ReadableStream({ async start(controller) { for (const part of parts) { if (part instanceof ReadableStream) { const reader = part.getReader(); while (true) { const { done, value } = await reader.read(); if (done) break; controller.enqueue(value); } } else { controller.enqueue(part); } } controller.close(); } }); } // 生成Tar文件头(简化的Ustar格式,兼容主流解压工具) function createTarHeader(filePath, fileSize) { const encoder = new TextEncoder(); const header = new Uint8Array(512); // 写入文件路径(最多100字节) const pathBytes = encoder.encode(filePath.slice(0, 100)); header.set(pathBytes, 0); // 文件权限:默认644 header.set(encoder.encode('0000644'), 100); // 文件大小:八进制格式,占12字节 const sizeOctal = fileSize.toString(8).padStart(12, '0'); header.set(encoder.encode(sizeOctal), 124); // 文件类型:普通文件(标记为'0') header.set(encoder.encode('0'), 156); // Ustar格式标记:确认归档格式 header.set(encoder.encode('ustar\x00'), 257); // 剩余空间填充0 return new TextDecoder().decode(header); } </script> </body> </html>
关键细节说明
- Tar归档的必要性:gzip只负责压缩单个数据流,tar则负责把多个文件(包含完整路径)打包成一个连续的数据流,两者结合才能生成带目录结构的压缩包。
- Tar格式规范:实现中遵循了Tar的Ustar标准,包括512字节块对齐、文件头格式,确保生成的tar.gz能被WinRAR、7-Zip等主流工具正确解压。
- 流式处理优势:全程使用浏览器的
ReadableStream处理,不会把大文件一次性加载到内存,性能更稳定,也符合内置API的设计逻辑。
内容的提问来源于stack exchange,提问作者Liam Swayne
相关产品推荐
相关产品推荐

