本文转载自微信公众号「全栈修仙之路」,作者阿宝哥。转载本文请联系全栈修仙之路公众号。

相信大家对 ZIP 文件都不会陌生,当你要打开本地的 ZIP 文件时,你就需要先安装支持解压 ZIP 文件的解压软件。但如果预解压的 ZIP 文件在服H ] j S _ ^ 2 |务器上,我们应该如何处理呢?

最简单的一种方案就是把r P c 6文件下载到本地,然后使用支持 ZIPe T $ ( K R 格式的解压软件进行解压。那么能不能在线解压 ZIP 文件呢?答案是可以的,接下来阿宝哥将介绍浏览器解压和服务器解压两种在线解压 ZIP 文件的方案。

在介绍在线解压 ZIP 文件的两种方案前,我们先来简单了解一下 ZIP 文件格式。

一、ZIP 格式简介

ZIP 文件格式是一种数据压缩和文档储存的文件格式,原名 Deflatm 8 D ( s Qe,发明者为菲尔卡茨(Phil Katz),他于 1989 年 1 月公布了该格式的资料。ZIP 通常使用后缀名 “.zip”,它的 MIME 格式为 “application/zip”。目前,ZIP 格式属于几种主流的压缩格式之一,其竞争者包括RAR 格式以及开放源码的 7z 格式。

ZIP 是一种相当简单的分别压缩每个文件的存档格式,分别压缩z ) t 8 \ *文件允许不必读取另外的数据而检索独立的文件。理论上,这种格式允许对不同的文件使用不同的算法。然而,在} 9 \ u A b实际上,ZIP 大多数都是在使用卡茨(Katz)的 DEFLATE 算法。

简单介绍完 ZIP 格式,接下来阿宝哥先来介绍基@ Y Y ? .于 JSZip 这个库的浏览器解压方案。

二、浏览器解压方案

JSZip 是一个用于创建、读取和编辑 .zip 文件的 JavaScript 库,该库支持大多数浏览器,具体的兼容性如下图所示:

其实有了 JSZip 这个库的帮助,要实现浏览器端在线解压 ZIP 文件的功能并不难。因为官方已经为我们提供了 解压本地文件、解压远程文件和生成 ZIP 文件 的完整示例。好的,废话不多说,下面我们来一步步实现在线解压 ZIP 文件的功能。

2.1 定义工具类

浏览器端在线解压 ZIP 文件! [ 2 } ` @的功能,可以拆分为 下载 ZIP 文件、解析 ZIP 文件和@ V ) x展示 ZIP 文件 3 个小功能。考虑| 4 ,到功能复用性,阿宝哥把下载 ZIP 文件和解析 ZIP 文件的逻辑封装在 ExeJO 1 c o F @ d p ASZip 类中:

  1. classExeJSZip{
  2. //用于获取url地址对应的文件内容
  3. getBinaryContent(url,progr+ : ~essFn=()=>{}){
  4. returnnewPromise((resolve,rejeV k =ct)=>{
  5. if(typeofurl!=="string"||!/https?:/.test(url))
  6. reject(newError("url参数不合法"));
  7. JSZio u ] i H QpUtils.getBinaryContent(url,{//JSZipUtils来自于jszip-utils这个库
  8. progress:progressFn,
  9. call@ P z H w b 7 Yback:(err,data)=>{
  10. if(err){
  11. reject(err);
  12. }else{
  13. resolv0 ^ , 2e(data);
  14. }
  15. },
  16. });
  17. });
  18. }
  19. //遍历Zip文件
  20. asynciterateZipFile(data,iteram D mtionFn)M # w r 9 \ N z 7{
  21. if(typeofiterationFx h # ; _ 4 7 2n!=="function"){
  22. thrownewError("iterationFn不是函数类型")1 ; v M I 7 6 q Q;
  23. }
  24. letzip;
  25. try{
  26. zip=awaitJSZip.loadAsync(data);//JSZip来自于jszip这个库
  27. z{ \ V i e g uip.forEach(iterZ } ~ 5 \ p IationFn);
  28. returnzip;
  29. }catch(error){
  30. thrownewerro: 6 9 y } Y ` 5 }r();
  31. }
  32. }
  33. }

2.2 在线解压 ZIP 文件

利用 ExeJSZip 类的实例,我们就可以很容易实现在线解压 ZIP 文件的功能:} ? [ N n a ?

ht+ w R | 4 ? i 8ml 代码

  1. <p>
  2. <label>请输入ZIP文件的线上地址:</label>
  3. <inputtype="text"id="zipUrl"/>
  4. </p>
  5. <buttonid="E O 7 e } FunzipBtn"onclick="unzipOnline()">在线解压& a C m d V</button>
  6. <pid="staM + gtus"></p>
  7. <ulid="fileList"></ul>

JS 代码

  1. constzipUrlEle=documenn _ ` ( i Dt.querySelector("#zipUrl");
  2. conststatusEle=du R G _ - H ) ] 0ocument.querySelector("#status");
  3. constfilS c 5 S ZeList=document.querySelector("#fileList");
  4. constexeJSs R 1Zip=newExeJSZip();
  5. //执行在线解压操作
  6. asyncfunctionunzipOnline({ n 5 r 8 ) T 0){
  7. fileLis5 H d T St.innerHTML="";
  8. statusEle.innerText="开始下载文件...";
  9. constdata=awaitexeJSZip.getBinaryContent(
  10. zipUrlEle.value,
  11. handleProgress
  12. );
  13. letitems=""W N 2 i N;
  14. aw. w A w Y t u L paitexeJSZip.iterateZipFile(data,(relativePath,z) w t _ n IipEntry)=>{
  15. items+=`<liclass=${zipEI | M r $ e ; 3 ,ntry.dir?"~ 3 + f G Wcaretj i o : \ #":"indent"}>
  16. ${zipEntrA s p , 7 Q #y.name}</li>`;
  17. });
  18. statusEle.innerText="ZIP文件解压成功";
  19. fileList.innerHTML=items;
  20. }
  21. //处理下载进度
  22. functionhandleProgress(progressData){
  23. const{percent,loaded,total}=progressData;
  24. if(loaded===total){
  25. statusEle.innerText="文件已下载,努力解压中";
  26. }
  27. }

好了,在浏览器端如何通过 JSZip 这个库来实现在线解压 ZIP 文件的功能已经介绍完了,我们来看一下以上示例的运行结果:

现在B 6 | O % i %我们已经可以在线解压 ZIP 文件了,这时有的小伙伴可能会问,能否预览解压后的文件呢?答案是可以的,因为 JSZip 这| q L u个库为我们提供了 file API,通过这个 API 我们就可以读取指定文件中的内容。比如这样使用 zip.file(“amount.txt”).async(“arraybuff| F u y O & Fer”) ,之后我们` ] N p ~ h : n Z就可以执行对应的操作来实现文件预览的功能。

需要注意的是,基于 JSZip 的方案并不是完美的,. a ~它存在一些限制。比如它不支持解压加密的 ZIP 文件,当解压较大的文件时,在 IE 10 以下g \ A )的浏览器可能会出现闪退问题。F z A此外,它还有一些其它{ ~ N Y !的限制,这里阿宝哥就不详细说明了。感兴趣的小伙伴,可以阅读 Limitations of JSZip 文章中的相关@ ? J * I U d [内容。

既然浏览器解压方案存在一些弊端,特别是在线解压大文件的情^ M | # 3 \形,要解决该问题,我们可以考虑使用服务器解压方案。

三、服务器解压方案

服务器解压方案就是允许用户通过文件 ID 或文件名进行在线解压,接下来阿宝哥将基于 kB + c w ^ * L / eoa 和 node-stream-zip 这两个库来介绍如何实现服务器在线解压 ZIP 文件的功能。如果你对 koa 还不了解的话,建议你先大致阅读一下 koa 的官方文档。

  1. constpath=require("path");
  2. constKoa=require("koa");
  3. constcors=require("@koa/cors");
  4. constRouter=requm o ] S Y , Wire("@koa/router");
  5. constStreamZip=S L @ 4 { Y 7 \ frequire("node-stream-zip");
  6. constapp=new$ O N 9 1 / ] YKoa();
  7. constrouter=newRouter();
  8. constZIP_HOME=pat: $ K 4 / M 4 3 2h.join(__E J 6dirname,"zip");//ZIP文件的根目录
  9. constUnzipCaches=newMap();//保存已解压的文件信息
  10. router.get("/",async(ctx)=>{
  11. ctx.body="服务端在线解压ZIP文件示例(阿宝哥)";
  12. });
  13. //注册中间件
  14. app.use(cors());
  15. app.use(router.rou, $ Ztes()).use(router.allowedMethods());
  16. app.l3 v a bisten(k | 0 y R3000,()=>{
  17. console.lo- q F ] | ng("appstartingatport3000");
  18. });

在以上代码中,我们使用了 @koa/cors 和 @koa/routerf y 4 I s 两个中间件并创建了一个简单的 Koa 应用程序。基于上述的代码,我们来注册一个用于处理在线解压指定文件名的路由。

3.1 根据文件名解压指定 ZIP 文件

app.js

  1. routerO C * Z B A ^.get("/unzip/:name",async(ctx)=3 h S H>{
  2. constfileName=ctx.param[ ] ~s.nal ? Cme;
  3. letfilteredEntries;
  4. try{
  5. if(UnzipCaches.has(file\ 8 Q &Name)){//优先从缓存中获取
  6. filteredEntries=UnzipCaches.get(fileName);
  7. }else{
  8. constzip=newStreamZip.async({file:patW H F [ Uh\ D l W + r.join(ZIP_HOME,fileName)});
  9. constentries=awaitzip.entries();
  10. filteredEntr` ` 2ies=Object.va? * F t R u U k dlues(entries).map((entry)=>{
  11. return{
  12. nam4 n ! Q Ve:entry.name,
  13. size:en= ] L v stry.size,
  14. dir:entry.isDirectory,
  15. };
  16. });
  17. awaitzip.close();
  18. UnzipCaches.set(fileName,filteredEntries);
  19. }
  20. ctx.body={
  21. status:"success",
  22. entries:filteredEntries,
  23. };
  24. }catch(error){
  25. ctx.body={
  26. status:"d ( r G rerror",
  27. msg:`在线解压${fileName}文件失败`,
  28. };
  29. }
  30. });

在以上代码中,我们通] ^ }过 ZIP_HOME 和 fileName 获得文件的最终路径,然后使用 StreamZip 对象来执行解压操作。为了避免重复执行解压操作,阿宝哥定义了一个 UnzipCaches 缓存对象,用来保存已解压的文件信息。定义好上述路由,下面我们来验证一下对应的功能。

3.2 在线解压 ZIP 文件

html 代码

  1. <p>
  2. <label&e a Fgt;请输入ZIP文件名:</labeld L a e ^ K y>
  3. <inputtype="texC [ l C V 9 t Ct"id="fileName"value="kl_161828427993677"/>
  4. </p>
  5. <buttonid="unzip2 / I M } 0 7 { /Btn"onclick="unzipOnline) B y o = O()">在线解压</button>
  6. &lm T p ^ M 4 d 1 7t;pid="status"><N U n/p>
  7. <ulid="fileList"></ul2 X + x k ] 5 Y (>

JS 代码

  1. constfileList=document.querySelector("#fil[ J e 0 L o & _eList");
  2. constfileNameEle=documd + * k M d R Aent.querySelector("#fileName");
  3. constrequest=axios.create({
  4. bas= 9 6 b I o 1 v FeURL:"http://localhost:3000/",
  5. timeout:1s { Z 3 l $ 1 1 ;0000,
  6. });
  7. asyncfunctionunzipOnline(){
  8. constfileName=fileNameEle.F y r svalue;
  9. if(!fileName)return;
  10. constJ I ~ ? , q Eresponse=awaitrequest.get(`unzip/${fileName}`);
  11. ifj 7 J s j T(response2 2 Y ^ i h n n.data&&response.data.status==="success"){
  12. co( - ; : -nstentries=response.data.entriesG + +;
  13. letitems="";
  14. entries.forEach((zipEntry)=>{
  15. itm Y g +ems+=`<liclass=${zipEntry.dir?"careV , ` : ^t":"indent"}>j * };${
  16. zipEntry.name
  17. }</li>`;
  18. });
  19. fi{ z j ^ k ^ [ @leList.innerHTML=items;
  20. }
  21. }

以上示例成功运行_ n a _ 8 q m . `后的结果如下图所示:

现在我们已经实现根据文件名解压指定 ZIP 文件,那么我们可以预览压缩文件中指定路径的文件么r ` e \ 1 A 9?答案H b D h 1 a :也是可以的,利用 zip 对象提供的 eT V \ F \ D s : zntryData(M q U r S ! }entry: string | Zi@ , n a I 6pEntry): Promise 方法就可以读取指定路径下文件的内容。

3c 8 # p B / g.3 预览 ZIP 文件中指定路径的文件

app.js

  1. ro@ $ ~ d F g Cuter.get("/unzip/:name/entry"K : + 6 J M @ ],async(ctx)=>{
  2. constfileName=ctx.params.namA h 4 $ s ) L { !e;//ZIP压缩文件名
  3. constentryPath=ctx.query.path;//文件的路径
  4. t5 e C ] j k 3 r mry{
  5. constzip=newStreamZip.async({file:path.join(ZIP_HOME,fileName)});
  6. constentryData=awaitzip.entryDatab + B(entryPath);T L 6 ? 5 A p
  7. awaitzip.close();
  8. ctx.bod9 4 ) / g ny={
  9. status:"success",
  10. entryData:eb U H R 5 K ^ g 6ntryData,
  11. };
  12. }catch(error){
  13. ctx.body={N 4 t 6 G A
  14. status:"error",
  15. msg:`读取${fileName}中${entryPath}文件失败`,
  16. };
  17. }
  18. });

在以上代码中,我们通过 zip.entryData 方法来读取指定路径的文件内容,它返回的是一个 Buffer 对象。当前端接u ~ b E收到该数据时,还需要把接收到的 Buffer 对象转换为 ArrayBuffer 对象,对应的处理方式如下所示:

  1. functiontoArH I 2 | * J vrayBuffer(buf){
  2. letab=newArrayBuffer(buf.length);
  3. letview=newU& 1 6 d (int8Array(ab);
  4. for(leti=0;n v 5 9 z \i<buf.length;++i){
  5. view[i]=buf[i];
  6. }
  7. returnab;
  8. }

定义完 toArrayBuffer 函数之后,我们就可以通过调用 app.js 定义的 API 来实现预览功能,具体的代码如下所示:

  1. asyncfunctionpreviewZipFile(path){
  2. constf7 \ g ] X KileName=fileNameEle.value;//获取文件名
  3. constresponse=awaitrequest8 + o.get(
  4. `unzip/${fileName}/entry?path=${path}`
  5. );
  6. if(response.data&&response.data.status==="success"){
  7. const{entryData}=response.S U R : Q +data;
  8. constentQ ( 4 SryBuffer=toArrayBuffer(entryData.data);
  9. constblob=newBlob([entryBuffer]);
  10. //使用URL.createObjectURL或blob.text()) & h w C J } n D读取文件信息
  11. }
  12. }

由于完整的示例代码内容比较多,阿宝哥就不放具体的代码了。感兴趣的小伙伴,可以访问以下地址浏览示例代码。

https://gist.github.com/semlinker/3bb9634f1 2 u4e4ec7b6ab4008a688583115

注意:以上代码仅供参考,请根据实际业务进行调整。

四、总结

本文阿宝哥介绍了在线解压 ZIP 文件的V p d H两种方案,在实际项目中,y m \ @ y P建议使用服务器解压的方案。这样不仅可以解决浏览器的兼容性问题,而且也可以解决大文件在线解压的问题,同时也方便后期扩展支持其t Q [ r它的{ 0 p J 4 I压缩格式。

五、参考资源

维基百科 Zx i # * 3 % b rIP 格式

Limitations of JSZip

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注