文件分批、分片上传

       最近一直在处理文件的分批、分片、自动上传,分批、分片上传还好处理,说起自动上传这个功能,各种权限的判断,各种情况的考虑,搞得头大如斗,今天主要说一下分批、分片上传:

先看效果图:

一、基础属性

data() {
    return {
      totalSizeImage: 0, //所有文件size
      uploadSizeImage: 0, //上传size
      dataFileListImage: [], //文件列表
      eachSize: 2 * 1024 * 1024, //文件切片尺寸
      dialogLoading: false, //
      dialogLoadText: '加载中···',
      page: {
        size: 10,
        current: 1,
        total: 0,
      },
      dataTable: [], //飞行图片分页显示数据
      maxUploadCount: 3, //最大上传文件数量
    }
  },

二、文件选择

accept:支持文件类型;multiple:是否支持多选;webkitdirectory:是否选择文件夹,true 是选择文件夹,false或者去掉是选择文件;

onchange触发后,要清理掉添加的input节点;

md5的加密处理,这个根据项目自身情况来定,不一定非要用md5

文件分片:我这里是按照2MB进行分片处理的,把大文件分成多个小文件进行上传,记录下当前上传的片数,可以做到断点续传

 handleChoiceFile() {
      let fileInput = document.createElement('input')
      fileInput.type = 'file'
      fileInput.accept = 'image/*'
      fileInput.multiple = true
      fileInput.webkitdirectory = true
      fileInput.click()
      fileInput.onchange = () => {
        let temp_files = Array.from(fileInput.files)
        this.uploadInputChange(temp_files)
        fileInput.remove()
      }
    },
    async uploadInputChange(files) {
      try {
        let tempFiles = files
        //判断选择文件是否超过30G
        let totalSize = 0
        tempFiles.forEach((item) => {
          totalSize += parseInt(item.fileSize)
        })
        if (totalSize > 30 * 1024 * 1024 * 1024) {
          return this.$message.warning(
            '图片总大小超过30GB,可以考虑分割!'
          )
        }
        let temp_file_list = []
        this.dialogLoading = true
        this.dialogLoadText = '文件读取中···'
        for (var i = 0; i < tempFiles.length; i++) {
          let item = tempFiles[i]
          // 这里可以对文件进行类型判断
          this.dialogLoadText = `读取文件${i + 1}/${tempFiles.length}`
          let chunks = Math.ceil(item.size / this.eachSize)
          let params = {
            file: item,
            fileName: item.name, //文件名称
            fileSize: item.size, //文件大小
            status: '1', //上传状态
            statusName: '待上传',
            totalChunk: chunks, // 切片个数
            chunkFile: [], // 切片文件
            currentChunk: 0, //默认上传的分片索引
            fileMd5: '', // md5,这里是文件第一片和最后一片相加的字符串,用于验证文件
            relativePath: '', //文件夹内的相对路径
            ratio: 0,
          }
          // 处理分片数据
          for (let chunk = 0; chunks > 0; chunks--) {
            let chunkFile = item.slice(chunk, chunk + this.eachSize)
            params.chunkFile.push(chunkFile)
            chunk += this.eachSize
          }
          // 计算md5
          let first = await params.chunkFile[0].text()
          let last = await params.chunkFile[params.chunkFile.length - 1].text()
          params.fileMd5 = md5(first + last)
          // 判断文件是否已经存在,如果存在则替换 chunkFile 和 file 
          if (this.dataFileListImage.length > 0) {
              let temp_index = this.dataFileListImage.findIndex(
                (o) => o.fileMd5 == params.fileMd5
              )
              if (temp_index > -1) {
                this.dataFileListImage[temp_index].chunkFile = params.chunkFile
                this.dataFileListImage[temp_index].file = params.file
                // 4,5是暂停和失败状态,这里替换成待上传状态
                if (
                  ['4', '5'].includes(this.dataFileListImage[temp_index].status)
                ) {
                  this.dataFileListImage[temp_index].status = params.status
                  this.dataFileListImage[temp_index].statusName =
                    params.statusName
                }
              } else {
                //未找到对应文件的,把文件添加到数组中
                this.dataFileListImage.push(params)
                //添加的临时数组中,用于初始化
                temp_file_list.push(params)
              }
            } else {
              this.dataFileListImage.push(params)
              //添加的临时数组中,用于初始化
              temp_file_list.push(params)
            }
          // 最后一个文件读取结束后,开始初始化
          if (i == tempFiles.length - 1) {
            if (temp_file_list.length > 0) {
              this.dialogLoadText = '文件初始化中···'
              this.dataFileListImage = temp_file_list
              //文件选择后,直接初始化
              let obj = {
                files: temp_file_list.map((v) => {
                  return {
                    fileMd5: v.fileMd5,
                    fileName: v.fileName,
                    fileSize: v.fileSize,
                    relativePath: v.relativePath,
                    totalChunk: v.totalChunk,
                  }
                }),
              }
              // 文件初始化方法,只初始化新增的文件
              const { data } = await fileInit(obj).finally(() => {
                this.dialogLoading = false
                this.dialogLoadText = '加载中···'
              })
              if (data.code == 0) {
                  // 初始化完成,重新获取文件列表
                  await this.getFileListImage()
                 // 计算文件size
                  this.computeImageTotalSize()
                 // 对文件进行前端分页,文件太多,分页更合适
                  this.setTableData()
                }
            } else {
              // 没有需要初始化的文件,这里重新分页
              this.setTableData()
            }
            this.dialogLoading = false
            this.dialogLoadText = '加载中···'
          }
        }
      } catch (error) {
        console.log(error)
        this.dialogLoading = false
        this.dialogLoadText = '加载中···'
      }
    },

 计算进度这里说一下:当文件上传片数和总片数相等时,使用文件的原始尺寸,这里不能通过计算,否则结果是偏大的

获取文件列表:主要判断一下文件是否已经存在,已经存在的要替换一下file 和 chunkFile,file对应的是当前文件,chunkFile对应的是切片后的文件数组,在初始化的时候这两个字段是不进行初始化的

// 计算上传进度
computeImageTotalSize() {
      let image_size = 0,
        image_upload = 0
      this.dataFileListImage.forEach((item) => {
        image_size += parseInt(item.fileSize)
        if (item.currentChunk >= item.totalChunk) {
          image_upload += parseInt(item.fileSize)
        } else {
          image_upload += parseInt(item.currentChunk) * this.eachSize
        }
      })
      this.totalSizeImage = image_size
      this.uploadSizeImage = image_upload
    },
   // 获取文件列表
   async getFileListImage() {
      const { data } = await uploadList()
      if (data.code == 0 && data.data) {
        if (this.dataFileListImage.length == 0) {
          this.dataFileListImage = data.data.map((item) => {
            item.file = null
            item.chunkFile = []
            return item
          })
        } else {
          this.dataFileListImage = data.data.map((item) => {
            let find = this.dataFileListImage.find(
              (o) => o.fileMd5 == item.fileMd5
            )
            if (find) {
              item.file = find.file
              item.chunkFile = find.chunkFile
            }
            return item
          })
        }
        this.setTableData()
      }
    },
    //计算分页
    setTableData() {
      this.page.total = this.dataFileListImage.length
      this.dataTable = []
      this.dataTable = this.dataFileListImage.slice(
        this.page.size * (this.page.current - 1),
        this.page.size * this.page.current
      )
    },

开始上传:判断文件是否存在,因为是多文件同时上传,判断当前上传中的文件数,以及需要上传的文件数,根据最大上传文件数计算得到可以上传的文件数,然后循环上传即可

切片上传:我这里对列表的处理都是在前端修改的,没有调用接口,调用接口重新获取列表会增加复杂度,也不好控制,所以这里直接在前端控制即可;每次上传一片后就要计算一下总进度,做到实时更新

//开始上传
    async handleStartUpload() {
      if (this.dataTable.length == 0)
          return this.$message.warning('请选择文件')
        //当前上传中的文件
        let uploading_list = this.dataTable.filter((o) => o.status == '2')
        //还未上传的文件
        let notUpload_list = this.dataTable.filter((o) => o.status == '1')
        //获取剩余可以上传的文件数
        let left_upload_count = this.maxUploadCount - uploading_list.length
        //需要上传的文件数
        let need_upload_count = 0
        // 未上传的文件大于0,继续上传
        if (notUpload_list.length > 0) {
          if (notUpload_list.length >= left_upload_count) {
            need_upload_count = left_upload_count
          } else {
            need_upload_count = notUpload_list.length
          }
          for (var i = 0; i < need_upload_count; i++) {
            let item = this.dataTable.find((o) => o.status == '1')
            this.uploadChunkFile(item)
          }
        } else {
          //判断是否还有正在上传中的文件,如果没有,则进行翻页
          if (uploading_list.length == 0) {
            //判断是否需要翻页
            if (this.page.size * this.page.current < this.page.total) {
              this.page.current += 1
              this.setTableData()
              this.handleStartUpload()
            } else {
              // 不需要翻页
              //判断文件总数组是否全部上传完成
              let index = this.dataFileListImage.findIndex(
                (o) => o.status == '1'
              )
              // 发现未上传的文件,则继续上传
              if (index > -1) {
                this.page.current = Math.ceil(index / this.page.size)
                this.setTableData()
                this.handleStartUpload()
              }
            }
          }
        }
    },
    //切片上传
    async uploadChunkFile(row, type) {
      let chunkFile = row.chunkFile
      row.status = '2'
      row.statusName = '上传中'
      try {
        for (var i = 0; i < chunkFile.length; i++) {
          if (i == row.currentChunk) {
            let param = {
              chunk: i,
              chunks: row.totalChunk,
              chunkSize: this.eachSize,
              file: chunkFile[i],
              fileName: row.fileName,
              fullSize: row.fileSize,
              md5: row.fileMd5,
            }
            let formData = new FormData()
            for (let p in param) {
              formData.append(p, param[p])
            }
            const { data } = await chunkUpload(formData).catch(() => {
              row.currentChunk = row.currentChunk + 1
              this.computeImageTotalSize()
              row.status = '5'
              row.statusName = '已失败'
            })
            if (data.code == 0) {
              row.currentChunk = row.currentChunk + 1
            }
            this.computeImageTotalSize()
            //如果已暂停,则直接终止上传
            if (row.status == '4') {
              break
            }
          }
          if (row.currentChunk == row.totalChunk) {
            row.status = '3'
            row.statusName = '已完成'
            this.computeImageTotalSize()
            this.handleStartUpload()
          }
        }
      } catch (err) {
        console.log(err, 'err')
      }
    },

  • 8
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

travel_wsy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值