前端大文件上传

发布于:2024-10-18 ⋅ 阅读:(19) ⋅ 点赞:(0)

在前端中实现大文件上传大致有以下几种方法:

     【 分片上传、 断点续传、Websocket 上传、 通过第三方服务上传 、使用第三方库】

分片上传:将大文件切割成多个小片段,然后分别上传。可以利用HTML5中的File API和Blob对象,通过FileReader读取文件内容,然后使用XMLHttpRequest或fetch API发送每个小片段,并在服务器端将它们合并成完整的文件。

---将一个大文件切割成多个小文件,分别上传,然后在服务端组合。这种方式可以提高上传速度和可靠性,但需要额外的前后端开发和维护工作。

断点续传:将大文件分成多个小片段,每个小片段上传成功后记录其上传进度,若中断或失败后可从上次记录的进度继续上传。可以使用XMLHttpRequest或fetch API发送每个小片段,同时在服务器端保留上传进度信息。

---将文件切片后,每次上传部分数据,可以在上传失败或者中断后继续上传剩余的数据。

这种方式对于大文件的上传非常友好,但需要更为复杂的前后端开发和维护工作

Websocket 上传
使用WebSocket协议进行文件上传。WebSocket提供了全双工通信,可以实时传输数据,适合处理大文件上传。通过WebSocket,可以将文件拆分成多个分片,并逐个发送到服务器端。

通过WebSocket协议实现实时的双向数据传输,适用于大文件或大量数据的实时传输,但需要特殊的服务器支持。

通过第三方服务上传
比如使用云存储服务(如七牛、阿里云OSS等)进行文件上传,即使是大文件也可以快速上传,同时也可以享受到云存储服务的稳定性和安全性。

针对不同的场景和业务需求,可以选择适合自己的上传方案

使用第三方库:有许多开源的第三方库可以简化大文件上传的过程,例如PluploadFineUploaderUppy等。这些库提供了丰富的API和功能,可以处理分片上传、断点续传、上传进度显示等复杂的操作。

注意以下几点:

文件分片大小的选择:过小的分片会增加上传请求的数量,而过大的分片可能会导致上传过程中的内存和网络压力增加;一般分片不要超过5M

上传进度的显示:可以使用XMLHttpRequest的upload事件或fetch API的ProgressEvent来获取上传进度,并将其显示给用户。

错误处理和恢复:如果上传过程中出现错误,需要及时捕获并给出错误提示,同时确保可以从错误处恢复并继续上传。

服务器端处理:在服务器端需要相应的接口来接收和处理分片上传的文件,并在上传完成后将其合并成完整的文件。同时,需要处理断点续传的逻辑,以保证上传进度的准确性。

一般性流程举例:

代码:

//1、获取文件专属MD5码

import sparkMD5 from 'spark-md5' // 安装下spark-md5包

// 定义hash函数,接受一个名为chunks的数组作为参数
hash = (chunks) => {
  // 返回一个Promise,这样调用者可以使用.then()或async/await来处理结果
  return new Promise((resolve) => {
    // 创建一个新的sparkMD5实例,用于计算MD5哈希值
    const spark = new sparkMD5();

    // 定义一个递归函数_read,用于逐个处理chunks数组中的blob
    function _read(i) {
      // 如果索引i大于等于chunks数组的长度,说明所有blob都已经处理完毕
      if (i >= chunks.length) {
        // 调用sparkMD5实例的end方法,并传入resolve回调,以返回最终的哈希值
        resolve(spark.end());
        return; // 读取完成,退出函数
      }

      // 获取当前索引i对应的blob
      const blob = chunks[i];

      // 创建一个新的FileReader实例,用于读取blob的内容
      const reader = new FileReader();

      // 设置FileReader的onload事件处理函数,当blob内容读取完成后调用
      reader.onload = e => {
        // 从事件对象e中获取读取到的字节数组
        const bytes = e.target.result;

        // 将字节数组添加到sparkMD5实例中,用于计算哈希值
        spark.append(bytes);

        // 递归调用_read函数,处理下一个blob
        _read(i + 1);
      };

      // 以ArrayBuffer格式异步读取当前blob的内容
      reader.readAsArrayBuffer(blob); // 这里是读取操作开始的地方
    }

    // 从索引0开始,调用_read函数,处理chunks数组中的第一个blob
    _read(0);
  });
};

// 2、接口1:传MD5码获取该文件已上传的片数

const md5Str = await this.hash(chunksList);
/* 这里写发送md5Str【接口1】 的方法,获取已上传片数 */

//3、接口2:将文件分片成promise请求数组
// 3.1将文件分片
  chunkFile = (file, chunksize) => {  
    const chunks = Math.ceil(file.size / chunksize);  
    const chunksList = [];  
    let currentChunk = 0;  
    while (currentChunk < chunks) {  
        const start = currentChunk * chunksize;  
        const end = Math.min(file.size, start + chunksize);  
        const chunk = file.slice(start, end);  
        chunksList.push(chunk);  
        currentChunk++;  
    }  
    return chunksList;  
};
// 3.2 封装成promise请求
  chunkPromise = data => { //每个分片的请求
	return new Promise((resolve, reject) => { 
		/* 这里写上传的 【接口2】,传data过去*/
	}).catch(err => {
		message.error(err.msg);
		reject();
	})
}
	 const promiseList = []
	 const chunkList = chunkFile(file, size)
	 chunkList.map(item => {
	 const formData = new FormData();
	 formData.append('file', item)
	 // limit是线程池组件 后面会写
	 promiseList.push(limit(() => this.chunkPromise(formData, chunks)))
})
// promise请求数组做线程池限制处理 p-limit
// p-limit npm 包是一个实用工具,它允许你限制同时运行的 Promise 数量。当你拥有可以在并行中运行的// 操作,但由于资源限制想要限制这些操作的数量时,这个工具就很有用,有助于控制并发性。

// p-limit npm地址官方案例:
const pLimit = require('p-limit');
const limit = pLimit(2);
// Only two promises will run at once, the rest will be queued
const input = [
  limit(() => fetchSomething('foo')),
  limit(() => fetchSomething('bar')),
  limit(() => doSomethingElse()),
];

// Only two promises will run at once, the rest will be queued
Promise.all(input).then(results => {
  console.log(results);
});



//4、接口3:promise.all执行结束后,请求一个是否合并成功的接口
	Promise.all(promiseList).then(res =>{
		// 这里写接口3 问后端是否合并成功了
	}).catch(err => {
		message.error(err.msg);
	})

以上代码合成完整的主函数:

const md5Str = await this.hash(chunksList);
/* 这里写发送md5Str【接口1】 的方法,获取已上传片数 */
const promiseList = []
const chunkList = this.chunkFile(file, size)
chunkList.map(item => {
	const formData = new FormData();
	formData.append('file', item)
	promiseList.push(limit(() => this.chunkPromise(formData, chunks)))
 // 【接口2 在this.chunkPromise里】
}
Promise.all(promiseList).then(res =>{
		// 这里写【接口3】 问后端是否合并成功了
	}).catch(err => {
		message.error(err.msg);
	})

断点续传  详细阐述
 

在Vue中实现断点续传,需要结合后端的支持。以下是一个基本的思路

1、切割文件:将要上传的文件切割成多个小文件片段。可以使用JavaScript的File API中的slice方法来实现。

2、上传文件片段:使用XMLHttpRequest或者Fetch API发送每个文件片段到后端,并携带相关信息,如文件名、文件ID、当前片段序号等。

3、后端接收并保存文件片段:后端接收到每个文件片段后,将其保存在临时位置,并记录文件片段的序号和文件ID等信息。

4、续传处理:如果上传过程中断,下次继续上传时,通过查询后端已保存的文件片段信息,得知已经成功上传的文件片段,从断点处继续上传剩余的文件片段。

5、合并文件:当所有文件片段都上传完成后,后端根据文件ID将所有片段合并成完整的文件。

在Vue中,你可以使用相关的库来简化文件上传和切割的过程,比如vue-upload-componentaxios等,具体使用方式可以参考它们的文档。

断点续传需要后端的支持来保存文件片段和处理合并操作,所以确保后端也做好了对应的处理。

另外,断点续传需要考虑到网络中断、用户关闭页面等各种异常情况,需要适当处理这些异常来保证上传的稳定性和可靠性。

分段上传 与 断点续传  比较:
分段上传和断点续传都是为了优化大文件上传的过程,提高上传的效率和可靠性。它们的实现方式不同,适用情况也略有差异。下面是两者的区别:

分段上传:将一个大文件切割成多个小部分进行上传,每个小部分的大小可以根据具体需求来确定。这种方法可以减少单个请求的数据量,提高上传效率,并且可以在上传失败后只重新上传失败的部分,而不需要重新上传整个文件。常见的例子包括百度网盘、腾讯微云等。

断点续传:当文件上传过程中出现网络断开或其他异常情况时,可以通过记录已上传的部分,下次从上次上传的位置继续上传。这种方法可以保证上传的连续性,避免上传失败后需要重新上传整个文件,同时也可以提高上传效率。常见的例子包括云存储、视频网站等。

尽管它们的实现方式不同,但是它们有一些相似之处:

都需要使用特殊的上传方式或协议来支持分段上传或断点续传。例如,HTTP协议默认并不支持断点续传,需要在服务器端做特殊处理。

都需要在前端和后端做相应的处理来支持。在前端,需要将大文件切割成多个小部分并发送到后端;在后端,需要接收和保存上传的文件片段,并在最后将它们合并成一个完整的文件。

分段上传和断点续传都是为了解决大文件上传的问题,提高上传效率和可靠性。不同的场景和需求可能需要选择不同的方法来实现。

分片上传代码示例

// HTML部分
<input type="file" id="fileInput" />
<button onclick="upload()">上传</button>
// JavaScript部分
function upload() {
  const fileInput = document.getElementById('fileInput');
  const file = fileInput.files[0];
  
  const chunkSize = 1024 * 1024; // 每个分片的大小,这里设置为1MB,建议最大不要超过5MB
  const totalChunks = Math.ceil(file.size / chunkSize); // 总分片数
  let currentChunk = 0; // 当前上传的分片
  
  // 读取并上传分片
  function uploadChunk(start, end) {
    const chunk = file.slice(start, end);
  
    const formData = new FormData();
    formData.append('file', chunk);
    formData.append('chunk', currentChunk);
    formData.append('chunks', totalChunks);
  
    // 发送POST请求上传分片
    fetch('/upload', {
      method: 'POST',
      body: formData
    })
    .then(response => response.json())
    .then(data => {
      if (data.success) {
        // 继续上传下一个分片
        currentChunk++;
        if (currentChunk < totalChunks) {
          const start = currentChunk * chunkSize;
          const end = start + chunkSize;
          uploadChunk(start, end);
        } else {
          console.log('文件上传完成');
        }
      } else {
        console.log('上传失败');
      }
    })
    .catch(error => {
      console.error('上传出错', error);
    });
  }
  
  // 开始上传第一个分片
  const start = currentChunk * chunkSize;
  const end = start + chunkSize;
  uploadChunk(start, end);
}

思路:我们首先获取文件对象,并设置每个分片的大小(这里可以设置为1MB-5MB)。然后使用slice方法将文件切割成多个分片,并通过FormData对象将分片数据和其他参数(如当前分片索引、总分片数)一同发送到服务器端。

上述仅为演示前端分片上传的基本思路,并未涉及实际的服务器端处理逻辑。服务器端收到每个分片后,可以根据索引进行存储,待所有分片上传完成后再进行合并操作。