Vue3+Ts的大文件分片上传,秒传,断点续传

Vue3+Ts的大文件分片上传,秒传,断点续传
1.为什么要使用分片上传
分片上传(Chunked Upload)是一种将大文件分割成多个较小的部分进行上传的技术。这种技术有几个优点:

稳定性: 上传大文件时,如果整个文件一次性上传,可能会因为网络不稳定或其他原因导致上传中断。使用分片上传可以降低上传失败的风险,因为只需要重新上传失败的部分,而不是整个文件。

进度显示: 对于大文件的一次性上传,用户可能会感觉上传过程很慢,因为他们无法看到上传的进度。而使用分片上传,可以在每个分片上传完成后更新上传进度,让用户更清楚地了解上传的进度。

并行处理: 分片上传可以让客户端同时上传多个分片,从而提高上传效率。这对于大文件上传来说尤为重要,因为这样可以充分利用网络带宽,加快上传速度。

断点续传: 如果上传过程中断,用户可以从已经上传完成的分片继续上传,而不需要重新上传整个文件。这样可以节省时间和带宽,并提升用户体验。

总的来说,分片上传可以提高大文件上传的稳定性、效率和用户体验,因此在需要上传大文件的应用场景中,使用分片上传是一种常见的做法。

下面我们来开始开发此项功能:实现大文件分片上传,秒传,断点续传,进度管控

1.技术栈
所用技术为Vue3+TSUI框架使用arco-design,接口请求使用fetch
2.构建页面:构建上传组件,以及进度条控制
<template>
  <div>
    <a-upload :auto-upload="false" ref="uploadRef" @change="fileChange" multiple draggable>
    </a-upload>
        <a-progress
        :percent="percentage"
        :style="{ width: '50%' }"
        :color="{
          '0%': 'rgb(var(--primary-6))',
          '100%': 'rgb(var(--success-6))',
        }"
      />
    <a-button type="primary" @click="submit"> start upload</a-button>
  </div>
</template>
3.获取文件进行分片,并获取文件唯一的hash值
import { ref } from "vue";
import SparkMD5 from "spark-md5";
import { Message } from "@arco-design/web-vue";

const uploadRef = ref()
const percentage = ref()
let chunkSize = 1024 * 512
// 存放所有分片内容
let chunkRes = ref()
let fileName = ref('')
let fileHash = ref('')

const fileChange = async (value: any) => {
  // 获取文件
  const files = value[0].file
  if (!files) {
    return
  }
  fileName.value = files.name
  chunkRes.value = createFileChunk(files) //分片
  const res = await getUniqueHash(chunkRes.value) //生成hash值
  fileHash.value = res as string
}

// 执行文件分片操作
const createFileChunk = (file: File) => {
  const chunkList = [];
  let cur = 0;
  while (cur < file.size) {
    chunkList.push(file.slice(cur, cur + chunkSize));
    cur += chunkSize;
  }
  return chunkList;
};

// 使用hash识别文件,实现秒传的功能
const getUniqueHash = (fileChunks: any) => {
  return new Promise(resolve => {
    const spark = new SparkMD5.ArrayBuffer()
    const fileReader = new FileReader()
    const target: Blob[] = []
    fileChunks.forEach((item: Blob, index: number) => {
      if (index === 0 || index === fileChunks.length - 1) {
        target.push(item)
      } else {
        // 当前切片的前面2字节
        target.push(item.slice(0, 2))
        // 中间的两个字节
        target.push(item.slice(chunkSize / 2, chunkSize / 2 + 2))
        // 后面的两个字节
        target.push(item.slice(chunkSize - 2, chunkSize))
      }
    })
    fileReader.readAsArrayBuffer(new Blob(target)) //将target中的Blob对象转化为ArrayBuffer
    fileReader.onload = (e: any) => { //onload方法是异步的
      spark.append(e.target?.result) //SparkMD5 库的 append 方法将读取的 ArrayBuffer 添加到 MD5 计算中
      resolve(spark.end())
    }
  })
}

4.上传分片,并识别当前的分片是否已经上传实现断点续传

// 上传分片 chunkRes是所有分片 data.existsChunks是已经存在的分片  
const uploadChunk = async (chunkRes: any, existsChunks:string[]) => {
  // 处理数据成我们需要的格式
  const data = chunkRes.map((item: Blob, index: number) => {
    return {
      fileHash: fileHash.value,
      chunkHash: `${fileHash.value}-${index}`,
      chunk: item,
      size: item.size,
    }
  })
  // 添加为formData对象
  const formDatas = data
  .filter((item:any) => !existsChunks.includes(item.chunkHash))
  .map((item: any) => {
    const formData = new FormData()
    formData.append('chunk', item.chunk)
    formData.append('chunkHash', item.chunkHash)
    formData.append('fileName', fileName.value)
    formData.append('fileHash', fileHash.value)
    return formData
  })
  // 控制它的最大请求书
  let max = 1
  let index = 0 //文件是否上传完毕控制
  const taskPool: any = [] //文件控制池
  while (index < formDatas.length) {
    const task = fetch('http://localhost:3000/upload', {
      method: 'POST',
      body: formDatas[index]
    })
    // 当任务执行完成之后,删除taskPool内已完成的数据
    task.then(() => {
      taskPool.splice(taskPool.findIndex((item: any) => item === task))
    })
    taskPool.push(task)
    // 当数组中的数据为6的时候,循环稍等,等其中一个完成再继续
    if (taskPool.length === max) {
      await Promise.race(taskPool)
    }
    console.log(index,'indx')
    console.log(formDatas.length,'formDatas.length')
    console.log(percentage.value)
    index++
    percentage.value = (index / formDatas.length).toFixed(2)
  }
  // 保险起见,将其中的任务再执行一遍
  await Promise.all(taskPool)
  // 所有的文件都上传完毕了,实现合并请求
  mergeFile()
}

5.提交请求,实现秒传以及文件合并


const mergeFile = async () => {
  const res = await fetch('http://localhost:3000/merge', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      fileName: fileName.value,
      fileHash: fileHash.value,
      size: chunkSize
    })
  })
  if (res.status === 200) {
    percentage.value = 1
    Message.success({
      content: '文件上传成功'
    })
  }
}

// 秒传
const secondPass = async () => {
  const res = await fetch('http://127.0.0.1:3000/verify', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      fileName: fileName.value,
      fileHash: fileHash.value
    })
  })
  const data = await res.json()
  return data
}


const submit = async () => {
  const { data } = await secondPass()
  if (!data.shouldUpload) {
     Message.success({
      content: '文件秒传成功'
    })
    return
  }
  uploadChunk(chunkRes.value,data.existsChunks)
}

  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值