• vue+大文件断点续传


      

    根据部门的业务需求,需要在网络状态不良的情况下上传很大的文件(1G+)。
    其中会遇到的问题:
    1,文件过大,超出服务端的请求大小限制;
    2,请求时间过长,请求超时;
    3,传输中断,必须重新上传导致前功尽弃。
    解决方案实现思路,拿到文件,保存文件唯一性标识,切割文件、分片上传、文件MD5验证、断点续传、手动重试上传。


    鉴于过往有使用过webupload文件上传组件的经验,于是此次采用的是Plupload作为替换。Plupload是一款由著名的web编辑器TinyMCE团队开发的上传组件,简单易用且功能强大。

    Plupload有以下功能和特点

    1. 拥有多种上传方式:HTML5、flash、silverlight以及传统的<input type=”file” />。Plupload会自动侦测当前的环境,选择最合适的上传方式,并且会优先使用HTML5的方式。所以你完全不用去操心当前的浏览器支持哪些上传方式,Plupload会自动为你选择最合适的方式。
    2. 支持以拖拽的方式来选取要上传的文件
    3. 支持在前端压缩图片,即在图片文件还未上传之前就对它进行压缩
    4. 可以直接读取原生的文件数据,这样的好处就是例如可以在图片文件还未上传之前就能把它显示在页面上预览
    5. 支持把大文件切割成小片进行上传,因为有些浏览器对很大的文件比如几G的一些文件无法上传。

    环境

    • vue2.x
    • webpack3.x
    • axios

    代码

    npm安装plupload,文件引入组件,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <uploader browse_button="upload_area"
                    :max_retries="3"
                    :url="action"
                    :headers="headers"
                    chunk_size="10MB"
                    drop_element="upload_area"
                    @disableBrowse="!loading"
                    :BeforeUpload="beforeUpload"
                    :ChunkUploaded="chunkUploaded"
                    :FilesAdded="filesAdded"
                    :StateChanged="stateChanged"
                    @inputUploader="inputUploader" />

      

    初始化方法filesAdded(),每次上传前清空队列的其他文件,保证上传的一致性。其次对文件类型进行判断过滤fileType(),文件进入时进行总md5一次fileMd5(),然后进入文件分片chunkCheckStatus(),每个分片都要进行md5并与后台进行校验fileMd5(),确保文件在中断后继续上传的准确性。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    filesAdded (up, files) {
          // 删除上传队列中其他文件,只保留最近上传的文件
          let fileLen = files.length, that = this
          if (fileLen > 1) {
            files = files.splice(0, fileLen - 1)// 清空上传队列
          }
          files.forEach((f) => {
            f.status = -1
            that.dataForm.file = f
            that.fileType(f.getNative())
            if (that.loading) {
              that.computeStatus = true
              that.progress = 0
              // 文件分片
              let chunkSize = 2097152, // Read in chunks of 2MB
                chunks = Math.ceil(f.size / chunkSize)
              that.fileMd5(f.getNative(), (e, md5) => {
                that.dataForm.md5 = md5
                if (that.loading == true) {
                  that.count = 0
                  that.chunkCheckStatus(md5, that.dataForm.fileName, (uploader, dataList) => {
                    that.uploading = uploader
                    if (that.uploading == true) {
                      for (let chunk = 1; chunk <= chunks; chunk++) {
                        that.fileChunkFile(f.getNative(), chunk, (e, chunkFile) => {
                          that.fileMd5(chunkFile, (e, blockMd5) => {
                            that.PostFile(up, chunkFile, chunk, chunks, md5, blockMd5)
                          })
                        })
                      }
                    else {
                      // 去重
                      that.progress = 0
                      for (let chunk = 1; chunk <= chunks; chunk++) {
                        let status = 0
                        dataList.some((item) => {
                          if (item.chunk == chunk) {
                            status = 1
                            return false
                          }
                        })
                        if (status == 0) {
                          that.fileChunkFile(f.getNative(), chunk, (e, chunkFile) => {
                            that.fileMd5(chunkFile, (e, blockMd5) => {
                              that.PostFile(up, chunkFile, chunk, chunks, md5, blockMd5)
                            })
                          })
                        }
                      }
                    }
                  })
                }
              })
            }
          })
        }

      

    文件md5方法,这里使用了SparkMD5,import SparkMD5 from 'spark-md5'

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    fileMd5 (file, callback) {
          let that = this
          var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,
            file = file,
            chunkSize = 2097152, // Read in chunks of 2MB
            chunks = Math.ceil(file.size / chunkSize),
            currentChunk = 0,
            spark = new SparkMD5.ArrayBuffer(),
            fileReader = new FileReader()
          fileReader.onload = function (e) {
            console.log('read chunk nr', currentChunk + 1, 'of', chunks)
            spark.append(e.target.result) // Append array buffer
            currentChunk++
            if (currentChunk < chunks) {
              loadNext()
            else {
              let blockMd5 = ''
              blockMd5 = spark.end()
              callback(null, blockMd5)
            }
          }
          fileReader.onerror = function () {
            callback('oops, something went wrong.')
          }
          function loadNext () {
            var start = currentChunk * chunkSize,
              end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize
            fileReader.readAsArrayBuffer(blobSlice.call(file, start, end))
          }
          loadNext()
        }

      

    文件分片上传方法,验证总分片信息后,把每个分片进行md5加密并上传校验,这里有写进度条相关的控制,不一一展示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    chunkCheckStatus (md5, fileName, callback) {
          this.$http({
            url: this.$http.adornUrl('/biz/upload/getFileBlockStatus'),
            method: 'get',
            params: this.$http.adornParams({
              md5: md5,
              fileName: fileName
            })
          }).then(({ data }) => {
            if (data && data.code === 0) {
              if (data.list != null) {
                this.uploading = false
                this.chunkCheckData = []
                data.list.map((item, index) => {
                  if (item.isUpload == true) {
                    this.count++
                    this.chunkCheckData.push(item)
                  }
                })
                callback(this.uploading, this.chunkCheckData)
                return
              }
              this.uploading = true
              callback(this.uploading)
            else {
              this.$message.error(data.msg)
              this.loading = false
              this.computeStatus = false
              return false
            }
          })
        }

      

    详细的配置信息可以参考我写的这篇文章:http://blog.ncmem.com/wordpress/2019/08/09/vue%e5%a4%a7%e6%96%87%e4%bb%b6%e6%96%ad%e7%82%b9%e7%bb%ad%e4%bc%a0/ 

  • 相关阅读:
    使用Docker-compose部署MySQL测试环境
    使用MySQL SQL线程回放Binlog实现恢复
    MySQL 插件之 连接控制插件(Connection-Control)
    sysbench工具使用
    故障分析--主从复制故障1
    MySQL性能指标计算方式
    AJAX的 同步异步;EZView.js 图片预览和pdf预览
    Caused by: java.lang.ClassNotFoundException: org.springframework.context.App
    解决Myeclipse或Eclipse出现JPA project Change Event Handler问题的解决办法
    java中转换为string的方法;eques和==区别
  • 原文地址:https://www.cnblogs.com/xproer/p/11362248.html
Copyright © 2020-2023  润新知