vue 大文件分片上传(断点续传、并发上传、秒传)

 更新时间:2022年07月08日 08:54:32   作者:小小猴冲刺  
本文主要介绍了vue 大文件分片上传,主要包括断点续传、并发上传、秒传,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

对于大文件的处理,无论是用户端还是服务端,如果一次性进行读取发送、接收都是不可取,很容易导致内存问题。所以对于大文件上传,采用切块分段上传,从上传的效率来看,利用多线程并发上传能够达到最大效率。

本文是基于 springboot + vue 实现的文件上传,本文主要介绍vue实现文件上传的步骤及代码实现,服务端(springboot)的实现步骤及实现请移步本人的另一篇文章:

springboot 大文件上传、分片上传、断点续传、秒传 

上传分步:

本人分析上传总共分为:

  • MD5读取文件,获取文件的MD5编码
  • 请求服务端判断文件是否上传,如上传完成就直接返回文件地址
  • 如未上传,判断是否是断点续传
  • 判断是并发上传还是顺序上传
  • 开始分片文件上传,分片上传完成后写入已上传列表中
  • 判断是否上传完成

直接上代码

文件上传:

import md5 from 'js-md5' //引入MD5加密
import UpApi from '@/api/common.js'
import { concurrentExecution } from '@/utils/jnxh'
 
/**
 * 文件分片上传
 * @params file {File} 文件
 * @params pieceSize {Number} 分片大小 默认3MB
 * @params concurrent {Number} 并发数量 默认2
 * @params process {Function} 进度回调函数
 * @params success {Function} 成功回调函数
 * @params error {Function} 失败回调函数
 */
export const uploadByPieces = ({
                                 file,
                                 pieceSize = 3,
                                 concurrent = 3,
                                 success,
                                 process,
                                 error
                               }) => {
  // 如果文件传入为空直接 return 返回
  if (!file || file.length < 1) {
    return error('文件不能为空')
  }
  let fileMD5 = '' // 总文件列表
  const chunkSize = pieceSize * 1024 * 1024 // 1MB一片
  const chunkCount = Math.ceil(file.size / chunkSize) // 总片数
  const chunkList = [] // 分片列表
  let uploaded = [] // 已经上传的
  let fileType = '' // 文件类型
  // 获取md5
  /***
   * 获取md5
   **/
  const readFileMD5 = () => {
    // 读取视频文件的md5
    fileType = file.name.substring(file.name.lastIndexOf('.') + 1, file.name.length)
    console.log('获取文件的MD5值')
    let fileRederInstance = new FileReader()
    console.log('file', file)
    fileRederInstance.readAsBinaryString(file)
    fileRederInstance.addEventListener('load', e => {
      let fileBolb = e.target.result
      fileMD5 = md5(fileBolb)
      var index = file.name.lastIndexOf('.')
      var tp = file.name.substring(index + 1, file.name.length)
      let form = new FormData()
      form.append('filename', file.name)
      form.append('identifier', fileMD5)
      form.append('objectType', fileType)
      form.append('chunkNumber', 1)
      UpApi.uploadChunk(form).then(res => {
        if (res.skipUpload) {
          console.log('文件已被上传')
          success && success(res)
        } else {
          // 判断是否是断点续传
          if (res.uploaded && res.uploaded.length != 0) {
            uploaded = [].concat(res.uploaded)
          }
          console.log('已上传的分片:' + uploaded)
          // 判断是并发上传或顺序上传
          if (concurrent == 1 || chunkCount == 1) {
            console.log('顺序上传')
            sequentialUplode(0)
          } else {
            console.log('并发上传')
            concurrentUpload()
          }
        }
      }).catch((e) => {
        console.log('文件合并错误')
        console.log(e)
      })
    })
  }
  /***
   * 获取每一个分片的详情
   **/
  const getChunkInfo = (file, currentChunk, chunkSize) => {
    let start = currentChunk * chunkSize
    let end = Math.min(file.size, start + chunkSize)
    let chunk = file.slice(start, end)
    return {
      start,
      end,
      chunk
    }
  }
  /***
   * 针对每个文件进行chunk处理
   **/
  const readChunkMD5 = () => {
    // 针对单个文件进行chunk上传
    for (var i = 0; i < chunkCount; i++) {
      const {
        chunk
      } = getChunkInfo(file, i, chunkSize)
 
      // 判断已经上传的分片中是否包含当前分片
      if (uploaded.indexOf(i + '') == -1) {
        uploadChunk({
          chunk,
          currentChunk: i,
          chunkCount
        })
      }
    }
  }
  /***
   * 原始上传
   **/
  const uploadChunk = (chunkInfo) => {
    var sd = parseInt((chunkInfo.currentChunk / chunkInfo.chunkCount) * 100)
    console.log(sd, '进度')
    process(sd)
    console.log(chunkInfo, '分片大小')
    let inde = chunkInfo.currentChunk + 1
    if (uploaded.indexOf(inde + '') > -1) {
      const {
        chunk
      } = getChunkInfo(file, chunkInfo.currentChunk + 1, chunkSize)
      uploadChunk({
        chunk,
        currentChunk: inde,
        chunkCount
      })
    } else {
      var index = file.name.lastIndexOf('.')
      var tp = file.name.substring(index + 1, file.name.length)
      // 构建上传文件的formData
      let fetchForm = new FormData()
      fetchForm.append('identifier', fileMD5)
      fetchForm.append('chunkNumber', chunkInfo.currentChunk + 1)
      fetchForm.append('chunkSize', chunkSize)
      fetchForm.append('currentChunkSize', chunkInfo.chunk.size)
      const chunkfile = new File([chunkInfo.chunk], file.name)
      fetchForm.append('file', chunkfile)
      // fetchForm.append('file', chunkInfo.chunk)
      fetchForm.append('filename', file.name)
      fetchForm.append('relativePath', file.name)
      fetchForm.append('totalChunks', chunkInfo.chunkCount)
      fetchForm.append('totalSize', file.size)
      fetchForm.append('objectType', tp)
      // 执行分片上传
      let config = {
        headers: {
          'Content-Type': 'application/json',
          'Accept': '*/*'
        }
      }
 
      UpApi.uploadChunk(fetchForm, config).then(res => {
 
        if (res.code == 200) {
          console.log('分片上传成功')
          uploaded.push(chunkInfo.currentChunk + 1)
          // 判断是否全部上传完
          if (uploaded.length == chunkInfo.chunkCount) {
            console.log('全部完成')
            success(res)
            process(100)
          } else {
            const {
              chunk
            } = getChunkInfo(file, chunkInfo.currentChunk + 1, chunkSize)
            uploadChunk({
              chunk,
              currentChunk: chunkInfo.currentChunk + 1,
              chunkCount
            })
          }
 
        } else {
          console.log(res.msg)
        }
 
      }).catch((e) => {
        error && error(e)
      })
      // if (chunkInfo.currentChunk < chunkInfo.chunkCount) {
      //   setTimeout(() => {
      //
      //   }, 1000)
      // }
    }
  }
  /***
   * 顺序上传
   **/
  const sequentialUplode = (currentChunk) => {
    const {
      chunk
    } = getChunkInfo(file, currentChunk, chunkSize)
    let chunkInfo = {
      chunk,
      currentChunk,
      chunkCount
    }
    var sd = parseInt((chunkInfo.currentChunk / chunkInfo.chunkCount) * 100)
    process(sd)
    console.log('当前上传分片:' + currentChunk)
    let inde = chunkInfo.currentChunk + 1
    if (uploaded.indexOf(inde + '') > -1) {
      console.log('分片【' + currentChunk + '】已上传')
      sequentialUplode(currentChunk + 1)
    } else {
      let uploadData = createUploadData(chunkInfo)
      let config = {
        headers: {
          'Content-Type': 'application/json',
          'Accept': '*/*'
        }
      }
      // 执行分片上传
      UpApi.uploadChunk(uploadData, config).then(res => {
        if (res.code == 200) {
          console.log('分片【' + currentChunk + '】上传成功')
          uploaded.push(chunkInfo.currentChunk + 1)
          // 判断是否全部上传完
          if (uploaded.length == chunkInfo.chunkCount) {
            console.log('全部完成')
            success(res)
            process(100)
          } else {
            sequentialUplode(currentChunk + 1)
          }
 
        } else {
          console.log(res.msg)
        }
 
      }).catch((e) => {
        error && error(e)
      })
    }
  }
  /***
   * 并发上传
   **/
  const concurrentUpload = () => {
    for (var i = 0; i < chunkCount; i++) {
      chunkList.push(Number(i))
    }
    console.log('需要上传的分片列表:' + chunkList)
    concurrentExecution(chunkList, concurrent, (curItem) => {
      return new Promise((resolve, reject) => {
        const {
          chunk
        } = getChunkInfo(file, curItem, chunkSize)
        let chunkInfo = {
          chunk,
          currentChunk: curItem,
          chunkCount
        }
        var sd = parseInt((chunkInfo.currentChunk / chunkInfo.chunkCount) * 100)
        process(sd)
        console.log('当前上传分片:' + curItem)
        let inde = chunkInfo.currentChunk + 1
        if (uploaded.indexOf(inde + '') == -1) {
          // 构建上传文件的formData
          let uploadData = createUploadData(chunkInfo)
          // 请求头
          let config = {
            headers: {
              'Content-Type': 'application/json',
              'Accept': '*/*'
            }
          }
          UpApi.uploadChunk(uploadData, config).then(res => {
            if (res.code == 200) {
              uploaded.push(chunkInfo.currentChunk + 1)
              console.log('已经上传完成的分片:' + uploaded)
              // 判断是否全部上传完
              if (uploaded.length == chunkInfo.chunkCount) {
                success(res)
                process(100)
              }
              resolve()
            } else {
              reject(res)
              console.log(res.msg)
            }
 
          }).catch((e) => {
            reject(res)
            error && error(e)
          })
        } else {
          console.log('分片【' + chunkInfo.currentChunk + '】已上传')
          resolve()
        }
      })
    }).then(res => {
      console.log('finish', res)
    })
  }
  /***
   * 创建文件上传参数
   **/
  const createUploadData = (chunkInfo) => {
    let fetchForm = new FormData()
    fetchForm.append('identifier', fileMD5)
    fetchForm.append('chunkNumber', chunkInfo.currentChunk + 1)
    fetchForm.append('chunkSize', chunkSize)
    fetchForm.append('currentChunkSize', chunkInfo.chunk.size)
    const chunkfile = new File([chunkInfo.chunk], file.name)
    fetchForm.append('file', chunkfile)
    // fetchForm.append('file', chunkInfo.chunk)
    fetchForm.append('filename', file.name)
    fetchForm.append('relativePath', file.name)
    fetchForm.append('totalChunks', chunkInfo.chunkCount)
    fetchForm.append('totalSize', file.size)
    fetchForm.append('objectType', fileType)
    return fetchForm
  }
  readFileMD5() // 开始执行代码
}

并发控制:

/**
 * 并发执行
 * @params list {Array} - 要迭代的数组
 * @params limit {Number} - 并发数量控制数,最好小于3
 * @params asyncHandle {Function} - 对`list`的每一个项的处理函数,参数为当前处理项,必须 return 一个Promise来确定是否继续进行迭代
 * @return {Promise} - 返回一个 Promise 值来确认所有数据是否迭代完成
 */
export function concurrentExecution(list, limit, asyncHandle) {
  // 递归执行
  let recursion = (arr) => {
    // 执行方法 arr.shift() 取出并移除第一个数据
    return asyncHandle(arr.shift()).then(() => {
      // 数组还未迭代完,递归继续进行迭代
      if (arr.length !== 0) {
        return recursion(arr)
      } else {
        return 'finish'
      }
    })
  }
  // 创建新的并发数组
  let listCopy = [].concat(list)
  // 正在进行的所有并发异步操作
  let asyncList = []
  limit = limit > listCopy.length ? listCopy.length : limit
  console.log(limit)
  while (limit--) {
    asyncList.push(recursion(listCopy))
  }
  // 所有并发异步操作都完成后,本次并发控制迭代完成
  return Promise.all(asyncList)
}

到此这篇关于vue 大文件分片上传(断点续传、并发上传、秒传)的文章就介绍到这了,更多相关vue 大文件分片上传内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 封装一下vue中的axios示例代码详解

    封装一下vue中的axios示例代码详解

    这篇文章主要介绍了封装一下vue中的axios,本文通过示例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-02-02
  • vue结合leaflet实现鹰眼图

    vue结合leaflet实现鹰眼图

    本文主要介绍了vue结合leaflet实现鹰眼图,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-06-06
  • vue中的搜索关键字实例讲解

    vue中的搜索关键字实例讲解

    这篇文章主要介绍了vue中的搜索关键字实例讲解,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-04-04
  • vue 虚拟DOM的原理

    vue 虚拟DOM的原理

    这篇文章主要介绍了vue 虚拟DOM的原理,帮助大家更好的理解和学习vue,感兴趣的朋友可以了解下
    2020-10-10
  • Vue动态修改网页标题的方法及遇到问题

    Vue动态修改网页标题的方法及遇到问题

    Vue下有很多的方式去修改网页标题,这里总结下解决此问题的几种方案:,需要的朋友可以参考下
    2019-06-06
  • el-menu递归实现多级菜单组件的示例

    el-menu递归实现多级菜单组件的示例

    本文主要介绍了el-menu使用递归组件实现多级菜单组件,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-04-04
  • 详解关于element el-button使用$attrs的一个注意要点

    详解关于element el-button使用$attrs的一个注意要点

    这篇文章主要介绍了详解关于element el-button使用$attrs的一个注意要点,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-11-11
  • vue初尝试--项目结构(推荐)

    vue初尝试--项目结构(推荐)

    这篇文章主要介绍了vue初尝试--项目结构的相关知识,需要的朋友可以参考下
    2018-01-01
  • Vue实现摇一摇功能(兼容ios13.3以上)

    Vue实现摇一摇功能(兼容ios13.3以上)

    这篇文章主要为大家详细介绍了Vue实现摇一摇功能,兼容ios13.3以上,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-01-01
  • vue 查看dist文件里的结构(多种方式)

    vue 查看dist文件里的结构(多种方式)

    本文通过三种方式给大家介绍了vue 查看dist文件里的结构,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-01-01

最新评论