基于vue-simple-uploader 实现大文件分片上传(一)

前言

教育项目视频上传、大文件频频上传失败、进度到100%后,服务端响应时间过长,影响观感。
继而采取分片上传方式,*有:
Web Uploader:http://fex.baidu.com/webuploader/
vue-simple-uploader:https://github.com/simple-uploader/vue-uploader
我们先看下可以直接npm导入的 vue-simple-uploader

简介

vue-simple-uploader就是一个基于 simple-uploader.js 和Vue结合做的一个上传组件,
自带 UI,可覆盖、自定义。它支持文件、多文件、文件夹上传;支持拖拽文件、
文件夹上传;可暂停、继续上传;支持秒传;上传队列管理,支持最大并发上传;
分片上传;支持进度、预估剩余时间、出错自动重试、重传等操作。
  1. 安装

    npm install vue-simple-uploader --save
    
  2. main全局导入

	import uploader from 'vue-simple-uploader'
    import App from './App.vue'
   	Vue.use(uploader)

需求和使用

  1. 项目需求
1、视频文件
2、一次只能上传一个,再次上传的视频文件会替换第一次上传
3、进度控制到99%
4、其余自定义

2.template

<uploader
   ref="uploader1"
   :options="options"
   :file-status-text="fileStatusText"
   @upload-start="onUploadStart"
   @file-added="onFileAdded"
   @file-progress="onFileProgress"
   @file-success="onFileSuccess"
   @file-error="onFileError"
   class="uploader-example">
     <uploader-unsupport></uploader-unsupport>
       <!-- <p>Drop files here to upload or</p> -->
       <el-button size="small" @click="beforeUploade">点击上传</el-button>
       <div>只能上传mp4,mov,avi格式</div>
       <uploader-btn :attrs="attrs" v-show="false" ref="upload">选择视频文件</uploader-btn>
       <!-- <uploader-btn :directory="true">select folder</uploader-btn> -->
     <uploader-list v-show="!form.videoUrl"></uploader-list>
     <div v-show="form.videoUrl">{{form.videoUrl}}</div>
   </uploader>
分析解释:
1、 options:uploader属性配置
fileStatusText:上传结果信息提示
@upload-start="onUploadStart" // 上传开始监听
@file-added="onFileAdded" // 文件上传到服务器前操作,可用校验等
@file-progress="onFileProgress" // onFileProgress上传进度回调
@file-success="onFileSuccess" // 所有分片上传完毕执行
@file-error="onFileError" // 上传错误监听

2、<uploader-btn :attrs="attrs" v-show="false" ref="upload">选择视频文件</uploader-btn>
attrs:{}默认添加属性到input
3、思路分析
采用el-button 按钮的beforeUploade调取页面隐藏uploader-btn标签上传,
是为了点击上传按钮前多一步操作,用于重置已经存在的文件,保证始终只有一个文件
  1. data中的参数
  // 文件上传参数
     uploaderInstance:undefined, // 用于保存ref即uploader组件
     options: {
       headers: { Authorization: "Bearer " + getToken() }, // 接口必须
       target: process.env.VUE_APP_BASE_API + "/file?type=1", // 接口url地址
       testChunks: false,
       chunkSize: 1024*1024*5,  // 5MB 每片大小
       simultaneousUploads: 3, //并发上传数
       maxChunkRetries: 2, //最大自动失败重试上传次数
       parseTimeRemaining: function (timeRemaining, parsedTimeRemaining) { //格式化时间
           return parsedTimeRemaining
               .replace(/\syears?/, '年')
               .replace(/\days?/, '天')
               .replace(/\shours?/, '小时')
               .replace(/\sminutes?/, '分钟')
               .replace(/\sseconds?/, '秒')
       },
       testChunks: true,   //开启服务端分片校验
       // 服务器分片校验函数
       checkChunkUploadedByResponse: (chunk, message) => {
           let obj = JSON.parse(message);
           if (obj.isExist) {
               this.statusTextMap.success = '秒传文件';
               return true;
           }
           return (obj.uploaded || []).indexOf(chunk.offset + 1) >= 0
       },
     },
     statusTextMap: {
           success: '上传成功',
           error: '上传出错了',
           uploading: '上传中...',
           paused: '暂停',
           waiting: '等待中...',
           cmd5: '计算md5...'
       },
     fileStatusText: (status, response) => {
         return this.statusTextMap[status];
     },
     attrs: {
       accept: 'video/*',
       // multiple: false, 
     },

注意事项:

attrs: {
       accept: 'video/*',
       // multiple: false, 
     },
     在这里设置禁止input多选无效,我们后面优化会修改源码进行控制以及进度条只让它显示99%
  1. methods
 onUploadStart(file){
     // 上传开始执行
   },
   beforeUploade(){ 
   	// .cancel() 取消上传且从文件列表中移除
     this.$refs.uploader1.uploader.cancel()
     this.form.videoUrl='' //保存最终上传成功的url地址 
     document.querySelector('.uploader-btn>input').click()
   },
   onFileAdded(file) { // 文件上传前的校验
       // 计算MD5   下一章我会讲到
       // this.computeMD5(file);
   },
   //计算MD5
   computeMD5(file) {
       let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,
           chunkSize = 2097152,
           chunks = Math.ceil(file.size / chunkSize),
           currentChunk = 0,
           spark = new SparkMD5.ArrayBuffer(),
           fileReader = new FileReader();
       let time = new Date().getTime();
       file.cmd5 = true;
       fileReader.onload = (e) => {
           spark.append(e.target.result);   // Append array buffer
           currentChunk++;
     
           if (currentChunk < chunks) {
               //console.log(`第${currentChunk}分片解析完成, 开始第${currentChunk +1} / ${chunks}分片解析`);
               let percent = Math.floor(currentChunk / chunks * 100);
               file.cmd5progress = percent;
               loadNext();
           } else {
               console.log('finished loading');
               let md5 = spark.end();
               console.log(`MD5计算完成:${file.name} \nMD5:${md5} \n分片:${chunks} 大小:${file.size} 用时:${new Date().getTime() - time} ms`);
               spark.destroy(); //释放缓存
               file.uniqueIdentifier = md5; //将文件md5赋值给文件唯一标识
               file.cmd5 = false; //取消计算md5状态
               file.resume(); //开始上传
           }
       };
       fileReader.onerror = () => {
           console.warn('oops, something went wrong.');
           file.cancel();
       };
     
       let loadNext = () => {
           let start = currentChunk * chunkSize,
               end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;
     
           fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end));
       };
     
       loadNext();
   },
   // 文件进度的回调
   onFileProgress(rootFile, file, chunk) {
       console.log(`上传中 ${file.name},chunk:${chunk.startByte / 1024 / 1024} ~ ${chunk.endByte / 1024 / 1024}`)
   },
   onFileSuccess(rootFile, file, response, chunk) {
       let resp = JSON.parse(response);
       console.log(resp)
       this.form.videoUrl = resp.data.url
       //合并分片 所有上传结束通知后端进行合并分片
       // if (resp.code === 0 && resp.merge === true) {
       //     axios.post('http://localhost:9999/up.php?action=merge', {
       //         filename: file.name,
       //         identifier: file.uniqueIdentifier,
       //         totalSize: file.size,
       //         totalChunks: chunk.offset + 1
       //     }).then(function(res){
       //         if (res.code === 0) {
       //             console.log('上传成功')
       //         } else {
       //             console.log(res.message);
       //         }
       //     })
       //     .catch(function(error){
       //         console.log(error);
       //     });
       // }
   },
   onFileError(rootFile, file, response, chunk) {
       console.log('Error:', response)
   },
上一篇:webpack抽离公共代码和第三方代码


下一篇:HTML上传文件支持大文件上传,下载源码