برای نمونه کدهای این مطلب از TypeScript و ESM استفاده کردم و نسخه کامل کدها داخل گیتهاب در دسترسه.
فشردهسازی و ایجاد فایل Zip
برای فشردهسازی فایلها از پکیج archiver استفاده میکنیم که پکیج معروفی برای ایجاد فایل Zip تو Node.js هستش.
پکیج رو ایمپورت میکنیم و یه نمونه (instance) با کانفیگ پیش فرض و فرمت zip ازش میسازیم. حالا تنها کاری که نیازه انجام بدیم مشخص کردن فایلها است که میشه مستقیم آدرس یک فایل رو بدیم، آدرس یه پوشه بدیم و کل فایلهای پوشه رو برامون اضافه کنه یا اینکه از glob استفاده کنیم تا یه پترن و الگو رو برای آدرس و نوع فایلها مشخص کنیم.
نمونه کد:
import archiver from "archiver";
const archive = archiver('zip');
archive.file("./exampleDir/example.png", { name: "example.png" });
archive.directory('dirPath/', 'inZipDirPath');
archive.directory('dirPath/', false); // add to root level of archived file
archive.glob('file*.txt');
await archive.finalize(); // شروع عملیات زیپ کردن
تا اینجا، فایل زیپ رو درست میکنه اما جایی ذخیره نمیکنه. برای اینکه فایلش ذخیره بشه از file stream خود node استفاده میکنیم و با دستور pipe خروجی stream آرشیو رو به file system وصل میکنیم:
import fs from "node:fs";
const outputFileStream = fs.createWriteStream("./temp/compressed.zip");
const archive = archiver('zip');
archive.pipe(outputFileStream);
archive.file("./exampleDir/example.png", { name: "example.png" });
archive.on("finish", () => {
console.info("Zip process finished"); // اینجا پروسه زیپ کردن تموم شده و کافیه استریم رو ببندیم
archive.end(); // بستن استریم
});
outputFileStream.on("finish", () => {
console.info("File saved"); // اینجا استریم بسته و فایل ذخیره شده
});
await archive.finalize();
ایجاد فایل Zip با پسورد
برای اینکه به فایلمون رمز اضافه کنیم باید از یه متد رمزنگاری استفاده کنیم. پکیج archiver توسعه پذیره و با استفاده از پکیج archiver-zip-encrypted میتونیم بهش فرمت جدید اضافه کنیم تا بتونیم از رمزنگاری استفاده کنیم.
متاسفانه برای این پکیج data-typeها تعریف نشدن و هرچند بهترین راه حل نیست اما با اضافه کردن declare module 'archiver-zip-encrypted';
میشه ارورهای تایپ اسکریپت رو برطرف کرد.
بعد از نصب پکیج کافیه فرمت جدید رو به archiver معرفی کنیم و موقع ساختن instance بهش بگیم که از چه فرمتی میخوایم:
import archiverZipEncrypted from "archiver-zip-encrypted";
import archiver, { type ArchiverOptions, type Format } from "archiver";
const format = "zip-encrypted" as Format; // نام فرمت جدیدی که میخوایم اضافه کنیم
// ثبت پکیج جدید با نام فرمت - به ازای هر پروسس نود یکبار این ثبت رو انجام بدید
archiver.registerFormat(format, archiverZipEncrypted);
const archive = archiver(format, { // بجای زیپ از فرمت جدید استفاده میکنیم
encryptionMethod: "zip20", // نوع رمزگذاری - این نوع مرسومه اما امن نیست
password: "t3st" // رمز
} as ArchiverOptions);
/*
ادامه
*/
خواندن فایل Zip
برای خوندن فایل زیپ و فشرده شده از پکیج unzipper استفاده میکنیم. این ماژول امکان خوندن لیست و مقدار فایلهای داخل zip رو از طریق آدرس یا بافر فایل فشرده شده به ما میده. برای مثال اگر بخوایم فقط یک فایل رو بخونیم:
import unzipper from "unzipper";
const rootDirectory = await unzipper.Open.file("./file.zip"); // خوندن لیست کامل فایلهای داخل زیپ
const file = rootDirectory.files.find(file => file.path === 'package.json'); // پیدا کردن فایل مورد نظر
const data = await file.buffer("password"); // مقدار رمز - اگر رمز نداشته باشه میتونید پاس ندید
اگر بخوایم کل فایل رو اکسترکت کنیم و رمز نداشته باشه:
import fs from "node:fs";
import unzipper from "unzipper";
const outputFileStream = fs.createReadStream("./temp/compressed.zip");
outputFileStream.on('close', () => {
console.info('Done'); // اتمام عملیات
});
outputFileStream.pipe(unzipper.Extract({
path: "./temp/extracted",
}));
ولی اگر رمزگذاری شده باشن باید کد رو شخصیسازی کنیم. با فرض اینکه همهی رمزها یکی باشن، ۱- اطلاعات کل فایلها و مسیرها رو میخونیم، ۲- پوشهها و فایلها رو فیلتر میکنیم و به ترتیب، اول ۳- پوشهها رو ایجاد میکنیم، ۴- بعدش فایلها رو تک تک میخونیم و تو مسیر مورد نظر ذخیره میکنیم:
import fs from "node:fs/promises";
import path from "node:path";
import unzipper from "unzipper";
const password = "t3st";
const outputDirectory = "./temp/extracted";
const rootDirectory = await unzipper.Open.file("./temp/compressed-encrypted.zip");
const directories = rootDirectory.files.filter(item => item.type === 'Directory');
const files = rootDirectory.files.filter(item => item.type === 'File');
for (const directory of directories) {
await fs.mkdir(path.join(outputDirectory, directory.path), { recursive: true });
}
for (const file of files) {
const data = await file.buffer(password);
await fs.writeFile(path.join(outputDirectory, file.path), data);
}