likes
comments
collection
share

如何实现大文件上传

作者站长头像
站长
· 阅读数 6

问题背景

如果将大文件一次性上传,耗时会非常长,甚至可能传输失败,那么我们怎么解决这个问题呢?既然大文件上传不适合一次性上传,那么我们可以尝试将文件分片散上传。 这样的技术就叫做分片上传。分片上传就是将大文件分成一个个小文件(切片),将切片进行上传,等到后端接收到所有切片,再将切片合并成大文件。通过将大文件拆分成多个小文件进行上传,确实就是解决了大文件上传的问题。因为请求时可以并发执行的,这样的话每个请求时间就会缩短,如果某个请求发送失败,也不需要全部重新发送。

解决方案

前置知识

首先,我们需要理解以下两点:

  1. <input />标签的作用
  2. File对象是什么

<input />标签用于上传文件,当我们通过<input />标签上传文件之后,可以通过input.files获取File对象。比如以下代码:

<!DOCTYPE html>
<html>
  <body>
    <input type="file" id="fileInput" onchange="handleFiles()" multiple>
    <script>
      function handleFiles() {
        var input = document.getElementById("fileInput");
        var files = input.files; // FileList对象

        for (var i = 0; i < files.length; i++) {
          var file = files[i]; // File对象
          console.log("文件名:", file.name);
          console.log("文件大小:", file.size);
          console.log("文件类型:", file.type);
          console.log("最后修改时间:", file.lastModifiedDate);
        }
      }
    </script>
  </body>
</html>

上传文件之后,可以看到打印结果如下。因此,我们发现每个File对象包含了有关文件的一些信息,如文件名、大小,类型和最后修改时间等等。

如何实现大文件上传

创建切片

通过以上例子看出,File对象就是我们需要上传的文件。在上例中,我们上传的文件大小是574835字节,也就是500多KB。这个文件太小了,于是后面我又上传了一个 8 MB的 文件。我们可以尝试将它分片上传(你自己尝试的时候,最好找个大文件比如几十兆的)

// 创建切片
function createChunk(file, size) {
	//file是大文件,size是切片的大小(也就是小文件的大小)
    const chunkList = []
    let cur = 0
    while (cur < file.size) {
        chunkList.push({
                file: file.slice(cur, cur + size)//使用slice()进行切片
        })
        cur += size
    }
    return chunkList;
}

以上函数用来创建切片,它的核心思想是:创建一个空的切片列表数组chunkList。将大文件分成若干个小切片,每个小切片的大小是size(size的大小你可以自定义)。

值得注意的是:这里的 slice 并非 Array 的原型方法,而是 File 对象从 Blob 对象继承而来,它的作用就是将一个大的 File 对象切割成若干个小的 File 对象。所以此处slice和数组没啥关系,不要搞混了。

接下来,我们可以调用上述函数,看看打印结果是什么。在调用函数的时候,第一个参数是 File 对象,在前置知识里已经讲了它的作用了。第二个参数是切片大小,2 * 1024 * 1024 个字节,也就是每个切片的大小是 2MB。最终,我们可以将一个 8MB 的文件 切成 4个小切片,

chunkList = createChunk(files, 2 * 1024 * 1024)
console.log(chunkList);

如何实现大文件上传

上传切片

首先进行数据处理。从上文可以知道,我们的每个小切片都是一个 File 对象。但是网络传输的时候,我们传输的是二进制文件。因此我们需要借助 FormData 来进行数据处理。FormData 对象会将数据编译成键值对,可用于发送带键数据。通过调用它的 append() 方法来添加字段。当我们调用 FormData.append(文件名,file对象) 的时候,file 对象会被转化为二进制。

<!DOCTYPE html>
<html lang="en">
<body>
  <div class="container">
    <h1>大文件上传</h1>
    <input type="file" id="fileInput" accept="image/*">
    <button id="uploadButton">切片上传</button>
    <br>
  </div>
  <script>
   //chunk就是一个切片,也就是小文件
    async function uploadChunk(chunk) {
      const formData = new FormData();
      formData.append('file', chunk);

      //这里的地址可以替换为你的后端地址
      const response = await fetch('https://file.io', {
        method: 'POST',
        body: formData
      });

      const result = await response.json();
      return result;
    }
    
 document.getElementById('uploadButton').addEventListener('click', async function() {
      const fileInput = document.getElementById('fileInput');
      const file = fileInput.files[0];
      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.slice(start, end);
        //上传一个切片
        const result = await uploadChunk(chunk);
        
      }
    });
  </script>
</body>
</html>