如何实现大文件上传
问题背景
如果将大文件一次性上传,耗时会非常长,甚至可能传输失败,那么我们怎么解决这个问题呢?既然大文件上传不适合一次性上传,那么我们可以尝试将文件分片散上传。 这样的技术就叫做分片上传。分片上传就是将大文件分成一个个小文件(切片),将切片进行上传,等到后端接收到所有切片,再将切片合并成大文件。通过将大文件拆分成多个小文件进行上传,确实就是解决了大文件上传的问题。因为请求时可以并发执行的,这样的话每个请求时间就会缩短,如果某个请求发送失败,也不需要全部重新发送。
解决方案
前置知识
首先,我们需要理解以下两点:
<input />
标签的作用- 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>
转载自:https://juejin.cn/post/7347616740862214170