vue-simple-uploader组件的一些使用心得

前言

因为项目需要上传大文件,考虑使用分片上传、断点续传这些功能故选用vue-simple-uploader,期间踩的一些坑及解决方法记录一下。

git文档链接

1.https://github.com/simple-uploader/vue-uploader/blob/master/README_zh-CN.md
2.https://github.com/simple-uploader/Uploader/blob/develop/README_zh-CN.md

使用vue-simple-uploader

1.安装插件:

npm install vue-simple-uploader --save

2.main.js中初始化

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

3.在*.vue文件中使用
代码仅供参考

        
          
          
          
          大文件上传
        

        
          
          
            
          
        

4.data中参数定义

      options:{
        target:"/file/fdfs/multipart-upload/chunkUpload",//即分片上传的URL
        chunkSize: 10 * 1024 * 1024,//分片大小
        simultaneousUploads:3,//并发上传数,默认 3
        testChunks: true,//是否开启服务器分片校验
        checkChunkUploadedByResponse: function (chunk, res) {// 服务器分片校验函数,秒传及断点续传基础
        //需后台给出对应的查询分片的接口进行分片文件验证
          let objMessage = JSON.parse(res);//skipUpload、uploaded 需自己跟后台商量好参数名称
          if (objMessage.skipUpload) {
            return true;
          }
          return (objMessage.uploaded || []).indexOf(chunk.offset + 1) >= 0
        },
        maxChunkRetries: 2, //最大自动失败重试上传次数
        headers: {//在header中添加token验证,Authorization请根据实际业务来
          getUploadHeaders()
        },
        processParams(params) {//自定义每一次分片传给后台的参数,params是该方法返回的形参,包含分片信息,
          //若不自定义则默认会把文件所有参数传给后台,自己可以通过接口查看插件本身传递的参数
          return {//返回一个对象,会添加到每一个分片的请求参数里面
            totalChunks: params.totalChunks,
            identifier:params.identifier,
            chunkNumber: params.chunkNumber,
            chunkSize: 10 * 1024 * 1024,
            //  这是我跟后台约定好的参数,可根据自己项目实际情况改变
          };
        },
      }

文件钩子的使用

主要讲一下几个文件上传钩子和实例方法的使用,由于官方文档写的也不是很清楚,踩了很多坑
代码仅供参考

    onFileAdded(file,fileList) {
      file.pause()
      if(file.getType()!='zip'){
        EventBus.$emit('alert.show', {type: 'warning', msg: `只能上传.zip格式的文件`});
        file.ignored = true//文件校验,不符规则的文件过滤掉
        // file.cancel()
        // return false;
      }
      else if(file.size<=800*1024*1024){
        EventBus.$emit('alert.show', {type: 'warning', msg: `请上传800M以上的文件`});
        file.ignored = true//文件过滤

      }
      else if(this.fileList.length >= 10){
        EventBus.$emit('alert.show', {type: 'warning', msg: `最多上传10份文件`});
        file.ignored = true//文件过滤
      }else{
        // 新增文件的时候触发,计算MD5
        this.myMD5(file);
      }
    },
    onFileSuccess(rootFile, file, response, chunk){
      //文件成功的时候触发
      let index = this.findFileById(file.uniqueIdentifier);
      let res = JSON.parse(response)
      if(res.result==="上传成功"){
        this.uploadingFileStr(res.path,getUploadHeaders()).then(ress=>{
          if(ress.data.msgCode===1){
            if(index > -1){
              this.fileList[index].id = ress.data.data.id;
              this.fileList[index].resName = file.name.replace(/\.\w+$/g, '');
              this.fileList[index].name = file.name;
              this.fileList[index].filePath = null;
              this.fileList[index].coverPath = null;
              this.fileList[index].progress = 100;
              this.fileList[index].status = "success";
              this.fileList[index].state = 1;
              this.fileList[index].isPlayOrPause=false;
              this.fileList[index].showPP = false;
              this.expandCollapse(file.uniqueIdentifier);
            }
          }else{
            EventBus.$emit('alert.show', {type: 'error', msg: res.result});
            if(index > -1){
              this.fileList[index].status = 'fail';
              this.fileList[index].state = 2;
              this.fileList[index].isPlayOrPause=false;
              this.fileList[index].showPP = false;
            }
          }
        })
      }
    },
    onFileError(rootFile, file, response, chunk){
      let res = JSON.parse(response)
      EventBus.$emit('alert.show', {type: 'error', msg: res.result});
      let index = this.findFileById(file.uniqueIdentifier);
      if(index > -1){
        this.fileList[index].status = 'fail';
        this.fileList[index].state = 2;
        this.fileList[index].isPlayOrPause=false;
        this.fileList[index].showPP = false;
      }
    },
    onFileProgress(rootFile, file, chunk){
      let index = this.findFileById(file.uniqueIdentifier),
        p = Math.round(file.progress()*100);
      if(index > -1){
        if(p < 100){
          this.fileList[index].progress = p;
        }
        this.fileList[index].status = file.status;
      }
    },
    myMD5(file) {//这里主要是使用MD5对文件做一个上传的查重
      let md5 = "";
      md5 = SparkMD5.hash(file.name);//业务需求以文件名作为加密
      let index = this.findFileById(md5);
      if(index==-1){
        file.uniqueIdentifier = md5;
        this.fileList.push({
          id: null,
          uid: file.uniqueIdentifier,
          filePath: '',
          coverPath: '',
          name: file.name,
          resName: '',
          progress: 0,
          state: 0,
          status: '',
          isPlayOrPause:true,
          showPP:true,
          elMD5:md5   //多余的参数可注释
        });
        //继续上传文件
        file.resume();
      }else{
        EventBus.$emit('alert.show', {type: 'warning', msg: `该文件已上传至列表,请勿重复上传`});
        file.ignored = true//文件过滤
      }
    },
    playFile(e,f){
      e.stopPropagation();
      f.isPlayOrPause=true
      const uploaderInstance = this.$refs.uploader.uploader
      let index = uploaderInstance.fileList.findIndex(e => e.uniqueIdentifier === f.uid)//兼容点击上传按钮
      uploaderInstance.fileList[index].resume();
    },
    pauseFile(e,f){
      e.stopPropagation();
      f.isPlayOrPause=false
      const uploaderInstance = this.$refs.uploader.uploader
      let index = uploaderInstance.fileList.findIndex(e => e.uniqueIdentifier === f.uid)
      uploaderInstance.fileList[index].pause();
    },
uoloader实例的一些方法和钩子

1.@file-added="onFileAdded" file-added(file,filelist)相当于before-upload,即在上传文件之前对文件进行的操作,一般用来做文件验证。如果没有配置autoStart=false点击上传文件按钮之后文件会自动上传

autoStart {Boolean}
默认 true, 是否选择文件后自动开始上传。

我们要在onFileAdded()中通过pause()方法暂停文件上传

   onFileAdded(file,fileList) {
      file.pause()//用来暂停文件上传
      if(file.getType()!='zip'){//根据自己项目要求进行文件验证
        EventBus.$emit('alert.show', {type: 'warning', msg: `只能上传.zip格式的文件`});
        file.ignored = true//文件校验,不符规则的文件过滤掉
        // file.cancel()//清除文件
        // return false;
      }
  }

这里要讲的是file.cancel()file.ignored = true

先讲一下这个插件获取uploader实例的方法
const uploaderInstance = this.$refs.uploader.uploader
uploaderInstance 就是uploader实例

在项目中点击上传文件按钮(第一次上传),文件成功加入fileList之后,再次上传此文件(第二次),会进入onFileAdded()进行文件验证,因为我进行文件查重,重复的就不在上传了,所以报错之后使用uploader实例提供的file.cancel()方法清除文件,此后再有点击文件上传此文件都不会跳入onFileAdded()方法,更不会进行验证和报错,这对于用户来说无疑是一个巨大的BUG。
通过检查log发现,每次点击上传文件之后插件会在uploader实例中的files数组中加入当前文件的信息,用插件实例方法cancel()是清除不掉当前文件的。如果使用uploaderInstance.cancel()则会清除包括正在上传,暂停上传,已上传成功的所有的文件信息。使用file.ignored = true使验证失败的文件不加入uploader实例中的files数组,则下次点击上传文件按钮可再次调起onFileAdded()进行验证

uploader实例中的fileList数组中的文件才是实际正在上传文件列表,对于uploader实例中files,file,fileList这几个数组的具体作用有待研究

2.@file-success="onFileSuccess" 文件上传成功的回调

    onFileSuccess(rootFile, file, response, chunk){
      //文件成功的时候触发
    },

3.@file-progress="onFileProgress" 用来获取文件的实时上传进度

    onFileProgress(rootFile, file, chunk){
      let index = this.findFileById(file.uniqueIdentifier),//通过index来获取对应的文件progress
          p = Math.round(file.progress()*100);
      if(index > -1){
        if(p < 100){
          this.fileList[index].progress = p;
        }
        this.fileList[index].status = file.status;
      }
    },

4.@file-error="onFileError" 文件上传失败的回调

    onFileError(rootFile, file, response, chunk){
      //文件上传失败的回调
    },

5.@file-removed="fileRemoved" 删除文件的回调
也可自定义删除的按钮不使用此钩子

  delFile(e, f){
      e.stopPropagation();
      let txt = '是否删除选中数据?';
      if(f.id){
        txt = '该资源还未提交信息,确认删除选中数据?';
      }
      this.$confirm(txt, '删除', {
        type: 'warning',
        showCancelButton: false
      }).then(() => {
        const uploaderInstance = this.$refs.uploader.uploader
        let index = uploaderInstance.fileList.findIndex(e => e.uniqueIdentifier === f.uid)
        if(index>-1){
          uploaderInstance.fileList[index].cancel();   //这句代码是删除所选上传文件的关键
        }
        this.delFileByFileuid(f.uid);
      }).catch(console.error);
    },

6.文件的暂停上传和继续上传

    playFile(e,f){
      e.stopPropagation();
      f.isPlayOrPause=true
      const uploaderInstance = this.$refs.uploader.uploader
      let index = uploaderInstance.fileList.findIndex(e => e.uniqueIdentifier === f.uid)//兼容点击上传按钮
      uploaderInstance.fileList[index].resume();   //
    },
    pauseFile(e,f){
      e.stopPropagation();
      f.isPlayOrPause=false
      const uploaderInstance = this.$refs.uploader.uploader
      let index = uploaderInstance.fileList.findIndex(e => e.uniqueIdentifier === f.uid)
      uploaderInstance.fileList[index].pause();   //
    },

在第5和第6点中对删除文件,暂停,继续上传都需要通过获取uploader实例中fileList对应的Index来进行操作
即:
const uploaderInstance = this.$refs.uploader.uploader
let index = uploaderInstance.fileList.findIndex(e => e.uniqueIdentifier === f.uid)
uploaderInstance.fileList[index].pause();// resume();cancel();

7.关于断点续传

官方文档是这样介绍的
checkChunkUploadedByResponse 可选的函数用于根据 XHR 响应内容检测每个块是否上传成功了,传入的参数是:Uploader.Chunk 实例以及请求响应信息。这样就没必要上传(测试)所有的块了

      checkChunkUploadedByResponse: function (chunk, res) {// 服务器分片校验函数,秒传及断点续传基础
        //需后台给出对应的查询分片的接口进行分片文件验证
          let objMessage = JSON.parse(res);//skipUpload、uploaded 需自己跟后台商量好参数名称
          if (objMessage.skipUpload) {
            return true;
          }
          return (objMessage.uploaded || []).indexOf(chunk.offset + 1) >= 0
        },

checkChunkUploadedByResponse()options中的一个配置项,配置之后上传文件之前会自动调起一个get请求获取已上传的分片信息。返回的内容自己跟后台协商,一般是返回已上传的分片的index数组,然后前端通过对返回的分片index进行筛选跳过已上传的分片,只上传未上传的分片。

  1. MD5文件验证
    断点续传及秒传的基础是要计算文件的MD5,这是文件的唯一标识,然后服务器根据MD5进行判断,我这里主要是用来做文件查重判断
    我这里使用的加密工具是spark-md5,可以通过npm来安装
npm install spark-md5 --save

在当前vue文件引用即可

import SparkMD5 from 'spark-md5';

file有个属性是uniqueIdentifier,代表文件唯一标示,我们把计算出来的MD5赋值给这个属性 file.uniqueIdentifier = md5

9.关于file.getType()的坑

getType()方法用于获取文件类型。
file.getType() ===>'zip' 等文件类型

如我在onFileAdd方法中做的文件格式验证

    if(file.getType()!='zip'){
        EventBus.$emit('alert.show', {type: 'warning', msg: `只能上传.zip格式的文件`});
        file.ignored = true
      }

但是项目提测之后发现验证文件格式老是出问题,检查log发现在我的电脑上file.getType()返回的是zip,在测试的电脑上返回的是x-zip-compressed。另外又试了其他格式的文件输出其文件格式如下图,发现两台电脑的zip格式文件返回的不一样。关键的问题就在于不同电脑上返回的文件格式不一样(有待研究该api),所以对于getType()的使用得根据自己项目来考虑自己获取还是使用该api
提供一个解决方案,截取file.name的值获取后缀

file.name.substring(file.name.lastIndexOf(".")+1) 

另外关于文件上传的格式application/x-zip-compressed,application/zip,application/vnd.ms-excel……有兴趣的小伙伴可以自行研究一下插件的底层代码

电脑1

电脑2

功能完成

图1

说明:兼容了el-upload的上传按钮,第4个文件是用el-upload按钮进行上传的

你可能感兴趣的:(vue-simple-uploader组件的一些使用心得)