文件打包为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做一次中转,看起来比较奇怪
- 其实下载单个文件的话,可以使用a标签的download属性直接实现
注:filesaverjs 在不同浏览器中能够下载的文件大小情况可以看下面文章https://blog.csdn.net/chixian4839/article/details/100728705