当前位置: 首页 > Web前端 > HTML

前端使用FileReader读取本地文件并校验文件的唯一性

时间:2023-04-02 23:45:37 HTML

故事背景昨天下午被问到一个问题:因为oss对象存储中共享了一些图片,所以上传了很多重复的图片或文件。上传前先判断文件是否已经上传。如果已经上传,直接去后台获取存储的地址。被问到的时候,我的第一反应是根据文件类型名和大小生成一个MD5,后来被拒绝了。如果添加了文件并更改了名称,文件仍会上传。研究了一天,了解到这个FileReader对象,之前一直没有用过,顺便被他的其他方法吸引了。今天,我将分享什么是FileReader。FileReader对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)。内容,使用File或BlobBlob对象表示不可变的原始数据文件类对象。Blob不一定代表JavaScript原生格式的数据。File接口基于Blob,继承了blob的功能并对其进行了扩展以支持用户系统上的文件。")对象指定要读取的文件或数据。其中File对象可以来自输入元素中的用户,用于为基于Web的表单创建交互式控件以接受来自用户的数据;各种类型的输入数据可以是使用和控制小部件,取决于设备和在用户代理元素上选择文件后返回的FileList对象,或从拖放操作生成的DataTransfer对象,或从执行mozGetAsFile()后返回的结果HTMLCanvasElement上的方法。(MDN)说白了,FileReader对象可以对内存中的数据进行操作,需要知道的一件重要事情是FileReader仅用于从用户(远程)读取文件的内容系统以安全的方式。它不能用于通过路径从文件系统读取文件内容。名称只是读取文件。也就是说,他不能直接使用本地路径来读取文件。他可以请求后端资源读取对应的文件,或者前端可以用更安全的方式读取文件,比如输入文件。上传FileReader的属性并打印如图EMPTY:0LOADING:1DONE:2这三个是对象实例的状态,分别是未读文件、正在读取和就绪状态:[Exception:TypeError:IllegalinvocationatFileReader.invokeGetter(:1:142)]结果:[Exception:TypeError:IllegalinvocationatFileReader.invokeGetter(:1:142)]error:[Exception:TypeError:IllegalinvocationatFileReader.invokeGetter(:1:142)]然后这三个属性readyState,这个是获取当前对象实例的状态结果,这个是读取文件成功后的返回值,具体返回值以对象实例的方法为准你调用Returned,下面我会讲到FileReader方法的具体使用错误。很明显,报错时的信息onloadstart:[Exception:TypeError:IllegalinvocationatFileReader.invokeGetter(:1:142)]onprogress:[Exception:TypeError:IllegalinvocationatFileReader.invokeGetter(:1:142)]onload:[Exception:TypeError:在FileReader.invokeGetter(:1:142)]处进行非法调用onabort:[Exception:TypeError:在FileReader.invokeGetter(:1:142)]onerror:[异常:TypeError:在FileReader.invokeGetter(:1:142)]非法调用onloadend:[异常:TypeError:非法调用在FileReader.invokeGetter(:1:142)]onloadstart读取开始的时候触发,这里可以做一些普通的处理,比如loading或者onprogress之类的这个事件比较讨喜,这个方法会在文件读取的时候被读取read是在fetching过程中触发的,大约每11927552字节触发一次。这样会返回一个ProgressEvent对象,里面包含了本次读取文件的最大字节数和已经读取的字节数,可以用来做进度条什么的。onload事件在文件读取成功时触发。这里可以通过上面提到的实例上面的result属性来查看你操作的函数对应的内容。onabort读取文件,在终端读取文件时触发。相应的还有中断onerror的方法,当读取文件失败时触发onloadend。无论读取文件失败还是成功,都会触发该方法。这个方法会返回一个你拿到的对象的base64地址,但是对于这个地址,你会发现你的文件越大,地址就越长。其实这个地址就是一个Base64编码的文件数据字符串然后前面说了FileReader的所有操作都是异步的,所以不能像下面这样获取返回值letfileReader=newFileReader()leturl=fileReader.readAsDataURL(file.file)console.log(url)不能这样打印,需要在他自己的处理事件上回调获取letfileReader=newFileReader()fileReader.readAsDataURL(file.file)fileReader.onload=()=>{console.log(fileReader.result)}回调结果在对象实例的result属性上,如上readAsBinaryString开始读取一旦指定的blob的内容被获取,结果属性将包含读取文件的原始二进制数据。这种方法得到的结果是原始的二进制数据,不能直接使用。它需要做一些转换或使用标签才能使用它。打印出来的大概是这样的abort这是中断文件的读取。比如,你认为这次阅读的事件是什么?有点长,或者你想让它在某种情况下停止,那么此时你可以使用这个方法来打断它,而使用这个方法之后,fileReader对象的状态是DONE,也就是说你可以在负载。数据readAsArrayBuffer和最后两个是我今天用的方法。readAsArrayBuffer开始读取指定Blob中的内容。一旦完成,result属性将保存读取文件的ArrayBuffer数据对象。这个ArrayBuffer上面有mdn传送门看看,或者看看阮一峰老师在es6底层的解释。它是一个字节数组,用于表示通用的、固定长度的原始二进制数据缓冲区。它不能被直接操纵。你可以使用对应的TypedArray接口或者DataView接口来操作他,这是一个二进制字节数据操作的低级接口,我这里使用TypeArrayTypeArray的公共构造函数Int8Array:8位有符号整数,长度1字节。Uint8Array:8位无符号整数,长度为1个字节。Uint8ClampedArray:8位无符号整数,长度1字节,溢出处理不同。Int16Array:16位有符号整数,长度为2个字节。Uint16Array:16位无符号整数,长度为2个字节。Int32Array:32位有符号整数,长度为4个字节。Uint32Array:32位无符号整数,长度为4个字节。Float32Array:32位浮点数,长度为4字节。Float64Array:64位浮点数,长度8字节。好了,看到这里,之前没有接触过的同学们是不是脑子嗡嗡作响了?.没关系。我昨天也一直在嗡嗡作响。..简单的说,上面列出的九个构造函数,会根据你传入的参数生成对应的数组,然后这些数组统称为TypeArray视图。这个数组包含了所有的数组方法和属性,你可以像操作数组一样操作它们。一会儿我把他们的结果打印在下面,大家看看就知道了。然后是上面提到的DataView,简单来说,这个DataView是和TypedArray一起使用的,因为DataView的参数是接受一个TypedArray对象,具体方法如下读取getInt8:读取1个字节,返回一个8位整数。getUint8:读取1个字节,返回一个无符号的8位整数。getInt16:读取2个字节,返回16位整数。getUint16:读取2个字节并返回一个无符号的16位整数。getInt32:读取4个字节,返回一个32位整数。getUint32:读取4个字节并返回一个无符号的32位整数。getFloat32:读取4个字节,返回一个32位的浮点数。getFloat64:读取8个字节,返回一个64位的浮点数。写入setInt8:在1个字节中写入一个8位整数。setUint8:写入一个1字节的8位无符号整数。setInt16:在2个字节中写入一个16位整数。setUint16:写入2个字节的16位无符号整数。setInt32:写入一个4字节的32位整数。setUint32:写入一个4字节的32位无符号整数。setFloat32:写入一个4字节的32位浮点数。setFloat64:写入一个8字节的64位浮点数。readAsText这是之前做的一个读取文件的方法,里面用到了FileReader的readAsText方法,废话不多说,直接附上代码和效果图exportdefaultfunctionreadFile(model){returnnewPromise((resolve)=>{//Googleif(window.FileReader){//获取文件流letfile=model.currentTarget?model.currentTarget.files[0]:model;//创建FileReader实例letreader=newFileReader();//读取文件reader。readAsText(file);reader.onload=()=>{resolve(reader.result)}}//支持IE78910elseif(typeofwindow.ActiveXObject!='undefined'){letxmlDoc;xmlDoc=newActiveXObject("Microsoft.XMLDOM");xmlDoc.async=false;resolve(xmlDoc.load(model))}//支持FFelseif(document.implementation&&document.implementation.createDocument){letxmlDoc;xmlDoc=document.实现.createDocu换(“”,“”,空);xmlDoc.async=false;resolve(xmlDoc.load(model))}})}//安装依赖npmizjsmethods-S~~~~//页面导入使用import{_readFile}from"zjsmethods"_readFile(file).then(res=>{console.log(res)})readAsArrayBuffer验证文件上面说了这么多,终于进入正题了。直接看第一个版本代码constreader=newFileReader();reader.readAsArrayBuffer(file.file);reader.onload=()=>{letu8Arr=newUint8Array(reader.result)console.log(u8Arr)console.log(md5(u8Arr))}ok没问题,结果如下。正当我以为这么简单的时候,意外发生了。当我使用比较小的文件时,只有1M左右,但是当我上传视频进行测试时,它可能有两个G并且浏览器崩溃。崩溃了。.然后我把之前那个比较小的文件的字节数组展开了。这样做的原因是readAsArrayBuffer在读取文件的时候会先把整个文件加载到内存中。如果文件太大,内存将不够。浏览服务器进程将崩溃。由于整个加载不起作用,我们选择分段加载文件。后来觉得10M的段比较稳妥,所以改成小于10M的文件平均分成10段。如果大于10M,则每10M分成一段,直到分割完成。至此,为了避免加密时数据过多造成卡顿,在生成logo时,放弃使用整个数组生成logo,采用固定的最大10M数据的规则生成logoasyncavaildArrayBuffer(){constreader=newFileReader();while(this.whileNumber--){this.start=this.endthis.end=this.end+this.whileMaxlet{start,end,sliceEnd,file}=thisreader.readAsArrayBuffer(file.slice(start,end));reader.onload=()=>{newUint8Array(reader.result).slice(0,sliceEnd).join('')}}}这次还有一个小插曲,调用时提示读者,文件正在处理中read,也就是一个reader在读取文件的时候不能同时读取两个,所以一开始我高估了读取的速度,放到了读取文件的callback中,代价就是看控制台眼巴巴的在电脑前约5分钟,然后换成承诺包。最终代码如下/**@Date:2020-03-2216:36:37*@information:最后更新时间*/importmd5from'md5'exportdefaultclassvaileFile{constructor(file){this.file=文件//每次拦截多少二进制this.whileMax=Math.floor(file.size/10>10240*1024?10240*1024:file.size/10);//循环多少次this.whileNumber=file.size<=10240*1024?10:Math.ceil(file.size/this.whileMax)//二进制截取长度,超过10M后,每隔10M截取一部分,最多10Mthis.sliceEnd=Math.floor(1024*10240/file.size*100/this.whileNumber*this.whileMax)this.sliceEnd=this.whileNumber>10?this.sliceEnd:10240*1024//转换二进制长度this.start=0this.end=0;}/***@Author:周劲松*@Date:2020-03-2215:53:07*@information:校验文件唯一*/asyncavaildArrayBuffer(){letpromiseArr=[]while(this.whileNumber-){这。start=this.endthis.end=this.end+this.whileMaxlet{start,end,sliceEnd,file}=thisletpromiseArrayBuffer=newPromise((resolve,reject)=>{constreader=new文件读取器();reader.readAsArrayBuffer(文件。切片(开始,结束));reader.onload=()=>{resolve(newUint8Array(reader.result).slice(0,sliceEnd).join(''))}})promiseArr.push(promiseArrayBuffer)}returnmd5((awaitPromise.all(promiseArr)).join(''))}}大功告成,上传文件后会生成一个md5,复制文件,重命名文件,可以识别为之前的文件,然后写README。md解释如何使用###_vaileFile,//使用文件二进制来验证文件的唯一性。当有业务需要上传oss对象存储时,为了避免同一个文件(视频、音频、图片、压缩包等),其他人可能会复制或改名等,导致重复文件上传,占用空间大,写了一个校验文件的方法//安装依赖npmizjsmethods-S//介绍这个类import{_vaileFile}from'zjsmethods'//然后当需要判断是否是oss有文件new_vaileFile('fileobject').vaildArrayBuffer().then(res=>{console.log(res)//继续上传或者向后端请求已有的文件url})//n在ew类之后有一个vaildArrayBuffer方法,返回一个promise,里面的返回值是一个md5字符串,就是这个文件的唯一标识。最后发布npm包传给git结束学习★,°:.☆( ̄▽ ̄)/$:.°★喜欢就点个赞吧,如有不足欢迎指正