首页 > 其他分享 >vue实现大文件切片上传、断点续传、并发数控制等

vue实现大文件切片上传、断点续传、并发数控制等

时间:2023-09-25 11:13:32浏览次数:35  
标签:断点续传 vue hash file new chunks const 上传

 

一、上传按钮和进度条等
<div>
  <h2>上传文件</h2>
  <div ref="drag" class="drag">
    <input class="file" type="file" @change="handlerChange" />
  </div>
  <el-progress style="width: 500px;" :percentage="progress"></el-progress>
  <div style="margin-top: 16px;">
    <el-button type="primary" @click="upload">上传</el-button>
  </div>
  <div>
    <p>hash进度条</p>
    <el-progress style="width: 500px;" :percentage="hashProgress"></el-progress>
  </div>
  <div>
    <p>网格进度条</p>
    <ul class="grid" :style="{'width': gridWidth + 'px'}">
      <li class="grid-block" v-for="chunk in chunks" :key="chunk.name">
        <div 
          :class="{ 'uploading': chunk.progress > 0 && chunk.progress < 100, 'success': chunk.progress == 100, 'error': chunk.progress < 0}"
          :style="{height: chunk.progress + '%'}"
        >
          <i class="el-icon-loading" style="color: #f56c6c" v-if="chunk.progress < 100 && chunk.progress > 0"></i>
        </div>
      </li>
    </ul>
  </div>
</div>
二、选择文件
//点击按钮上传
handlerChange (e) {
  const [file] = e.target.files
  if (!file) return
  this.fileData = file
}
//拖拽上传
dragRelevant () {
  const dragDom = this.$refs.drag
  //进入区域
  dragDom.addEventListener('dragover', e => {
    dragDom.style.borderColor = '#f00'
    e.preventDefault()
  })
  //离开区域
  dragDom.addEventListener('dragleave', e => {
    dragDom.style.borderColor = '#41B883'
    e.preventDefault()
  })
  //放下文件
  dragDom.addEventListener('drop', e => {
    dragDom.style.borderColor = '#41B883'
    const [file] = e.dataTransfer.files
    if (!file) return
    this.fileData = file
    e.preventDefault()
  })
}
三、利用文件内容计算hash

为了防止文件上传重复,我们可以使用将每个文件都用hash作为文件名来上传,这里用的是spark-md5来计算hash值。
首先定一个分块的大小

const CHUNK_SIZE = 1 * 1024 * 1024 //每次分片大小

因为大文件用整个内容来计算hash肯定是很慢的,我们不能阻塞页面执行其他任务,所以我通过下面三种方式来计算:

  • 使用WebWorker来计算
//使用webWorker来计算文件的md5值
calculateHashByWebWorker (chunks) {
  this.hashProgress = 0 //hash进度条
  return new Promise(resolve => {
    const worker = new Worker('/hash.js')
    worker.postMessage(chunks)
    worker.onmessage = e => {
      const { hash, progress } = e.data
      this.hashProgress = progress
      if (hash) {
        resolve(hash)
      }
    }
  })
}
  • 使用requestIdleCallbck来计算
//使用requestIdleCallbck来计算文件的md5值  这个方法会在浏览器空闲时调用
calculateHashByRequestIdleCallback (chunks) {
  return new Promise(resolve => {
    const spark = new Spark.ArrayBuffer()
    let count = 0
    const appendToSpark = file => {
      return new Promise(resolve => {
        const reader = new FileReader()
        reader.readAsArrayBuffer(file)
        reader.onload = data => {
          spark.append(data.target.result)
          resolve()
        }
      })
    }
    const workLoop = async deadLine => {
      while (count < chunks.length && deadLine.timeRemaining() > 1) {
        await appendToSpark(chunks[count].file)
        count++
        if (count < chunks.length) {
          this.hashProgress = (count * 100 / chunks.length).toFixed(2) - 0
        } else {
          this.hashProgress = 100
          resolve(spark.end())
        }
      }
      window.requestIdleCallback(workLoop)
    }
    window.requestIdleCallback(workLoop)
  })
}
    • 实现抽样hash,降低精度,提高效率
      大文件每次都全量计算md5的话,效率很低,如果我们每次取每个分片的一部分用来计算,这样会大大提高计算的效率
//抽样hash 取前两个和后一个  中间每兆取前中后三个点
calulateSamplingHash (chunks) {
  return new Promise(resolve => {
    const spark = new Spark.ArrayBuffer()
    const head = chunks.slice(0, 2)
    const tail = chunks[chunks.length - 1]
    const middle = chunks.slice(2, chunks.length - 1)
    const files = []
    files.push(head[0].file, head[1].file)
    middle.forEach(item => {
      const head = item.file.slice(0, 1)
      const tail = item.file.slice(-1, item.file.length)
      const center = Math.floor(item.file.length - 1) / 2
      const middle = item.file.slice(center, center + 1)
      files.push(head, tail, middle)
    })
    files.push(tail.file)
    //追加计算hash
    const reader = new FileReader()
    reader.readAsArrayBuffer(new Blob(files))
    reader.onload = data => {
      spark.append(data.target.result)
      this.hashProgress = 100
      resolve(spark.end())
    }
  })
}
四、上传

将上传的诸多分片都放在对应hash值得目录下面,每次上传前检查下是否有这个文件了
如果有就提示秒传成功
如果没有就读取下这个目录,将这个目录下面的所有文件名都返回给前端

    • 检查文件是否已上传
//检查文件是否已上传
const fileExt = this.fileData.name.split('.').pop()
// uploaded:文件是否已上传,uploadedList:上传的分片列表
const { data: { uploaded, uploadedList } } = await this.$axios.get('/checkFile', {
  params: {
    hash,
    ext: fileExt
  }
})
if (uploaded) {
  this.$message.success('秒传成功')
  return
}
//断点续传  根据之前上传的文件
this.chunks = chunks.map((chunk, index) => {
  const fileName = `${hash}-${index}`
  return {
    file: new File([chunk.file], fileName + '.' + fileExt, { type: 'image/mp4' }),
    name: fileName,
    hash,
    progress: uploadedList.includes(fileName) ? 100 : 0 //如果当前分片已经上传,进度直接设置为100
  }
})
  • 上传请求(断点续传)
//上传请求
async uploadRequest (hash) {
  //如果已经上传过了 就不用上传了  用filter过滤掉(断点续传)
  const requests = this.chunks.map((chunk, index) => {
    if (chunk.progress === 100) {
      return null
    } else {
      const form = new FormData()
      form.append('chunk', chunk.file)
      form.append('hash', chunk.hash)
      form.append('name', chunk.name)
      return { form, index, error: 0 }
    }
  }).filter(val => val)
  //实现并发数控制
  await this.sendRequest(requests)
  //合并上传的分片
  this.mergeFile(hash)
}
  • 并发数控制+错误重试
//请求并发数控制
sendRequest (requests, limit = 3) {
  return new Promise((resolve, reject) => {
    const len = requests.length
    let counter = 0
    let isStop = false //如果一个片段失败超过三次 认为当前网洛有问题 停止全部上传
    const startRequest = async () => {
      if (isStop) return
      const task = requests.shift()
      if (task) {
        //利用try...catch捕获错误
        try {
          //具体的接口  抽离出去了
          await this.launchRequest(task)
          if (counter === len - 1) { //最后一个任务
            resolve()
          } else { //否则接着执行
            counter++
            startRequest() //启动下一个任务
          }
        } catch (error) {
          this.$set(this.chunks[task.index], 'progress', -1)
          //接口报错重试,限制为3次
          if (task.error < 3) {
            task.error++
            requests.unshift(task)
            startRequest()
          } else {
            isStop = true
            reject(error)
          }
        }
      }
    }
    //启动任务
    while (limit > 0) {
      //模拟不同大小启动
      setTimeout(() => {
        startRequest()
      }, Math.random() * 2000)
      limit--
    }
  })
}

 

参考文章:http://blog.ncmem.com/wordpress/2023/09/25/vue%e5%ae%9e%e7%8e%b0%e5%a4%a7%e6%96%87%e4%bb%b6%e5%88%87%e7%89%87%e4%b8%8a%e4%bc%a0%e3%80%81%e6%96%ad%e7%82%b9%e7%bb%ad%e4%bc%a0%e3%80%81%e5%b9%b6%e5%8f%91%e6%95%b0%e6%8e%a7%e5%88%b6%e7%ad%89/

欢迎入群一起讨论

 

 

标签:断点续传,vue,hash,file,new,chunks,const,上传
From: https://www.cnblogs.com/songsu/p/17727480.html

相关文章

  • vue2兼容ie10
    1.找到ie浏览器。可以下载,一般windows自带2.启动项目,可能会出现再ie中输入本地地址后直接跳转到MicrosoftEdge的情况,需要配置后正常使用ie10配置过程:控制面板--网络和Internet--Internet选项--更改主页--高级--勾选跟InternetExplorer相关的某项。3.解决:babel分为预设和单......
  • 文件上传 切片与断点续传
    主要讲前端,后端使用node。会写一下后端的处理1.单个文件上传请求头为multipart/form-data数据为from-data格式letformData=newFormData();formData.append('file',_file);formData.append('filename',_file.name);然后使用post直接给后端。后端直接存......
  • Vue-进阶:路由及elementUI组合开发
    Vue-router路由什么是vue-router?服务端路由指的是服务器根据用户访问的URL路径返回不同的响应结果。当我们在一个传统的服务端渲染的web应用中点击一个链接时,浏览器会从服务端获得全新的HTML,然后重新加载整个页面。然而,在单页面应用中,客户端的JavaScript可以拦截页面的......
  • 竟然可以在一个项目中混用 Vue 和 React?
    React和Vue是前端开发中的两大热门框架,各自都有着强大的功能和丰富的生态系统。然而,你有没有想过,在一个项目中同时使用React和Vue?是的,你没有听错,可以在同一个项目中混用这两个框架!本文就来分享3个用于混合使用React和Vue的工具!#VeauryVeaury是一个基于React和Vue3的工......
  • Vue3 事件处理实战:让你的代码更有魅力
    监听事件我们可以使用 v-on 指令(通常缩写为 @ 符号)来监听DOM事件,并在触发事件时执行一些JavaScript。用法为 v-on:click="methodName" 或使用快捷方式 @click="methodName"例如:<divid="basic-event"><button@click="counter+=1">Add1</button>&......
  • Tomcat--文件上传--文件包含--(CVE-2017-12615)&&(CVE-2020-1938)
    Tomcat--文件上传--文件包含--(CVE-2017-12615)&&(CVE-2020-1938)复现环境采用Vulfocus靶场环境进行复现,搭建操作和文章参考具体搭建教程参考vulfocus不能同步的解决方法/vulfocus同步失败。CVE-2017-12615文件上传漏洞简介当存在漏洞的Tomcat运行在Windows/Linux主机上,且......
  • 大文件切片上传+断点续传解决方案-前后端实现
    上传文件大家应该都做过,前端直接把file文件传给后端就ok了,但是大文件这样传就会造成页面假死,体验极差。如果遇到网络不稳定的时候,中途上传失败的话,又要从头开始传,本来文件就大,还慢。所以今天我们用一种新方法-切片上传+断点续传前端实现:页面上很简单,我就放了进度条和一个上传文件......
  • vue笔记
    一、vue项目vscode自动import,VUE组件和ts模块1、在vscode插件市场安装vetur插件2、修改tsconfig.json文件将moduleResolution改为node3、重启vscode,后面再下代码就可以自动import了。二、vue项目中使用elementplus参考elementplus官网:https://element-plus.org/zh-CN/guide/desig......
  • PbootCMS附件上传报错UNKNOW: Code: 8192; Desc: stripos()
    PbootCMS附件上传报错UNKNOW:Code:8192;Desc:stripos(),具体显示如下图所示:解决方法:打开/core/function/file.php,找到以下代码:1if (stripos($types,$ext)!== false)改为如下代码:1if (stripos($types,chr($ext))!== false)......
  • springBoot上传文件时MultipartFile报空问题解决方法
    1.问题描述:之前用springMVC,转成springboot之后发现上传不能用。网上参考说是springboot已经有CommonsMultipartResolver了,但是我的上传后台接收的还是null。2.解决方法加入配置类importorg.springframework.context.annotation.Bean;importorg.springframework.context......