聊一聊前端的大文件上传思路

1,272 阅读3分钟

为什么要对大文件上传进行讨论

在前端开发中,我们经常会遇到需要上传文件的场景,比如上传图片、视频、音频等。上传文件的过程中,我们可能会遇到一些问题,比如:

  • 文件过大,导致上传时间过长,用户体验不佳
  • 文件过大,导致服务器压力过大,资源消耗过多
  • 文件过大,导致网络不稳定时,上传失败,需要重新上传,浪费时间和流量
  • 文件过大,导致浏览器内存占用过多,影响性能和稳定性

为了解决这些问题,我们需要对大文件上传进行优化。

在现代的互联网应用中,用户对于文件上传的需求越来越高,比如:

  • 在社交平台上分享高清的图片和视频
  • 在教育平台上提交作业和课程资料
  • 在企业平台上上传项目文档和报告

这些场景中,用户可能需要上传的文件大小都不小,甚至有可能达到几百兆甚至几个G。如果我们使用传统的文件上传方式,就会遇到上面提到的问题。

传统的文件上传方式是将整个文件作为一个请求体发送给服务器,这种方式有以下几个缺点:

  • 上传时间长:由于文件较大,需要较长的时间来传输数据,用户需要等待很久才能看到上传结果
  • 服务器压力大:由于文件较大,服务器需要一次性接收和处理大量的数据,可能会导致服务器内存、CPU、带宽等资源消耗过多
  • 网络不稳定时容易失败:由于文件较大,网络传输过程中可能会出现断网、超时、丢包等情况,导致上传失败,用户需要重新上传整个文件
  • 浏览器内存占用高:由于文件较大,浏览器需要将整个文件读取到内存中,并且保持连接状态,可能会导致浏览器内存占用过高,影响其他页面的性能和稳定性

为了解决这些问题,我们需要对大文件上传进行优化。

设计思路

对于大文件上传的优化思路,主要有以下几个方面:

  • 分片:将一个大文件分割成多个小片段(chunk),每个片段作为一个单独的请求发送给服务器。这样可以减少单次请求的数据量,缩短上传时间,降低服务器压力,并且可以实现断点续传的功能。
    function sliceFile(file, chunkSize) {
      const fileSize = file.size;
      const chunks = Math.ceil(fileSize / chunkSize);
      const slice = Array.from({ length: chunks }, (_, index) => {
        const start = index * chunkSize;
        const end = start + chunkSize;
        return file.slice(start, end);
      });
      return slice;
    }
    
  • 并发:同时发送多个分片请求给服务器。这样可以充分利用网络带宽和服务器资源,并且可以提高用户体验。
    async function uploadChunks(fileChunks) {
      const uploadPromises = fileChunks.map((chunk) => {
        return fetch('/upload', { method: 'POST', body: chunk });
      });
      const responses = await Promise.all(uploadPromises);
      return responses;
    }
    
  • 压缩:在发送分片请求之前,对每个分片进行压缩处理。这样可以进一步减少数据量,并且可以提高传输效率。
    async function compressChunk(chunk) {
      const compressedChunk = await new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onload = (event) => {
          const result = pako.deflate(event.target.result);
          resolve(result);
        };
        reader.onerror = (event) => {
          reject(event.error);
        };
        reader.readAsArrayBuffer(chunk);
      });
      return compressedChunk;
    }
    
  • 校验:在发送分片请求之前或之后,对每个分片进行校验处理。这样可以保证数据的完整性和正确性,并且可以避免重复或错误的数据传输。
    async function verifyChunk(chunk) {
      const hash = await calculateHash(chunk);
      const response = await fetch(`/verify?hash=${hash}`);
      const result = await response.json();
      return result;
    }
    
  • 断点续传: 如果碰到网络故障,可以从已经上传或下载的部分开始继续上传下载未完成的部分,而没有必要从头开始上传下载。用户可以节省时间,提高速度
    async function resumeUpload(file, resumeByte) {
      const blob = file.slice(resumeByte);
      const formData = new FormData();
      formData.append('file', blob);
      const response = await fetch('/upload', { method: 'POST', body: formData });
      const result = await response.json();
      return result;
    }
    
  • 秒传: 在对文件进行切片上传前对文件进行hash计算,并发送给后端,如果后端发现有相同文件则可以直接显示上传成功,避免重复上传文件。
    async function checkFileExists(file) {
      const hash = await calculateHash(file);
      const response = await fetch(`/check?hash=${hash}`);
      const result = await response.json();
      return result;
    }
    

总结

本文介绍了为什么要对大文件上传进行优化,以及优化的主要思路。通过代码示例,展示了如何实现这些优化方法,目的是帮助读者理解和实现大文件上传的优化方案。