资讯专栏INFORMATION COLUMN

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

3403771864 / 615人阅读

  上传文件中出现内存比较大的情况要如何处理?其实不论是用户端还是服务端,假如采用一次性进行读取发送、接收都是不可取,很容易导致内存问题。这样我们可以考虑采用切块分段上传,从上传的效率来看,利用多线程并发上传能够达到最大效率。

  本篇文章主要为大家讲述就是基于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大文件分片上传(断点续传、并发上传、秒传)嘛!希望大家可以好好看看。


文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/130355.html

相关文章

  • HTML5文件上传组件的深度剖析

    摘要:前段时间在技术交流会中分享了基于技术的文件上传组件,由于携带的信息非常有限,故在此整理成文章分享出来,供感兴趣的同学阅读。断点续传有了分块上传,其实我们可以实现更多的功能。 前段时间在w3ctech技术交流会中分享了基于 HTML5 技术的文件上传组件,由于ppt携带的信息非常有限,故在此整理成文章分享出来,供感兴趣的同学阅读。 HTML VS FLASH 对于文件上传,相信还有不...

    xiangzhihong 评论0 收藏0
  • Spring Boot 2.x(十六):玩转vue文件上传

    摘要:为什么使用最近用到了来完成文件上传的操作,踩了一些坑,对比了一些的组件,发现了一个很好用的组件再说说为什么选用这个组件,对比和的上传组件,它能做到更多的事情,比如可暂停继续上传上传队列管理,支持最大并发上传分块上传支持进度预估 为什么使用Vue-Simple-Uploader 最近用到了Vue + Spring Boot来完成文件上传的操作,踩了一些坑,对比了一些Vue的组件,发现了一...

    JessYanCoding 评论0 收藏0
  • Node+H5实现文件分片上传(有源码)

    摘要:话前上传大文件上传的教程网上很多但是大部分没给出一个比较完整的出来这个博客给出的是前后端一套完整的解决方案其中前端没有使用第三方上传库希望能帮到有同样需求的朋友们大文件分片上传的好处在这里就不用多说了之前不管是上传单文件还是分片文件上传都是 话前 上传大文件上传的教程网上很多, 但是大部分没给出一个比较完整的出来, 这个博客给出的是前后端一套完整的解决方案, 其中前端没有使用第三方上传...

    abson 评论0 收藏0
  • Node+H5实现文件分片上传(有源码)

    摘要:话前上传大文件上传的教程网上很多但是大部分没给出一个比较完整的出来这个博客给出的是前后端一套完整的解决方案其中前端没有使用第三方上传库希望能帮到有同样需求的朋友们大文件分片上传的好处在这里就不用多说了之前不管是上传单文件还是分片文件上传都是 话前 上传大文件上传的教程网上很多, 但是大部分没给出一个比较完整的出来, 这个博客给出的是前后端一套完整的解决方案, 其中前端没有使用第三方上传...

    1treeS 评论0 收藏0
  • 解读阿里云oss-android/ios-sdk 断点续传(多线程)

    摘要:多线程上传的好处进一步占满网络资源。分片上传部分,采用多线程并发上传机制,目前线程开启数量最多条,根据的核数进行判断,如果核数会采用核数进行配置,分片的个数最多。 摘要: oss sdk 断点续传功能使用及其相关原理 前言移动端现状随着移动端设备的硬件水平的不断提高,如今的cpu,内存等方面都大大的超过了一般的pc电脑,因此在现今的程序中,合理的使用多线程去完成一些事情是非常有必要的。...

    Salamander 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<