本文转载自微信公众号「全栈修仙之路」,作者阿宝哥。转载本文请联系全栈修仙之路公众号。
相信大家对 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 类中:
- classExeJSZip{
- //用于获取url地址对应的文件内容
- getBinaryContent(url,progr+ : ~essFn=()=>{}){
- returnnewPromise((resolve,rejeV k =ct)=>{
- if(typeofurl!=="string"||!/https?:/.test(url))
- reject(newError("url参数不合法"));
- JSZio u ] i H QpUtils.getBinaryContent(url,{//JSZipUtils来自于jszip-utils这个库
- progress:progressFn,
- call@ P z H w b 7 Yback:(err,data)=>{
- if(err){
- reject(err);
- }else{
- resolv0 ^ , 2e(data);
- }
- },
- });
- });
- }
- //遍历Zip文件
- asynciterateZipFile(data,iteram D mtionFn)M # w r 9 \ N z 7{
- if(typeofiterationFx h # ; _ 4 7 2n!=="function"){
- thrownewError("iterationFn不是函数类型")1 ; v M I 7 6 q Q;
- }
- letzip;
- try{
- zip=awaitJSZip.loadAsync(data);//JSZip来自于jszip这个库
- z{ \ V i e g uip.forEach(iterZ } ~ 5 \ p IationFn);
- returnzip;
- }catch(error){
- thrownewerro: 6 9 y } Y ` 5 }r();
- }
- }
- }
2.2 在线解压 ZIP 文件
利用 ExeJSZip 类的实例,我们就可以很容易实现在线解压 ZIP 文件的功能:} ? [ N n a ?
ht+ w R | 4 ? i 8ml 代码
- <p>
- <label>请输入ZIP文件的线上地址:</label>
- <inputtype="text"id="zipUrl"/>
- </p>
- <buttonid="E O 7 e } FunzipBtn"onclick="unzipOnline()">在线解压& a C m d V</button>
- <pid="staM + gtus"></p>
- <ulid="fileList"></ul>
JS 代码
- constzipUrlEle=documenn _ ` ( i Dt.querySelector("#zipUrl");
- conststatusEle=du R G _ - H ) ] 0ocument.querySelector("#status");
- constfilS c 5 S ZeList=document.querySelector("#fileList");
- constexeJSs R 1Zip=newExeJSZip();
- //执行在线解压操作
- asyncfunctionunzipOnline({ n 5 r 8 ) T 0){
- fileLis5 H d T St.innerHTML="";
- statusEle.innerText="开始下载文件...";
- constdata=awaitexeJSZip.getBinaryContent(
- zipUrlEle.value,
- handleProgress
- );
- letitems=""W N 2 i N;
- aw. w A w Y t u L paitexeJSZip.iterateZipFile(data,(relativePath,z) w t _ n IipEntry)=>{
- items+=`<liclass=${zipEI | M r $ e ; 3 ,ntry.dir?"~ 3 + f G Wcaretj i o : \ #":"indent"}>
- ${zipEntrA s p , 7 Q #y.name}</li>`;
- });
- statusEle.innerText="ZIP文件解压成功";
- fileList.innerHTML=items;
- }
- //处理下载进度
- functionhandleProgress(progressData){
- const{percent,loaded,total}=progressData;
- if(loaded===total){
- statusEle.innerText="文件已下载,努力解压中";
- }
- }
好了,在浏览器端如何通过 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 的官方文档。
- constpath=require("path");
- constKoa=require("koa");
- constcors=require("@koa/cors");
- constRouter=requm o ] S Y , Wire("@koa/router");
- constStreamZip=S L @ 4 { Y 7 \ frequire("node-stream-zip");
- constapp=new$ O N 9 1 / ] YKoa();
- constrouter=newRouter();
- constZIP_HOME=pat: $ K 4 / M 4 3 2h.join(__E J 6dirname,"zip");//ZIP文件的根目录
- constUnzipCaches=newMap();//保存已解压的文件信息
- router.get("/",async(ctx)=>{
- ctx.body="服务端在线解压ZIP文件示例(阿宝哥)";
- });
- //注册中间件
- app.use(cors());
- app.use(router.rou, $ Ztes()).use(router.allowedMethods());
- app.l3 v a bisten(k | 0 y R3000,()=>{
- console.lo- q F ] | ng("appstartingatport3000");
- });
在以上代码中,我们使用了 @koa/cors 和 @koa/routerf y 4 I s 两个中间件并创建了一个简单的 Koa 应用程序。基于上述的代码,我们来注册一个用于处理在线解压指定文件名的路由。
3.1 根据文件名解压指定 ZIP 文件
app.js
- routerO C * Z B A ^.get("/unzip/:name",async(ctx)=3 h S H>{
- constfileName=ctx.param[ ] ~s.nal ? Cme;
- letfilteredEntries;
- try{
- if(UnzipCaches.has(file\ 8 Q &Name)){//优先从缓存中获取
- filteredEntries=UnzipCaches.get(fileName);
- }else{
- constzip=newStreamZip.async({file:patW H F [ Uh\ D l W + r.join(ZIP_HOME,fileName)});
- constentries=awaitzip.entries();
- filteredEntr` ` 2ies=Object.va? * F t R u U k dlues(entries).map((entry)=>{
- return{
- nam4 n ! Q Ve:entry.name,
- size:en= ] L v stry.size,
- dir:entry.isDirectory,
- };
- });
- awaitzip.close();
- UnzipCaches.set(fileName,filteredEntries);
- }
- ctx.body={
- status:"success",
- entries:filteredEntries,
- };
- }catch(error){
- ctx.body={
- status:"d ( r G rerror",
- msg:`在线解压${fileName}文件失败`,
- };
- }
- });
在以上代码中,我们通] ^ }过 ZIP_HOME 和 fileName 获得文件的最终路径,然后使用 StreamZip 对象来执行解压操作。为了避免重复执行解压操作,阿宝哥定义了一个 UnzipCaches 缓存对象,用来保存已解压的文件信息。定义好上述路由,下面我们来验证一下对应的功能。
3.2 在线解压 ZIP 文件
html 代码
- <p>
- <label&e a Fgt;请输入ZIP文件名:</labeld L a e ^ K y>
- <inputtype="texC [ l C V 9 t Ct"id="fileName"value="kl_161828427993677"/>
- </p>
- <buttonid="unzip2 / I M } 0 7 { /Btn"onclick="unzipOnline) B y o = O()">在线解压</button>
- &lm T p ^ M 4 d 1 7t;pid="status"><N U n/p>
- <ulid="fileList"></ul2 X + x k ] 5 Y (>
JS 代码
- constfileList=document.querySelector("#fil[ J e 0 L o & _eList");
- constfileNameEle=documd + * k M d R Aent.querySelector("#fileName");
- constrequest=axios.create({
- bas= 9 6 b I o 1 v FeURL:"http://localhost:3000/",
- timeout:1s { Z 3 l $ 1 1 ;0000,
- });
- asyncfunctionunzipOnline(){
- constfileName=fileNameEle.F y r svalue;
- if(!fileName)return;
- constJ I ~ ? , q Eresponse=awaitrequest.get(`unzip/${fileName}`);
- ifj 7 J s j T(response2 2 Y ^ i h n n.data&&response.data.status==="success"){
- co( - ; : -nstentries=response.data.entriesG + +;
- letitems="";
- entries.forEach((zipEntry)=>{
- itm Y g +ems+=`<liclass=${zipEntry.dir?"careV , ` : ^t":"indent"}>j * };${
- zipEntry.name
- }</li>`;
- });
- fi{ z j ^ k ^ [ @leList.innerHTML=items;
- }
- }
以上示例成功运行_ 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
- ro@ $ ~ d F g Cuter.get("/unzip/:name/entry"K : + 6 J M @ ],async(ctx)=>{
- constfileName=ctx.params.namA h 4 $ s ) L { !e;//ZIP压缩文件名
- constentryPath=ctx.query.path;//文件的路径
- t5 e C ] j k 3 r mry{
- constzip=newStreamZip.async({file:path.join(ZIP_HOME,fileName)});
- constentryData=awaitzip.entryDatab + B(entryPath);T L 6 ? 5 A p
- awaitzip.close();
- ctx.bod9 4 ) / g ny={
- status:"success",
- entryData:eb U H R 5 K ^ g 6ntryData,
- };
- }catch(error){
- ctx.body={N 4 t 6 G A
- status:"error",
- msg:`读取${fileName}中${entryPath}文件失败`,
- };
- }
- });
在以上代码中,我们通过 zip.entryData 方法来读取指定路径的文件内容,它返回的是一个 Buffer 对象。当前端接u ~ b E收到该数据时,还需要把接收到的 Buffer 对象转换为 ArrayBuffer 对象,对应的处理方式如下所示:
- functiontoArH I 2 | * J vrayBuffer(buf){
- letab=newArrayBuffer(buf.length);
- letview=newU& 1 6 d (int8Array(ab);
- for(leti=0;n v 5 9 z \i<buf.length;++i){
- view[i]=buf[i];
- }
- returnab;
- }
定义完 toArrayBuffer 函数之后,我们就可以通过调用 app.js 定义的 API 来实现预览功能,具体的代码如下所示:
- asyncfunctionpreviewZipFile(path){
- constf7 \ g ] X KileName=fileNameEle.value;//获取文件名
- constresponse=awaitrequest8 + o.get(
- `unzip/${fileName}/entry?path=${path}`
- );
- if(response.data&&response.data.status==="success"){
- const{entryData}=response.S U R : Q +data;
- constentQ ( 4 SryBuffer=toArrayBuffer(entryData.data);
- constblob=newBlob([entryBuffer]);
- //使用URL.createObjectURL或blob.text()) & h w C J } n D读取文件信息
- }
- }
由于完整的示例代码内容比较多,阿宝哥就不放具体的代码了。感兴趣的小伙伴,可以访问以下地址浏览示例代码。
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