Vue.js實現大文件分片md5斷點續傳

背景

根據部門的業務需求,需要在網絡狀態不良的情況下上傳很大的文件(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,文件引入組件,

<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(),確保文件在中斷後繼續上傳的準確性。

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'

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加密並上傳校驗,這裏有寫進度條相關的控制,不一一展示

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
        }
      })
    }

  

總結

4095867158-5ac2049c8e1d6_articlex
上圖可以清晰的說明文件加密上傳的傳輸流程,在文件上傳加密及Content-Type格式需要與後臺協商一致,通常有base64、multipart/form-data兩種類型,要清晰瞭解分片算法及md5,自定義上傳文件異步代碼,除此之外,可拖拽上傳文件、進度條控制等可更好的豐富文件上傳體驗。

注意

功能在某些舊版瀏覽器中不起作用。

參考

https://www.plupload.com/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章