大文件上传——分片上传

在HTTP协议下,上传大文件可能会遇到很多问题;
最近,在开发中就遇到了很多挫折;

最开始的需求是对 500M及以上的文件 进行上传;

1 直接上传大文件

直接上传大文件,网络请求时间太长,超过了项目中规定的超时时间 —— 30s
并且文件向服务器上传的过程中,文件流是存在于内存中,很有可能造成 内存溢出
因此该方案 失败

2 分片上传大文件

由于文件size过大,最好的方法就是 分片上传 ,将大文件分割成多个固定大小的块,然后逐个上传;

<input type="file" />

<script>
  cont inp = documnet.querySelector('input');
  inp.onChange = async (e) => {
    cont file = inp.files[0];
    if (!file) {
      return;
    }
    file.slice(0, 100); // 0-99个字节
    console.log(file); 
    // 得到一个blob对象 Blob {size: 100, type: ''}
    // blob对象可以直接利用ajax发送请求;
	const chunks = createChunks(file, 10 * 1024 * 1024);
	const result = await hash(chunks); //  拿到hash计算结果
	console.log(result); // 得到hash值
  }
 
function createChunks(file, chunkSize) {
  const result = [];
  for (let i = 0; i < file.size; i += chunkSize) {
    result.push(file.slice(i, i + chunkSize));
  }

  return result; // Blob对象的集合,Blob对象是可以直接ajax请求发到服务器的
}
</script>

这里可以使用FormData函数来增加文件信息;

const formData = new FormData();
formData.append('file', file, file.name); // 'file' 是后端接收文件数据的字段名
formData.append('fieldName', 'value'); // 添加其他数据

fetch('upload_endpoint', { // 替换为你的上传端点
  method: 'POST',
  body: formData
})
.then(response => response.json()) // 假设后端返回JSON格式的响应
.then(data => {
  console.log(data);
})
.catch(error => {
  console.error('Error:', error);
});
断点上传需要去问服务端,这个文件,我还需要传递哪些分片;
// 如何告诉服务端是哪个文件?
// 文件名不合适,会重复;文件路径不合适;
// 合理的做法是采用hash值

// 增量算法,计算hash值;
// 先用一块数据计算结果,计算出来
function hash(chunks) {
  return new Promise((resolve) => {
    const spark = new SparkMD5();
    function _read(i) {
      if (i >= chunks.length) {
        console.log(spark.end()); // 文件的hash值计算完毕
        resolve(spark.end());
        return; // 读取完成
      }
      const blob = chunks[i];
      const reader = new FileReader(); // 读字节
      reader.onload = e => {
        const bytes = e.target.result; // 读取到的字节数组
        spark.append(bytes);
        _read(i + 1);
      }

      reader.readAsArrayBuffer(blob); 
      // 读ArrayBuffer的字节,读取过程是异步的。所以有onload事件
    }

    _read(0);
  });
}

上传过程中,每个块都会被独立地上传到服务器,并且在服务器端进行合并;如此减少了单个请求的数据量,降低了上传过程中的网络传输负担,同时通过并发请求合理利用了网络带宽资源

(插入一段冷知识 -^-)
谷歌浏览器从 59版本 开始限制并发请求数为 6,超过这个数量,浏览器会等待之前的请求完成后再发起新的请求;

但是在分片上传的时候遇到了 “token过期的问题”;

  1. 项目规定token在10min之后过期;将10min分为两段,前5分钟调用接口,返回一个token;后5min调用接口,返回一个新的token;
  2. 拦截器拦截到新的token,会在下次调用接口的时候,携带该token;
  3. 服务器端会验证token是否合法,来判断请求是否合法;(很明显,无论何时,都会有两个token是合法的)

举例:将大文件分为“50”片;由于分片是在文件添加好之后一次性下发,因此调用上传接口时的request header中的 Token都是相同的;可能会出现 Token 过期的情况;

并且同时下发50个上传请求,会占用全部的网络资源,如果上传不能及时完成,会阻塞其他功能的使用。

3 控制并发请求数

参考之前写的一片文章 前端如何控制并发请求数量
将大文件分成 50片,并且同时只能下发 3个请求,当其中一个请求结束,下一个请求接上;

如此,文件下发时,可以携带最新的Token,解决了 Token过期的问题;并且并发请求3个,解决了阻塞其他请求的问题。

4 断点续传

以上3个步骤已经初步实现大文件上传了;但一旦出现断网,网络不稳定的情况,就是灾难了,需要重新添加文件,重新上传;

为了解决这个问题,采用“断点续传”功能;将50个分片请求分别编号;失败的请求放置在一个“失败数组”中;在50个请求发送完成后,接着重新发送失败的请求;不断重试,直到全部发送完成。

总结一下

实现分片上传,通常要考虑的因素有以下几个方面:

  1. 块大小选择:选择适当的块大小对于分片上传的效率和性能至关重要;如果块太小,会增加上传请求的数量和服务器端的处理开销;如果块太大,可能会导致上传过程中的超时或内存限制问题。一般来说,块大小应该在几百KB到几MB之间;
  2. 块编号和顺序:为了在服务器端正确地合并块,每个块都应该有一个唯一的编号,并按照正确的顺序上传。可以在上传前将块编号和总块数等信息发送到服务器端,以便服务器端正确地组装文件;
  3. 断点续传:分片上传也可以支持断点续传功能,即在上传过程中出现中断或失败时,可以从上次上传的位置继续上传。为了实现断点续传,服务器端需要保存已上传的块信息,并在继续上传时进行验证和处理。

对于分片上传的处理:

  1. 前端负责将文件切分成多个小片段:使用 JavaScript 的 File API,可以将大文件分割成多个小的 Blob 对象或者ArrayBuffer 对象;
  2. 前端上传分片:通过 AJAX 或者 Fetch API 发送每个分片到服务器。可以使用 XMLHttpRequest 或者 fetch 函数来发送异步请求;
  3. 服务器端接收分片:服务器端需要接收每个分片,并将其存储在临时位置或者内存中;
  4. 服务器端验证和合并分片:当所有分片都上传完成后,服务器端需要验证每个分片的完整性和正确顺序。如果所有分片都齐全,可以将它们合并成完整的文件;
  5. 完成上传:一旦文件合并完成,服务器可以将文件保存到永久存储位置,并返回上传成功的响应给前端;
  6. 错误处理和重试:在上传过程中,需要处理可能出现的错误,例如网络中断、分片丢失等情况。可以通过重试机制来处理上传失败的分片。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值