文件打包为zip下载(filesaver\ StreamSaver)

8 篇文章 0 订阅
5 篇文章 0 订阅

文件打包为zip下载(filesaver\StreamSaver)

在react项目中使用的,仅展示核心方法,其他的结构没写,使用时记得自己修改规范之类的

1,使用filesaver.js

需安装axios 、 filesaver 、 jszip

import axios from 'axios';
import JSZip from 'jszip';
import FileSaver from 'file-saver';

// axios请求文件
getFile = url => new Promise((resolve, reject) => {
  axios({
    method: 'get',
    url,
    responseType: 'blob',
    crossOrigin: true,
    withCredentials: true
  }).then((data) => {
    resolve(data.data);
  }).catch((error) => {
    reject(error.toString());
  });
})

// 下载单文件(不打包)
downloadSingleFile = (name, url) => {
  this.getFile(url).then((data) => {
    FileSaver.saveAs(data, `${name}`);
  }).catch((e) => {
    console.log('file download failed', e;
  });
}

// 打包下载
// 比如说需要下载的文件链接都在一个数组里面
// [{fileUrl: 'xxx', fileName: 'aa.ppt'}]
download = () => {
  const zip = new JSZip();
  const promises = [];

  filesArr.forEach((item) => {
    const promise = this.getFile(item.fileUrl).then((data) => { // 下载文件, 这里可以catch下计算或者存放失败的文件情况
      zip.file(item.fileName, data, { binary: true }); // 逐个添加文件
    });
    promises.push(promise);
  });
  
  Promise.all(promises).then((resolve, reject) => {
    zip.generateAsync({ type: 'blob' }).then((content) => { // 生成二进制流
        FileSaver.saveAs(content, '打包下载.zip'); // 利用file-saver保存文件
    });
  }).catch((e) => {
    console.log('file download failed', e);
  });
}

2, 使用StreamSaver

aver.js

需要自己创建一个新文件zip-stream.js

class Crc32 {
  constructor() {
    this.crc = -1;
  }

  append(data) {
    let crc = this.crc | 0; const { table } = this;
    for (let offset = 0, len = data.length | 0; offset < len; offset++) {
      crc = (crc >>> 8) ^ table[(crc ^ data[offset]) & 0xFF];
    }
    this.crc = crc;
  }

  get() {
    return ~this.crc;
  }
}
Crc32.prototype.table = (() => {
  let i; let j; let t; const table = [];
  for (i = 0; i < 256; i++) {
    t = i;
    for (j = 0; j < 8; j++) {
      t = (t & 1)
        ? (t >>> 1) ^ 0xEDB88320
        : t >>> 1;
    }
    table[i] = t;
  }
  return table;
})();

const getDataHelper = (byteLength) => {
  const uint8 = new Uint8Array(byteLength);
  return {
    array: uint8,
    view: new DataView(uint8.buffer)
  };
};

const pump = zipObj => zipObj.reader.read().then((chunk) => {
  if (chunk.done) return zipObj.writeFooter();
  const outputData = chunk.value;
  zipObj.crc.append(outputData);
  zipObj.uncompressedLength += outputData.length;
  zipObj.compressedLength += outputData.length;
  zipObj.ctrl.enqueue(outputData);
});

/**
   * [createWriter description]
   * @param  {Object} underlyingSource [description]
   * @return {Boolean}                  [description]
   */
export default function createWriter(underlyingSource) {
  const files = Object.create(null);
  const filenames = [];
  const encoder = new TextEncoder();
  let offset = 0;
  let activeZipIndex = 0;
  let ctrl;
  let activeZipObject; let
    closed;

  function next() {
    activeZipIndex++;
    activeZipObject = files[filenames[activeZipIndex]];
    if (activeZipObject) processNextChunk();
    else if (closed) closeZip();
  }

  const zipWriter = {
    enqueue(fileLike) {
      if (closed) throw new TypeError('Cannot enqueue a chunk into a readable stream that is closed or has been requested to be closed');

      let name = fileLike.name.trim();
      const date = new Date(typeof fileLike.lastModified === 'undefined' ? Date.now() : fileLike.lastModified);

      if (fileLike.directory && !name.endsWith('/')) name += '/';
      if (files[name]) throw new Error('File already exists.');

      const nameBuf = encoder.encode(name);
      filenames.push(name);

      const zipObject = files[name] = {
        level: 0,
        ctrl,
        directory: !!fileLike.directory,
        nameBuf,
        comment: encoder.encode(fileLike.comment || ''),
        compressedLength: 0,
        uncompressedLength: 0,
        writeHeader() {
          const header = getDataHelper(26);
          const data = getDataHelper(30 + nameBuf.length);

          zipObject.offset = offset;
          zipObject.header = header;
          if (zipObject.level !== 0 && !zipObject.directory) {
            header.view.setUint16(4, 0x0800);
          }
          header.view.setUint32(0, 0x14000808);
          header.view.setUint16(6, (((date.getHours() << 6) | date.getMinutes()) << 5) | date.getSeconds() / 2, true);
          header.view.setUint16(8, ((((date.getFullYear() - 1980) << 4) | (date.getMonth() + 1)) << 5) | date.getDate(), true);
          header.view.setUint16(22, nameBuf.length, true);
          data.view.setUint32(0, 0x504b0304);
          data.array.set(header.array, 4);
          data.array.set(nameBuf, 30);
          offset += data.array.length;
          ctrl.enqueue(data.array);
        },
        writeFooter() {
          const footer = getDataHelper(16);
          footer.view.setUint32(0, 0x504b0708);

          if (zipObject.crc) {
            zipObject.header.view.setUint32(10, zipObject.crc.get(), true);
            zipObject.header.view.setUint32(14, zipObject.compressedLength, true);
            zipObject.header.view.setUint32(18, zipObject.uncompressedLength, true);
            footer.view.setUint32(4, zipObject.crc.get(), true);
            footer.view.setUint32(8, zipObject.compressedLength, true);
            footer.view.setUint32(12, zipObject.uncompressedLength, true);
          }

          ctrl.enqueue(footer.array);
          offset += zipObject.compressedLength + 16;
          next();
        },
        fileLike
      };

      if (!activeZipObject) {
        activeZipObject = zipObject;
        processNextChunk();
      }
    },
    close() {
      if (closed) throw new TypeError('Cannot close a readable stream that has already been requested to be closed');
      if (!activeZipObject) closeZip();
      closed = true;
    }
  };

  function closeZip() {
    let length = 0;
    let index = 0;
    let indexFilename; let
      file;
    for (indexFilename = 0; indexFilename < filenames.length; indexFilename++) {
      file = files[filenames[indexFilename]];
      length += 46 + file.nameBuf.length + file.comment.length;
    }
    const data = getDataHelper(length + 22);
    for (indexFilename = 0; indexFilename < filenames.length; indexFilename++) {
      file = files[filenames[indexFilename]];
      data.view.setUint32(index, 0x504b0102);
      data.view.setUint16(index + 4, 0x1400);
      data.array.set(file.header.array, index + 6);
      data.view.setUint16(index + 32, file.comment.length, true);
      if (file.directory) {
        data.view.setUint8(index + 38, 0x10);
      }
      data.view.setUint32(index + 42, file.offset, true);
      data.array.set(file.nameBuf, index + 46);
      data.array.set(file.comment, index + 46 + file.nameBuf.length);
      index += 46 + file.nameBuf.length + file.comment.length;
    }
    data.view.setUint32(index, 0x504b0506);
    data.view.setUint16(index + 8, filenames.length, true);
    data.view.setUint16(index + 10, filenames.length, true);
    data.view.setUint32(index + 12, length, true);
    data.view.setUint32(index + 16, offset, true);
    ctrl.enqueue(data.array);
    ctrl.close();
  }

  function processNextChunk() {
    if (!activeZipObject) return;
    if (activeZipObject.directory) return activeZipObject.writeFooter(activeZipObject.writeHeader());
    if (activeZipObject.reader) return pump(activeZipObject);
    if (activeZipObject.fileLike.stream) {
      activeZipObject.crc = new Crc32();
      activeZipObject.reader = activeZipObject.fileLike.stream().getReader();
      activeZipObject.writeHeader();
    } else next();
  }
  return new ReadableStream({
    start: (c) => {
      ctrl = c;
      underlyingSource.start && Promise.resolve(underlyingSource.start(zipWriter));
    },
    pull() {
      return processNextChunk() || (
        underlyingSource.pull
          && Promise.resolve(underlyingSource.pull(zipWriter))
      );
    }
  });
}

// window.ZIP = createWriter;

Then

import axios from 'axios';
import StreamSaver from 'streamsaver';
import ZIP from '@/utils/zip-stream';

getFile = url => new Promise((resolve, reject) => {
  axios({
    method: 'get',
    url,
    responseType: 'blob',
    crossOrigin: true,
    withCredentials: true
  }).then((data) => {
    resolve(data.data);
  }).catch((error) => {
    reject(error.toString());
  });
})

download = () => {
  const promises = [];

  filesArr.forEach((item) => {
    const promise = this.getFile(item.fileUrl).then((data) => { // 下载文件
      // 这里可以做一些进度条处理之类的
    });
    promises.push(promise);
  });


  Promise.all(promises).then(() => {
    zip.generateAsync({ type: 'blob' }).then((content) => { // 生成二进制流
      const fileStream = StreamSaver.createWriteStream(`压缩包.zip`);

      const readableZipStream = new ZIP({
        start(ctrl) {
          cache.forEach(file => ctrl.enqueue(file));
          ctrl.close();
        }
      });
      if (window.WritableStream && readableZipStream.pipeTo) {
        return readableZipStream.pipeTo(fileStream)
      }
    });
  }).catch((e) => {
    console.log('file download failed', e);
  });
}

downloadSingleFile = (name, url) => {
  this.getFile(url).then((data) => {
    const stream = data.stream();
    const fileStream = StreamSaver.createWriteStream(`${name}`);
    if (window.WritableStream && stream.pipeTo) {
      return stream.pipeTo(fileStream);
    }
  }).catch((e) => {
    console.log('file download failed', e);
  });
}

自己项目还是选择使用的filesaver的方式,因为文件总和不会很大,再就是StreamSaver会使用github做一次中转,看起来比较奇怪

  1. 其实下载单个文件的话,可以使用a标签的download属性直接实现

注:filesaverjs 在不同浏览器中能够下载的文件大小情况可以看下面文章https://blog.csdn.net/chixian4839/article/details/100728705

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值