【实践功能记录4】文件下载-大文件切片下载

一、前置知识

1.File对象: 选择文件时,这些文件被存储在File对象中
2.Blob对象: Blob表示二进制数据,常用来表示大型数据对象(图片、音频)。File对象是Blob对象的一个子类,它继承了Blob对象的所有属性和方法
3.formData对象: 前端(二进制)先将文件存储在formData对象中才能传给后端

const formData = new FormData();
formData.append("file", file);

4.slice方法:
File对象的slice方法是从其父类Blob对象继承来的。用于从文件中提取一段范围的数据.
大文件上传的核心步骤是对文件进行切割成若干个小文件
参数:

  • start: 开始提取数据的字节偏移量,默认0
  • end: 结束提取数据的字节偏移量,默认为文件或Blob对象的总字节数
  • slice方法的返回值是一个新的Blob对象,包含从原始文件或Blob对象中提取的指定字节范围的数据。

5.md5
hash算法可以把任何数据换算成一个固定长度的字符串,这种换算是单向的(数据 -> 字符串)且对数据的变化非常敏感,利用hash算法可以唯一的代表一个文件的整个内容,md5是hash算法中常用的一种。
如何在客户端计算出文件的hash值?
利用spark-md5第三方库
安装:pnpm install spark-md5 --save
引入:import SparkMD5 from ‘spark-md5’

二、问题

  1. 谁负责资源分块?
  2. 谁负责资源整合?
  3. 如何进行资源分块?
  4. 如何确定前端分块已上传完成?
  5. 上传失败如何处理?

三、大文件上传思路

1.选择上传资源,判断是否需要符合大文件范围,是否需要大文件上分块上传
2.进行切片,再分片上传
3.考虑断点续传:
上传文件前,请求接口获取文件切片上传状态,获取已上传的切片索引
4.上传完成后,请求合并,告诉服务端整合资源
需要进行多个请求:
检查分片
分片上传
合并分片

四、实现步骤

文件切片:
  1. 获取File文件对象
  2. 使用File对象的slice方法提取部分数据(例如,前200KB)
const file = fileInput.files[0];
const sliceBlob = file.slice(0, 1024*200, file.type); // 0~200个字节的内容

1024个字节 = 1KB, 截取前200KB的内容

  1. 使用FileReader对象读取slicedBlobFileReader对象有一个onload回调函数
const reader = new FileReader();
reader.onload = function(e){
    imgElement.src = e.target.result;
    imgElement.style.display = "block";
}
reader.readAsDataURL(slicedBlob)

简易流程:

  1. 设置chunkSize表示切片体积
  2. 调用file.slice()方法进行切片
  3. 得到切片后的小文件chunk
  4. 将chunk添加到formData对象中
  5. 将formData对象传给后端
const chunkSize = 100 * 1024; // 切片大小 100KB
const totalChunks = Math.ceil(file.size / chunkSize);// 切片数量
for(let i = 0; i < totalChunks; i++) {
    const start = i * chunkSize;
    // 判断结束字节是否已经大于总体积,获取最小值
    const end = Math.min(start + chunkSize, file.size);
    const chunk = file.silce(start, end);
    const result = await uploadChunk(chunk)
}
async function uploadChunk(chunk){
    const formData = new FormData();
    formData.append('file', chunk)
}

示例:

const inp = document.querySelector('input');
inp.onchange = async (e) => {
    const file = inp.files[0];
    if(!file){
        return;
    }
    const chunks = createChunks(file, 10 *1024 * 1024);
    const result = await getHash(chunks)
}

// 文件分块
function createChunks(file, chunkSize){
   const result = [];
   for (let i = 0; i < file.size; i += chunkSize) {
       result.push(file.slice(i, i + chunkSize));
   }
   return result;
}

// 计算文件hash值, chunks 分块数据
function getHash(chunks) {
  return new Promise((resolve) => {
    const currentChunk = 0;
    const spark = new SparkMD5.ArrayBuffer();
    function _read(chunkIndex: number) {
      if (chunkIndex >= chunks.length) {
        // 读取完成
        resolve(spark.end());
        return;
      }
      const blob = chunks[chunkIndex];
      // 使用FileReader对象读取分块内容
      const fileReader = new FileReader();
      // FileReader对象有一个onload回调函数
      fileReader.onload = function (e) {
        const bytes = e.target?.result as ArrayBuffer; // 读取到的字节数;
        spark.append(bytes);
        chunkIndex++;
        _read(chunkIndex);
      };
      fileReader.readAsArrayBuffer(blob);
    }
    _read(currentChunk);
  });
}

注意: 在计算文件hash时,不推荐一次性计算整个文件的hash值,如果文件过大内容会吃不消,分块计算hash(增量算法)
因为计算的时间较长不回放到主线程中去做,可以利用web worker 单独放到一个线程中去处理,避免浏览器卡死。

分片上传:

上传分片请求了很多次上传接口,要确保再全部分片上传后再去执行请求合并的方法,用到了Promise.all()方法。且在每一次请求分片执行完后再去计算进度,在uploadAip(fd, index).then()中去计算进度,代码如下:
image.png

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值