webuploader前端分片上傳

前端分片上傳附件

  1. 分片上傳定義:

所謂的分片上傳抱完,就是將所要上傳的文件,按照一定的大小刃泡,將整個(gè)文件分隔成多個(gè)數(shù)據(jù)塊(我們稱之為 Part)來進(jìn)行分別上傳巧娱,上傳完之后再由服務(wù)端對(duì)所有上傳的文件進(jìn)行匯總整合成原始的文件。分片上傳不僅可以避免因網(wǎng)絡(luò)環(huán)境不好導(dǎo)致的一直需要從文件起始位置還是上傳的問題烘贴,還能使用多線程對(duì)不同分塊數(shù)據(jù)進(jìn)行并發(fā)發(fā)送禁添,提高發(fā)送效率,降低發(fā)送時(shí)間桨踪。

  1. 主要適用于以下幾種場(chǎng)景:
  • 網(wǎng)絡(luò)環(huán)境不好:當(dāng)出現(xiàn)上傳失敗的時(shí)候老翘,可以對(duì)失敗的 Part 進(jìn)行獨(dú)立的重試,而不需要重新上傳其他的 Part。
  • 斷點(diǎn)續(xù)傳:中途暫停之后酪捡,可以從上次上傳完成的 Part 的位置繼續(xù)上傳叁征。
  • 加速上傳:要上傳到服務(wù)器的本地文件很大的時(shí)候,可以并行上傳多個(gè) Part 以加快上傳逛薇。
  • 流式上傳:可以在需要上傳的文件大小還不確定的情況下開始上傳捺疼。這種場(chǎng)景在視頻監(jiān)控等行業(yè)應(yīng)用中比較常見。
  • 文件較大:一般文件比較大時(shí)永罚,默認(rèn)情況下一般都會(huì)采用分片上傳啤呼。
  1. 分片上傳的流程大致如下:
  • 將需要上傳的文件按照一定的分割規(guī)則,分割成相同大小的數(shù)據(jù)塊呢袱;
  • 初始化一個(gè)分片上傳任務(wù)官扣,返回本次分片上傳唯一標(biāo)識(shí);
  • 按照一定的策略(串行或并行)發(fā)送各個(gè)分片數(shù)據(jù)塊羞福;
  • 發(fā)送完成后惕蹄,服務(wù)端根據(jù)判斷數(shù)據(jù)上傳是否完整,如果完整治专,則進(jìn)行數(shù)據(jù)塊合成得到原始文件
  1. 分片上傳規(guī)則如下:
  • 定義分片規(guī)則大小: chunkSize: 5 * 1024 * 1024 // 分多大一片 5M;
  • 上傳并發(fā)數(shù): threads: 3 // 上傳并發(fā)數(shù), 允許同時(shí)最大上傳進(jìn)程數(shù);
  • 定義分片上傳對(duì)象: 定義分片上傳對(duì)象基礎(chǔ)屬性包含附件文件名卖陵、原始文件大小、原始文件 MD5 值张峰、分片總數(shù)泪蔫、每個(gè)分片大小、當(dāng)前分片大小喘批、當(dāng)前分片序號(hào)等. 定義基礎(chǔ)屬于便于后續(xù)對(duì)文件合理分割撩荣、分片的合并等業(yè)務(wù)拓展,當(dāng)然根據(jù)業(yè)務(wù)場(chǎng)景可以定義拓展屬性;

5.前端分片上傳使用方法:

下載 import Webuploader from 'webuploader';
fileSliceUpload.vue文件:

<template>
  <!-- 模型 - 分片上傳 -->
  <div id="slice_uploader" class="slice-uploader">
    <div>上傳附件</div>
  </div>
</template>

<script>
import WebUploader from 'webuploader'
import { sliceDetail } from '@/api/manager.js'
export default {
  name: 'FileSliceUpload',
  props: {
    // 分片上傳url
    url: {
      type: String,
      default: '',
    },
    // 文件數(shù)量限制
    fileNumLimit: {
      type: Number,
      default: 1,
    },
    isLoading: {
      type: Boolean,
      default: false,
    },
    fileSizeLimit: {
      type: Number,
      default: 2 * 1024 * 1024 * 1024, // 2G
    },
    isReupload: {
      // 默認(rèn)初始上傳饶深,為true時(shí)是再次上傳
      type: Boolean,
      default: false,
    },
  },
  data() {
    return {
      uploader: null,
      fileMd5: '',
      fileName: '',
      entireUpload: false,
      uploadMsg: {
        totalChunks: 0,
        identifier: [],
      },
      fileId: '',
      response: null,
      chunkList: [],
      chunkTotal: 0,
      successNum: 0,
      currentFile: null,
    }
  },
  mounted() {
    this.$nextTick(() => {
      this.initUploader()
    })
  },
  methods: {
    initUploader() {
      let that = this
      let acceptRules = [
        {
          mimeTypes: '.zip,.jar',
        },
      ]
      const _this = this
      //監(jiān)聽分塊上傳過程中的三個(gè)時(shí)間點(diǎn)
      WebUploader.Uploader.unRegister('contractUpload')
      WebUploader.Uploader.register(
        {
          name: 'contractUpload',
          'before-send-file': 'beforeSendFile', //整個(gè)文件上傳前
          'before-send': 'beforeSend',
          'after-send-file': 'afterSendFile',
        },
        {
          // 所有分塊進(jìn)行上傳之前調(diào)用此函數(shù)
          beforeSendFile(file) {
            var deferred = WebUploader.Deferred()
            _this.fileMd5 = ''
            _this.uploader.md5File(file).then((val) => {
              _this.fileMd5 = val
              _this.fileId = ''
              _this.chunkList = []
              sliceDetail({
                md5: _this.fileMd5,
              })
                .then((res) => {
                  if (res.data.chunkList) {
                    _this.chunkList = res.data.chunkList
                    _this.fileId = res.data.fileId
                  }
                  deferred.resolve()
                })
                .catch((err) => {
                  this.$message.error(err.errmsg)
                  console.log(err, 'err')
                })
            })
            // }

            return deferred.promise()
          },
          // 如果有分塊上傳餐曹,則上傳之前調(diào)用此函數(shù)
          beforeSend(block) {
            _this.chunkTotal = block.chunks
            var deferred = WebUploader.Deferred()
            // _this.chunkList = _this.chunkList.slice(0, 5)
            // 跳過如果存在則跳過
            if (block.chunks === _this.chunkList.length) {
              const file = {}
              const response = {
                data: {
                  fileId: _this.fileId,
                },
              }
              that.$emit('uploadSuccess', file, response)
              that.$emit('uploadProgress', block.file.name, 1)

              deferred.reject() // 跳過該分片
              _this.uploader.removeFile(block.file)
              _this.uploader.reset()

              return deferred.promise()
            }
            // 如果里面有上傳過的分片
            else if (
              _this.chunkList.includes(block.chunk) &&
              block.chunks !== _this.chunkList.length
            ) {
              // this.owner.skipFile(block.file)
              _this.successNum++
              deferred.reject() // 跳過該分片
            } else {
              deferred.resolve() // 繼續(xù)上傳該分片
            }
            return deferred.promise()
          }
        }
      )

      // 1. 文件上傳方法的參數(shù)說明
      this.uploader = WebUploader.create({
        auto: true,
        server: this.url,
        dnd: '#slice_uploader',
        pick: '#slice_uploader',
        fileNumLimit: this.fileNumLimit, // 驗(yàn)證文件總數(shù)量, 超出則不允許加入隊(duì)列
        fileSingleSizeLimit: this.fileSizeLimit, // 驗(yàn)證文件總大小是否超出限制, 超出則不允許加入隊(duì)列
        chunked: true, // 是否要分片處理大文件上傳
        chunkRetry: 3, // 如果某個(gè)分片由于網(wǎng)絡(luò)問題出錯(cuò),允許自動(dòng)重傳多少次
        chunkSize: 5 * 1024 * 1024, // 分多大一片 5M
        threads: 3, // 上傳并發(fā)數(shù)粥喜。允許同時(shí)最大上傳進(jìn)程數(shù)
        multiple: false,
        resize: false,
        // duplicate: true,
        disableGlobalDnd: true,
        accept: acceptRules,
        formData: {
          sliceSize: 5242880,
        },
        timeout: 60000, //超時(shí)時(shí)間
      })
      // 文件入隊(duì)列之前
      this.uploader.on('beforeFileQueued', (file) => {
        that.uploadMsg.totalChunks = 0
        that.$emit('setLoadingStatus', true)
        that.$emit('uploadProgress', file.name, 0)
        const zipTypes = [
          'application/x-zip',
          'application/zip',
          'application/x-zip-compressed',
        ]
        // 如果不是zip文件凸主,不再進(jìn)行分片上傳
        if (
          file.ext !== 'zip' &&
          !zipTypes.includes(file.type) &&
          file.ext !== 'jar'
        ) {
          this.entireUpload = true
          this.uploader.options.chunked = false
        } else {
          this.entireUpload = false
          this.uploader.options.chunked = true
        }
      })

      // 文件入隊(duì)列(.on事件監(jiān)聽不到)
      this.uploader.on('fileQueued', (file) => {
        this.fileName = file.name
      })

      // 監(jiān)聽分片上傳 可以添加參數(shù)
      this.uploader.on('uploadBeforeSend', (block, data, headers) => {
        data.md5 = this.fileMd5
      })

      // 處理分片接收
      this.uploader.on('uploadAccept', (file, res) => {
        if (res.errno !== 0) {
          that.$emit('uploadError', file)
          this.$message.error(res.errmsg)
          this.uploader.stop()
          this.uploader.reset()
          this.successNum = 0
        } else {
          this.successNum++
        }
        that.$emit(
          'uploadProgress',
          file.file.name,
          this.successNum / this.chunkTotal
        )
      })

      // 當(dāng)文件上傳成功時(shí)觸發(fā)
      this.uploader.on('uploadSuccess', (file, response) => {
        that.$emit('uploadSuccess', file, response)
        this.successNum = 0
        this.uploader.removeFile(file)
      })

      // 上傳失敗方法
      this.uploader.on('uploadError', (file, reason) => {
        that.$emit('uploadError', file, reason)
      })
    },
  },
  beforeDestroy() {
    this.uploader.reset()
  },
}
</script>

使用

 <file-slice-upload
     url="/xxx/xxx/api/sliceList"
    :isLoading="isLoading"
    @setLoadingStatus="setLoadingStatus"
    @mergeCompleted="uploadSuccess"
    @uploadSuccess="uploadSuccess"
    @uploadProgress="uploadProgress"
    @uploadError="uploadError"
  >
    setLoadingStatus(val) {
      this.isLoading = val
    },
    uploadSuccess(val, data) {
      if (data && data.data && data.data.fileId) {
        this.sliceFileId = data.data.fileId || ''
      }
      this.uploadStatus = '上傳成功'
      this.isLoading = false
      //上傳成功
    },
    uploadProgress(filename, percent) {
      // this.isLoading = false
      this.uploadData = { filename, percent } //進(jìn)度條
    },
    uploadError(file) {
      this.uploadStatus = '上傳失敗'
      this.isLoading = false //上傳失敗
    },
  1. 總結(jié):

所謂的分片上傳,就是將所要上傳的文件额湘,按照一定的大小,將整個(gè)文件分隔成多個(gè)數(shù)據(jù)塊(我們稱之為 Part)來進(jìn)行分別上傳旁舰。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末锋华,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子箭窜,更是在濱河造成了極大的恐慌毯焕,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異纳猫,居然都是意外死亡婆咸,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門芜辕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來尚骄,“玉大人,你說我怎么就攤上這事侵续【笳桑” “怎么了?”我有些...
    開封第一講書人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵状蜗,是天一觀的道長(zhǎng)需五。 經(jīng)常有香客問我,道長(zhǎng)轧坎,這世上最難降的妖魔是什么宏邮? 我笑而不...
    開封第一講書人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮缸血,結(jié)果婚禮上蜜氨,老公的妹妹穿的比我還像新娘。我一直安慰自己属百,他們只是感情好记劝,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著族扰,像睡著了一般厌丑。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上渔呵,一...
    開封第一講書人閱讀 49,829評(píng)論 1 290
  • 那天怒竿,我揣著相機(jī)與錄音,去河邊找鬼扩氢。 笑死耕驰,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的录豺。 我是一名探鬼主播朦肘,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼双饥!你這毒婦竟也來了媒抠?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤咏花,失蹤者是張志新(化名)和其女友劉穎趴生,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡苍匆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年刘急,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片浸踩。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡叔汁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出民轴,到底是詐尸還是另有隱情攻柠,我是刑警寧澤悯搔,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布给赞,位于F島的核電站,受9級(jí)特大地震影響轿衔,放射性物質(zhì)發(fā)生泄漏微驶。R本人自食惡果不足惜浪谴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望因苹。 院中可真熱鬧苟耻,春花似錦、人聲如沸扶檐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽款筑。三九已至智蝠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間奈梳,已是汗流浹背杈湾。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留攘须,地道東北人漆撞。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像于宙,于是被迫代替她去往敵國(guó)和親浮驳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容