因为我做的是全局上传的插件,要先把上传的窗口隐藏起来,在点击某个上传按钮的时候,用Bus发送一个openUploader的事件,在globalUploader.vue中接收该事件,trigger我们uploader-btn的click事件。
在某个页面中,点击上传按钮,同时把要给后台的参数带过来(如果有的话),这里组件之间传值我用的event bus,当然用vuex会更好:
Bus.$emit('openUploader', { superiorID: this.superiorID })在globalUploader.vue中接收该事件:
Bus.$on('openUploader', query => { this.params = query || {}; if (this.$refs.uploadBtn) { // 这样就打开了选择文件的操作窗口 $('#global-uploader-btn').click(); } });2. 选择文件后,将上传的窗口展示出来,开始md5的计算工作
onFileAdded(file) { this.panelShow = true; // 计算MD5,下文会提到 this.computeMD5(file); },这里有个前提,我在uploader中将autoStart设为了false,为什么要这么做?
在选择文件之后,我要计算MD5,以此来实现断点续传及秒传的功能,所以选择文件后直接开始上传肯定不行,要等MD5计算完毕之后,再开始文件上传的操作。
具体的MD5计算方法,会在下面讲,这里先简单引出。
上传过程中,会不断触发file-progress上传进度的回调
// 文件进度的回调 onFileProgress(rootFile, file, chunk) { console.log(`上传中 ${file.name},chunk:${chunk.startByte / 1024 / 1024} ~ ${chunk.endByte / 1024 / 1024}`) },3. 文件上传成功后
文件上传成功后,在“上传完成”的回调中,通过服务端返回的needMerge字段,来判断是否需要再发送合并分片的请求,
如果这个字段为true,则需要给后台发一个请求合并的ajax请求,否则直接上传成功。
注意:这里的needMerge是我和后台商议决定的字段名
onFileSuccess(rootFile, file, response, chunk) { let res = JSON.parse(response); // 服务器自定义的错误,这种错误是Uploader无法拦截的 if (!res.result) { this.$message({ message: res.message, type: 'error' }); return } // 如果服务端返回需要合并 if (res.needMerge) { api.mergeSimpleUpload({ tempName: res.tempName, fileName: file.name, ...this.params, }).then(data => { // 文件合并成功 Bus.$emit('fileSuccess', data); }).catch(e => {}); // 不需要合并 } else { Bus.$emit('fileSuccess', res); console.log('上传成功'); } }, onFileError(rootFile, file, response, chunk) { console.log(error) }, 5. 文件分片vue-simple-uploader自动将文件进行分片,在options的chunkSize中可以设置每个分片的大小。
如图:对于大文件来说,会发送多个请求,下面的第一个请求是get请求,用于和服务端分片校验,后面的每一个请求都是上传分片的post请求
看一下发送给服务端的参数,其中chunkNumber表示当前是第几个分片,totalChunks代表所有的分片数,这两个参数都是都是插件根据你设置的chunkSize来计算的。
需要注意的就是在最后文件上传成功的事件中,通过后台返回的字段,来判断是否要再给后台发送一个文件合并的请求。
6. MD5的计算过程断点续传及秒传的基础是要计算文件的MD5,这是文件的唯一标识,然后服务器根据MD5进行判断,是进行秒传还是断点续传。
在file-added事件之后,就计算MD5,我们最终的目的是将计算出来的MD5加到参数里传给后台,然后继续文件上传的操作,详细的思路步骤是:
把uploader组件的autoStart设为false,即选择文件后不会自动开始上传
先通过 file.pause()暂停文件,然后通过H5的FileReader接口读取文件
将异步读取文件的结果进行MD5,这里我用的加密工具是spark-md5,你可以通过npm install spark-md5 --save来安装,也可以使用其他MD5加密工具。
file有个属性是uniqueIdentifier,代表文件唯一标示,我们把计算出来的MD5赋值给这个属性 file.uniqueIdentifier = md5,这就实现了我们最终的目的。
通过file.resume()开始/继续文件上传。
/** * 计算md5,实现断点续传及秒传 * @param file */ computeMD5(file) { let fileReader = new FileReader(); let time = new Date().getTime(); let md5 = ''; file.pause(); fileReader.readAsArrayBuffer(file.file); fileReader.onload = (e => { if (file.size != e.target.result.byteLength) { this.error('Browser reported success but could not read the file until the end.'); return } md5 = SparkMD5.ArrayBuffer.hash(e.target.result); // 添加额外的参数 this.uploader.opts.query = { ...this.params } console.log(`MD5计算完毕:${file.id} ${file.name} MD5:${md5} 用时:${new Date().getTime() - time} ms`); file.uniqueIdentifier = md5; file.resume(); }); fileReader.onerror = function () { this.error('FileReader onerror was triggered, maybe the browser aborted due to high memory usage.'); }; }, 7. 秒传及断点续传在计算完MD5后,我们就能谈断点续传及秒传的概念了。
服务器根据前端传过来的MD5去判断是否可以进行秒传或断点续传:
a. 服务器发现文件已经完全上传成功,则直接返回秒传的标识。
b. 服务器发现文件上传过分片信息,则返回这些分片信息,告诉前端继续上传,即断点续传。
7.1 对于前端来说