当前位置: 首页 > 后端技术 > Node.js

详解,从后端导出文件到前端(Blob)下载的过程

时间:2023-04-03 16:32:13 Node.js

前言对于不搞音视频的同学,很多时候都是通过window.location.href下载文件.这样前后端登录状态一般都是基于Cookie+Session的方式。默认情况下,浏览器会将本地cookie塞入HTTP请求头域的Set-Cookie中,从而实现用户的SessionId。因此,我们也可以使用window.location.href打开一个链接来下载一个文件。当然还有一种情况是不需要验证登录状态的(比较che)。众所周知,还有一种登录状态处理方式JWT(JSONWebToken)。这种情况一般要求前端在下载文件时在请求头域中加入Token头域。所以,这样一来,我们就不能直接通过window.location.href来下载文件了。不过还好,我们有Blobs,它是浏览器端的类文件对象,基于二进制数据,通过它我们可以优雅的处理文件下载,不局限于音视频、PDF、Excel等。那么,今天我们会从后端导出文件到HTTP协议,非简单请求下的preflight请求,最后Blob处理文件,了解为什么以及如何实现?后端(Koa2)导出文件(Excel)首先,我们先从后端导出文件说起。这里我选择Koa2来实现Excel的导出,然后使用node-xlsx库来实现Excel二进制数据的导出。它看起来像这样:constxlsx=require("node-xlsx")router.get("/excelExport",asyncfunction(ctx,next){//数据查询的结果constres=[["name","age"],["五柳","22"],];//生成excel对应的buffer二进制文件constexcelFile=xlsx.build([{name:"firstSheet",data:res}])//设置文件名相关的响应头字段ctx.set("Content-Disposition","attachment;filename=test.xlsx;filename*=UTF-8")//返回给前端ctx.body=buffer;});这里我们没有展开数据库查询,只是模拟了查询后的结果res然后我们用浏览器请求这个接口,我们会看到ResponseHeaders字段中的Content-Type是application/octet-stream,就是二进制文件的默认MIME类型。Connection:keep-aliveContent-Disposition:attachment;filename=test.xlsx;filename*=UTF-8Content-Length:14584Content-Type:application/octet-streamDate:Sun,23Aug202011:33:16GMTMIME类型,即多用途用于表示文件、文档或字节流的内部邮件扩展。如果HTTP协议识别二进制文件流,我们就不会参与到后台返回Excel的过程中。然后,HTTP协议可以帮助我们减少通信,了解我们的前端需要如何进行相应的处理。这里会涉及到三个HTTP实体头字段:Content-TypeContent-LengthContent-Disposition然后,我们来看看它们在HTTP文件传输过程中的特殊含义。Content-TypeContent-Type这个再平常不过的实体头字段想必大家都不陌生吧。用于描述实体主体内容对象的媒体类型,遵循type/subtype结构。常见的有text/plain、text/html、image/jpeg、application/json、application/javascript等。在我们的二进制文件中,它没有具体的子类型,即使用application/octet-stream作为Content-Type的值。也就是我们上面看到的:Content-Type:application/octet-stream那么,只要我们熟悉了Content-Type,那么开发中的沟通成本就可以降低。Content-LengthContent-Length是另一个熟悉的实体头字段,它指示传输的实体主体的大小(以字节为单位)。在我们的例子中,传输的Excel二进制文件的大小是14584。Content-DispositionContent-Disposition这个实体头域,我想??大部分前端同学都会觉得陌生。它用于指示实体主体内容旨在显示在浏览器中或下载为文件。它对应的值有几个内容:formdata,表示实体主体是formdata的形式。inline,表示实体主体的内容显示在浏览器中。attachment,表示实体正文内容作为文件下载。filename,表示文件编码格式或文件名,例如filename*=UTF-8表示文件的编码,filename=test.xlsx表示下载时的文件名。name,当formdata上传文件时,对应的是类型为file的input的name值,例如,此时对应的name为upload。需要注意的是,对于Content-Disposition的formdata,只是信息提示,并没有意识到实体主体的内容是formdata,这是Content-Type的职责。那么回到今天的栗子,它的Content-Disposition是:Content-Disposition:attachment;filename=test.xlsx;filename*=UTF-8所以,现在我们知道它主要做了几件事情:通知浏览器你需要下载二进制文件作为附件。附件的文件名为test.xlsx。附件编码为UTF-8Blob。优雅地处理文件(Excel)下载。为什么优雅?因为,Blob可以处理很多类型的文件,而且是受控的。您可以控制从接收二进制文件流,转换为Blob,然后使用其他API实现下载文件。因为,如果下载带有window.location.href的文件,确实可以达到同样的效果,但是不能在获取二进制文件流和下载文件之间进行个性化操作。而且在复杂的文件处理中,Blob肯定是首选,比如分片上传下载,拼接音视频文件等等。所以,在这里我也推荐大家使用Blob来处理文件下载。而且,值得一提的是,XMLHttpRequest默认支持设置responseType。通过设置reposponseType为blob,可以直接将获取到的二进制文件转换为Blob。当然axios也支持设置responseType,我们也可以将responseType设置为arraybuffer,不过我觉得没必要拐弯抹角。那么,在得到二进制文件对应的Blob对象后,我们需要进行下载操作。下面就介绍这两种使用Blob实现文件下载的方法。URL.createObjectURL在浏览器端,我们要下载文件,无非就是用a标签指向一个文件的下载地址。所以window.location.href的本质是一样的。因此,我们在得到二进制文件对应的Blob对象后,需要为这个Blob对象创建一个下载地址URL。URL.createObjectURL方法可以接收一个File或Blob对象,创建一个包含相应URL的DOMString,指向Blob或File对象,它看起来像这样:“blob:http://localhost:8080/a48aa254-866e-4c66-ba79-ae71cf5c1cb3"使用Blob和URL.createObjectURL下载文件的完整实用函数:exportconstdownloadFile=(fileStream,name,extension,type="")=>{constblob=newBlob([fileStream],{类型});constfileName=`${name}.${extension}`;if("download"indocument.createElement("a")){constelink=document.createElement("a");elink.download=文件名;elink.style.display="无";elink.href=URL.createObjectURL(blob);document.body.appendChild(elink);elink.click();URL.revokeObjectURL(elink.href);document.body.removeChild(elink);}else{navigator.msSaveBlob(blob,fileName);}};FileReader类似的,FileReader对象也可以让我们为Blob对象生成一个下载地址URL,与URL.createObject一样接收一个File或Blob对象。这个过程主要由两个函数readAsDataURL和onload完成,前者用于将Blob或File对象转换成对应的URL,后者用于接收前者完成的URL,会在e.target上。结果。使用Blob和FileReader下载文件的完整util函数:constreadBlob2Url=(blob,type)=>{returnnewPromise(resolve=>{constreader=newFileReader()reader.onload=(e)=>{resolve(e.target.result)}reader.readAsDataURL(blob)})}写在最后,如果你只是使用Blob浏览器API来处理文件下载,它可能不会给你带来太多好处。但是,通过了解从后端导出文件、HTTP协议、Blob处理文件下载的整个过程,可以构建一个完整的技术思维体系,并从中获益。只有了解它,我们才能做到这一点。这也符合我们作为一个不断学习的从业者的态度。因此,良好的技术知识储备可以使我们具备良好的编程思维和设计思维。文章对应的DEMO我已经上传到GitHub了。有兴趣的同学可以去clone了解更多。往期文章回顾作为前端,你需要了解RxJS(响应式编程-流程)TypeScript高级类型,你知道几个???爱情三连击看完之后觉得有所收获的,就来一波爱情三连击吧!!!