JavaScript 如何在線解壓 ZIP 文件?

相信你們對 ZIP 文件都不會陌生,當你要打開本地的 ZIP 文件時,你就須要先安裝支持解壓 ZIP 文件的解壓軟件。但若是預解壓的 ZIP 文件在服務器上,咱們應該如何處理呢?最簡單的一種方案就是把文件下載到本地,而後使用支持 ZIP 格式的解壓軟件進行解壓。那麼能不能在線解壓 ZIP 文件呢?答案是能夠的,接下來阿寶哥將介紹瀏覽器解壓和服務器解壓兩種在線解壓 ZIP 文件的方案。javascript

在介紹在線解壓 ZIP 文件的兩種方案前,咱們先來簡單瞭解一下 ZIP 文件格式。html

1、ZIP 格式簡介

ZIP 文件格式是一種數據壓縮和文檔儲存的文件格式,原名 Deflate,發明者爲菲爾·卡茨(Phil Katz),他於 1989 年 1 月公佈了該格式的資料。ZIP 一般使用後綴名 「.zip」,它的 MIME 格式爲 「application/zip」。目前,ZIP 格式屬於幾種主流的壓縮格式之一,其競爭者包括RAR 格式以及開放源碼的 7z 格式。前端

ZIP 是一種至關簡單的分別壓縮每一個文件的存檔格式,分別壓縮文件容許沒必要讀取另外的數據而檢索獨立的文件。理論上,這種格式容許對不一樣的文件使用不一樣的算法。然而,在實際上,ZIP 大多數都是在使用卡茨(Katz)的 DEFLATE 算法。java

簡單介紹完 ZIP 格式,接下來阿寶哥先來介紹基於 JSZip 這個庫的瀏覽器解壓方案。node

關注「全棧修仙之路」閱讀阿寶哥原創的 4 本免費電子書(累計下載 3萬+)及 11 篇 Vue 3 進階系列教程。ios

2、瀏覽器解壓方案

JSZip 是一個用於建立、讀取和編輯 .zip 文件的 JavaScript 庫,該庫支持大多數瀏覽器,具體的兼容性以下圖所示:git

其實有了 JSZip 這個庫的幫助,要實現瀏覽器端在線解壓 ZIP 文件的功能並不難。由於官方已經爲咱們提供了 解壓本地文件、解壓遠程文件和生成 ZIP 文件 的完整示例。好的,廢話很少說,下面咱們來一步步實如今線解壓 ZIP 文件的功能。github

2.1 定義工具類

瀏覽器端在線解壓 ZIP 文件的功能,能夠拆分爲 下載 ZIP 文件、解析 ZIP 文件和展現 ZIP 文件 3 個小功能。考慮到功能複用性,阿寶哥把下載 ZIP 文件和解析 ZIP 文件的邏輯封裝在 ExeJSZip 類中:算法

class ExeJSZip {
  // 用於獲取url地址對應的文件內容
  getBinaryContent(url, progressFn = () => {}) {
    return new Promise((resolve, reject) => {
      if (typeof url !== "string" || !/https?:/.test(url))
        reject(new Error("url 參數不合法"));
      JSZipUtils.getBinaryContent(url, { // JSZipUtils來自於jszip-utils這個庫
        progress: progressFn,
        callback: (err, data) => {
          if (err) {
            reject(err);
          } else {
            resolve(data);
          }
        },
      });
    });
  }
  
  // 遍歷Zip文件
  async iterateZipFile(data, iterationFn) {
    if (typeof iterationFn !== "function") {
      throw new Error("iterationFn 不是函數類型");
    }
    let zip;
    try {
      zip = await JSZip.loadAsync(data); // JSZip來自於jszip這個庫
      zip.forEach(iterationFn);
      return zip;
    } catch (error) {
      throw new error();
    }
  }
}
複製代碼

2.2 在線解壓 ZIP 文件

利用 ExeJSZip 類的實例,咱們就能夠很容易實如今線解壓 ZIP 文件的功能:axios

html 代碼
<p>
  <label>請輸入ZIP文件的線上地址:</label>
  <input type="text" id="zipUrl" />
</p>
<button id="unzipBtn" onclick="unzipOnline()">在線解壓</button>
<p id="status"></p>
<ul id="fileList"></ul>
複製代碼
JS 代碼
const zipUrlEle = document.querySelector("#zipUrl");
const statusEle = document.querySelector("#status");
const fileList = document.querySelector("#fileList");
const exeJSZip = new ExeJSZip();

// 執行在線解壓操做
async function unzipOnline() {
  fileList.innerHTML = "";
  statusEle.innerText = "開始下載文件...";
  const data = await exeJSZip.getBinaryContent(
    zipUrlEle.value,
    handleProgress
  );
  let items = "";
  await exeJSZip.iterateZipFile(data, (relativePath, zipEntry) => {
    items += `<li class=${zipEntry.dir ? "caret" : "indent"}> ${zipEntry.name}</li>`;
  });
  statusEle.innerText = "ZIP文件解壓成功";
  fileList.innerHTML = items;
}

// 處理下載進度
function handleProgress(progressData) {
  const { percent, loaded, total } = progressData;
  if (loaded === total) {
    statusEle.innerText = "文件已下載,努力解壓中";
  }
}
複製代碼

好了,在瀏覽器端如何經過 JSZip 這個庫來實如今線解壓 ZIP 文件的功能已經介紹完了,咱們來看一下以上示例的運行結果:

如今咱們已經能夠在線解壓 ZIP 文件了,這時有的小夥伴可能會問,可否預覽解壓後的文件呢?答案是能夠的,由於 JSZip 這個庫爲咱們提供了 file API,經過這個 API 咱們就能夠讀取指定文件中的內容。好比這樣使用 zip.file("amount.txt").async("arraybuffer") ,以後咱們就能夠執行對應的操做來實現文件預覽的功能。

須要注意的是,基於 JSZip 的方案並非完美的,它存在一些限制。好比它不支持解壓加密的 ZIP 文件,當解壓較大的文件時,在 IE 10 如下的瀏覽器可能會出現閃退問題。此外,它還有一些其它的限制,這裏阿寶哥就不詳細說明了。感興趣的小夥伴,能夠閱讀 Limitations of JSZip 文章中的相關內容。

既然瀏覽器解壓方案存在一些弊端,特別是在線解壓大文件的情形,要解決該問題,咱們能夠考慮使用服務器解壓方案。

3、服務器解壓方案

服務器解壓方案就是容許用戶經過文件 ID 或文件名進行在線解壓,接下來阿寶哥將基於 koanode-stream-zip 這兩個庫來介紹如何實現服務器在線解壓 ZIP 文件的功能。若是你對 koa 還不瞭解的話,建議你先大體閱讀一下 koa 的官方文檔。

const path = require("path");
const Koa = require("koa");
const cors = require("@koa/cors");
const Router = require("@koa/router");
const StreamZip = require("node-stream-zip");

const app = new Koa();
const router = new Router();
const ZIP_HOME = path.join(__dirname, "zip"); // ZIP文件的根目錄
const UnzipCaches = new Map(); // 保存已解壓的文件信息

router.get("/", async (ctx) => {
  ctx.body = "服務端在線解壓ZIP文件示例(阿寶哥)";
});

// 註冊中間件
app.use(cors());
app.use(router.routes()).use(router.allowedMethods());

app.listen(3000, () => {
  console.log("app starting at port 3000");
});
複製代碼

在以上代碼中,咱們使用了 @koa/cors@koa/router 兩個中間件並建立了一個簡單的 Koa 應用程序。基於上述的代碼,咱們來註冊一個用於處理在線解壓指定文件名的路由。

3.1 根據文件名解壓指定 ZIP 文件

app.js
router.get("/unzip/:name", async (ctx) => {
  const fileName = ctx.params.name;
  let filteredEntries;
  try {
    if (UnzipCaches.has(fileName)) { // 優先從緩存中獲取
      filteredEntries = UnzipCaches.get(fileName);
    } else {
      const zip = new StreamZip.async({ file: path.join(ZIP_HOME, fileName) });
      const entries = await zip.entries();
      filteredEntries = Object.values(entries).map((entry) => {
        return {
          name: entry.name,
          size: entry.size,
          dir: entry.isDirectory,
        };
      });
      await zip.close();
      UnzipCaches.set(fileName, filteredEntries);
    }
    ctx.body = {
      status: "success",
      entries: filteredEntries,
    };
  } catch (error) {
    ctx.body = {
      status: "error",
      msg: `在線解壓${fileName}文件失敗`,
    };
  }
});
複製代碼

在以上代碼中,咱們經過 ZIP_HOMEfileName 得到文件的最終路徑,而後使用 StreamZip 對象來執行解壓操做。爲了不重複執行解壓操做,阿寶哥定義了一個 UnzipCaches 緩存對象,用來保存已解壓的文件信息。定義好上述路由,下面咱們來驗證一下對應的功能。

3.2 在線解壓 ZIP 文件

html 代碼
<p>
  <label>請輸入ZIP文件名:</label>
  <input type="text" id="fileName" value="kl_161828427993677" />
</p>
<button id="unzipBtn" onclick="unzipOnline()">在線解壓</button>
<p id="status"></p>
<ul id="fileList"></ul>
複製代碼
JS 代碼
const fileList = document.querySelector("#fileList");
const fileNameEle = document.querySelector("#fileName");

const request = axios.create({
  baseURL: "http://localhost:3000/",
  timeout: 10000,
});

async function unzipOnline() {
  const fileName = fileNameEle.value;
  if(!fileName) return;
  const response = await request.get(`unzip/${fileName}`);
  if (response.data && response.data.status === "success") {
    const entries = response.data.entries;
    let items = "";
    entries.forEach((zipEntry) => {
      items += `<li class=${zipEntry.dir ? "caret" : "indent"}>${ zipEntry.name }</li>`;
    });
    fileList.innerHTML = items;
  }
}
複製代碼

以上示例成功運行後的結果以下圖所示:

如今咱們已經實現根據文件名解壓指定 ZIP 文件,那麼咱們能夠預覽壓縮文件中指定路徑的文件麼?答案也是能夠的,利用 zip 對象提供的 entryData(entry: string | ZipEntry): Promise<Buffer> 方法就能夠讀取指定路徑下文件的內容。

3.3 預覽 ZIP 文件中指定路徑的文件

app.js
router.get("/unzip/:name/entry", async (ctx) => {
  const fileName = ctx.params.name; // ZIP壓縮文件名
  const entryPath = ctx.query.path; // 文件的路徑
  try {
    const zip = new StreamZip.async({ file: path.join(ZIP_HOME, fileName) });
    const entryData = await zip.entryData(entryPath);
    await zip.close();
    ctx.body = {
      status: "success",
      entryData: entryData,
    };
  } catch (error) {
    ctx.body = {
      status: "error",
      msg: `讀取${fileName}${entryPath}文件失敗`,
    };
  }
});
複製代碼

在以上代碼中,咱們經過 zip.entryData 方法來讀取指定路徑的文件內容,它返回的是一個 Buffer 對象。當前端接收到該數據時,還須要把接收到的 Buffer 對象轉換爲 ArrayBuffer 對象,對應的處理方式以下所示:

function toArrayBuffer(buf) {
  let ab = new ArrayBuffer(buf.length);
  let view = new Uint8Array(ab);
  for (let i = 0; i < buf.length; ++i) {
    view[i] = buf[i];
  }
  return ab;
}
複製代碼

定義完 toArrayBuffer 函數以後,咱們就能夠經過調用 app.js 定義的 API 來實現預覽功能,具體的代碼以下所示:

async function previewZipFile(path) {
  const fileName = fileNameEle.value; // 獲取文件名
  const response = await request.get(
    `unzip/${fileName}/entry?path=${path}`
  );
  if (response.data && response.data.status === "success") {
    const { entryData } = response.data;
    const entryBuffer = toArrayBuffer(entryData.data);
    const blob = new Blob([entryBuffer]);
    // 使用URL.createObjectURL或blob.text()讀取文件信息
  }
}
複製代碼

因爲完整的示例代碼內容比較多,阿寶哥就不放具體的代碼了。感興趣的小夥伴,能夠訪問如下地址瀏覽示例代碼。

gist.github.com/semlinker/3…

注意:以上代碼僅供參考,請根據實際業務進行調整。

4、總結

本文阿寶哥介紹了在線解壓 ZIP 文件的兩種方案,在實際項目中,建議使用服務器解壓的方案。這樣不只能夠解決瀏覽器的兼容性問題,並且也能夠解決大文件在線解壓的問題,同時也方便後期擴展支持其它的壓縮格式。

關注「全棧修仙之路」閱讀阿寶哥原創的 4 本免費電子書(累計下載 3萬+)及 11 篇 Vue 3 進階系列教程。想一塊兒學習 TS/Vue 3.0 的小夥伴能夠添加阿寶哥微信 —— semlinker

5、參考資源

相關文章
相關標籤/搜索