Archiver zip打包

说在前面

由于现有的上传下载服务实现不合理——在内存中暂存完整内容。只要并发稍微高一点,内存占用就疯狂报警;除此之外,还伴随着段错误,服务重构迫在眉睫。
NodeJS 中有个zlib模块提供压缩功能,但是没有找到支持多文件压缩的文档。最后找到了archiver模块,支持zip及tar格式的压缩,使用起来也很简单。

Archiver 介绍

基础事件

close

listen for all archive data to be written
‘close’ event is fired only when a file descriptor is involved

监听要写入的所有存档数据,当涉及到文件描述符时事件会触发——一般来说,当可写数据完毕后,close事件会被触发

end

This event is fired when the data source is drained no matter what was the data source.
It is not part of this library but rather from the NodeJS Stream API.
@see: https://nodejs.org/api/stream.html#stream_event_end

这个事件很奇怪,在NodeJS可写流相关文档中,没有找到end事件相关的文档;同时在end event上方的说明中,有一个文档——在一个可读流中,如果没有更多的数据流出,end事件将会被触发。

这里突然想到,如果在压缩时,有一个转换流——当数据流完之后,end 事件会被触发
PS:https://github.com/JianmingXia/StudyTest/blob/master/Archiver/endEventFired.js

Archiver 事件

error

https://archiverjs.com/docs/lib_core.js.html
在此文档搜索ArchiverError即可,可以发现有很多触发error事件的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* Error listener that re-emits error on to our internal stream.
*
* @private
* @param {Error} err
* @return void
*/
Archiver.prototype._onModuleError = function(err) {
/**
* @event Archiver#error
* @type {ErrorData}
*/
this.emit('error', err);
};

比如调用finalize方法,依然调用append方法,此时error方法会被触发:

1
2
3
4
5
archive.append("", { name: "sdffsdfsd/" });

archive.finalize();

archive.append("", { name: "sdffsdfsd/" });

PS:https://github.com/JianmingXia/StudyTest/blob/master/Archiver/errorEventFired.js

warning

https://archiverjs.com/docs/lib_core.js.html
在此文档搜索ArchiverError即可,可以发现有很多触发warning事件的情况

entry

每个正常被append的item都会触发
PS:https://github.com/JianmingXia/StudyTest/blob/master/Archiver/entryAndProgressEvent.js

1
2
3
4
5
6
7
8
9
10
11
/**
* Fires when the entry's input has been processed and appended to the archive.
*
* @event Archiver#entry
* @type {EntryData}
*/
this.emit('entry', data);
this._entriesProcessedCount++;
if (data.stats && data.stats.size) {
this._fsEntriesProcessedBytes += data.stats.size;
}

image.png

progress

每个正常被append的item都会触发
PS:https://github.com/JianmingXia/StudyTest/blob/master/Archiver/entryAndProgressEvent.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @event Archiver#progress
* @type {ProgressData}
*/
this.emit('progress', {
entries: {
total: this._entriesCount,
processed: this._entriesProcessedCount
},
fs: {
totalBytes: this._fsEntriesTotalBytes,
processedBytes: this._fsEntriesProcessedBytes
}
});

image.png

Zip 配置

image.png

zlib

image.png

比如 level 可配置项:

1
2
3
4
zlib.constants.Z_NO_COMPRESSION
zlib.constants.Z_BEST_SPEED
zlib.constants.Z_BEST_COMPRESSION
zlib.constants.Z_DEFAULT_COMPRESSION

更多参数可见文档。

forceLocalTime

1
2
3
4
const archive = archiver('zip', {
zlib: { level: ZLIB_LEVEL.Z_BEST_COMPRESSION },
forceLocalTime: true,
});

forceLocalTime 设置为true,即可解决下面的问题:

image.png

Demo

append

追加一个文件流

1
archive.append(fs.createReadStream(__dirname + "/sourceFiles/1.txt"), { name: "file1.txt" });

追加一个string

1
archive.append("string cheese!", { name: "file2.txt" });

追加一个buffer

1
2
var buffer = Buffer.from("buff it!");
archive.append(buffer, { name: "file3.txt" });

新增空文件夹

1
archive.append("", { name: "empty-dir/" });

file

使用file追加一个文件

1
archive.file("sourceFiles/1.txt", { name: "file4.txt" });

directory

以下三种方式比较类似,只是会有微小差别

追加一个目录(子目录也会被增加)

1
archive.directory("sourceFiles/", "new-subdir");

追加一个目录——类似于直接复制

1
archive.directory("sourceFiles/");

将目标目录中的文件追加至根目录

1
archive.directory("sourceFiles/", false);

glob

使用通配符匹配

1
archive.glob("*.js");

完整代码

https://github.com/JianmingXia/StudyTest/tree/master/Archiver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
var fs = require("fs");
var archiver = require("archiver");

var output = fs.createWriteStream(__dirname + "/example.zip");
var archive = archiver("zip", {
zlib: { level: 9 },
comment: "testtest"
});

output.on("close", function() {
console.log(archive.pointer() + " total bytes");
console.log(
"archiver has been finalized and the output file descriptor has closed."
);
});

archive.on("warning", function(err) {
console.log(err);
});

// good practice to catch this error explicitly
archive.on("error", function(err) {
console.log(err);
});

archive.pipe(output);

// 追加一个文件流
archive.append(fs.createReadStream(__dirname + "/sourceFiles/1.txt"), { name: "file1.txt" });

// 追加一个string
archive.append("string cheese!", { name: "file2.txt" });

// 追加一个buffer
var buffer = Buffer.from("buff it!");
archive.append(buffer, { name: "file3.txt" });

// 追加一个现有文件
archive.file("sourceFiles/1.txt", { name: "file4.txt" });

// 追加一个目录(子目录也会被增加)
archive.directory("sourceFiles/", "new-subdir");

// 追加一个目录——类似于直接复制
archive.directory("sourceFiles/");

// 将目标目录中的文件追加至根目录
archive.directory("sourceFiles/", false);

// 新增空文件夹
archive.append('', { name: "empty-dir/" });

// 使用通配符
archive.glob("*.js");

archive.finalize();

打包后的目录结构

image.png

资料