webuploader前端分片上传

前端分片上传附件

  1. 分片上传定义:

所谓的分片上传,就是将所要上传的文件,按照一定的大小,将整个文件分隔成多个数据块(我们称之为 Part)来进行分别上传,上传完之后再由服务端对所有上传的文件进行汇总整合成原始的文件。分片上传不仅可以避免因网络环境不好导致的一直需要从文件起始位置还是上传的问题,还能使用多线程对不同分块数据进行并发发送,提高发送效率,降低发送时间。

  1. 主要适用于以下几种场景:
  • 网络环境不好:当出现上传失败的时候,可以对失败的 Part 进行独立的重试,而不需要重新上传其他的 Part。
  • 断点续传:中途暂停之后,可以从上次上传完成的 Part 的位置继续上传。
  • 加速上传:要上传到服务器的本地文件很大的时候,可以并行上传多个 Part 以加快上传。
  • 流式上传:可以在需要上传的文件大小还不确定的情况下开始上传。这种场景在视频监控等行业应用中比较常见。
  • 文件较大:一般文件比较大时,默认情况下一般都会采用分片上传。
  1. 分片上传的流程大致如下:
  • 将需要上传的文件按照一定的分割规则,分割成相同大小的数据块;
  • 初始化一个分片上传任务,返回本次分片上传唯一标识;
  • 按照一定的策略(串行或并行)发送各个分片数据块;
  • 发送完成后,服务端根据判断数据上传是否完整,如果完整,则进行数据块合成得到原始文件
  1. 分片上传规则如下:
  • 定义分片规则大小: chunkSize: 5 * 1024 * 1024 // 分多大一片 5M;
  • 上传并发数: threads: 3 // 上传并发数, 允许同时最大上传进程数;
  • 定义分片上传对象: 定义分片上传对象基础属性包含附件文件名、原始文件大小、原始文件 MD5 值、分片总数、每个分片大小、当前分片大小、当前分片序号等. 定义基础属于便于后续对文件合理分割、分片的合并等业务拓展,当然根据业务场景可以定义拓展属性;

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: '',
    },
    // 文件数量限制
    fileNumLimit: {
      type: Number,
      default: 1,
    },
    isLoading: {
      type: Boolean,
      default: false,
    },
    fileSizeLimit: {
      type: Number,
      default: 2 * 1024 * 1024 * 1024, // 2G
    },
    isReupload: {
      // 默认初始上传,为true时是再次上传
      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
      //监听分块上传过程中的三个时间点
      WebUploader.Uploader.unRegister('contractUpload')
      WebUploader.Uploader.register(
        {
          name: 'contractUpload',
          'before-send-file': 'beforeSendFile', //整个文件上传前
          'before-send': 'beforeSend',
          'after-send-file': 'afterSendFile',
        },
        {
          // 所有分块进行上传之前调用此函数
          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()
          },
          // 如果有分块上传,则上传之前调用此函数
          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() // 继续上传该分片
            }
            return deferred.promise()
          }
        }
      )

      // 1. 文件上传方法的参数说明
      this.uploader = WebUploader.create({
        auto: true,
        server: this.url,
        dnd: '#slice_uploader',
        pick: '#slice_uploader',
        fileNumLimit: this.fileNumLimit, // 验证文件总数量, 超出则不允许加入队列
        fileSingleSizeLimit: this.fileSizeLimit, // 验证文件总大小是否超出限制, 超出则不允许加入队列
        chunked: true, // 是否要分片处理大文件上传
        chunkRetry: 3, // 如果某个分片由于网络问题出错,允许自动重传多少次
        chunkSize: 5 * 1024 * 1024, // 分多大一片 5M
        threads: 3, // 上传并发数。允许同时最大上传进程数
        multiple: false,
        resize: false,
        // duplicate: true,
        disableGlobalDnd: true,
        accept: acceptRules,
        formData: {
          sliceSize: 5242880,
        },
        timeout: 60000, //超时时间
      })
      // 文件入队列之前
      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文件,不再进行分片上传
        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
        }
      })

      // 文件入队列(.on事件监听不到)
      this.uploader.on('fileQueued', (file) => {
        this.fileName = file.name
      })

      // 监听分片上传 可以添加参数
      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
        )
      })

      // 当文件上传成功时触发
      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 } //进度条
    },
    uploadError(file) {
      this.uploadStatus = '上传失败'
      this.isLoading = false //上传失败
    },
  1. 总结:

所谓的分片上传,就是将所要上传的文件,按照一定的大小,将整个文件分隔成多个数据块(我们称之为 Part)来进行分别上传。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,270评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,489评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,630评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,906评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,928评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,718评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,442评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,345评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,802评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,984评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,117评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,810评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,462评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,011评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,139评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,377评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,060评论 2 355

推荐阅读更多精彩内容