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

如何用客户端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>
关键细节说明
  1. Tar归档的必要性:gzip只负责压缩单个数据流,tar则负责把多个文件(包含完整路径)打包成一个连续的数据流,两者结合才能生成带目录结构的压缩包。
  2. Tar格式规范:实现中遵循了Tar的Ustar标准,包括512字节块对齐、文件头格式,确保生成的tar.gz能被WinRAR、7-Zip等主流工具正确解压。
  3. 流式处理优势:全程使用浏览器的ReadableStream处理,不会把大文件一次性加载到内存,性能更稳定,也符合内置API的设计逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 02:15:55