谈谈大文件分片上传

这个问题对于一些视频网站,是非常常见的一个问题。

首先我们要知道为什么要分片上传。

  • 提高上传速度:通过将大文件切割成多个小片段,可以减少单个文件的传输时间,从而提高上传速度。
  • 提高上传稳定性:分片上传可以实现断点续传,即使在上传过程中断开连接,也可以从上次中断的地方继续上传,提高了上传的稳定性。
  • 减少服务器压力:分片上传可以将大文件切割成多个小片段进行上传,这样可以减少服务器的压力,提高服务器的并发处理能力。
  • 增加传输安全性:在某些情况下,对大文件进行分片上传可以增加数据的安全性,防止单个文件被攻击或篡改。

最简单的一个道理,我们拿b站的举例。如果我们再这个过程中,断网了。那么一整个文件是全部都要重新上传吗?很明显b站的做法不是的。

并且我们可以发现在上传的过程中

image-20230902213745228

是不断的去发送ajax请求的。也就是我们要聊的分片上传

我们可以把一个大的文件,切成很多小片,一片一片的进行上传。

下面我们来看如何进行具体的实现。

如何进行分片

首先我们来进行一个实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<body>
<input type="file" />

</body>
<script>
const input = document.querySelector('input');
input.onchange = (e)=>{
const file = input.files[0];
if (!file){
return;
}
console.log(file)
}
</script>

可以看到是一个File对象

image-20230902214111795

之后我们用这个要用到slice函数

1
2
const blob = file.slice(0,100);
console.log(blob)

这个打印出来的东西可以自己去看 Blob对象没有特定的数据类型,只是原始数据的一个包装

知道这个原理之后。我们以此来封装一个函数

1
2
3
4
5
6
7
function createChunks(file,chunkSize){
const result=[]
for (let i = 0; i < file.size; i+=chunkSize){
result.push(file.slice(i,i+chunkSize))
}
return result;
}
1
2
const chunks = createChunks(file,10*1024*1024);//10MB
console.log(chunks)

可以看到这就完成的切片操作

image-20230902214643587

Blob对象有两个主要属性

  • size:Blob的字节长度。
  • type:Blob的MIME类型,如果未知则返回空字符串。

之后我们需要读取数据的时候。需要FileReader才可以进行读取

关于如何把分片的内容合在一块,这就是后端要去做的了,我们前端的职责就是负责把请求通过ajax发送过去就可以了。

断网情况解决

为了解决断网后的情况,我们需要通过ajax不断的去告诉服务器,我们上传到了哪里。那么如何对文件进行唯一标识呢?

这里我们用到了spark-md5

安装:

1
npm install --save spark-md5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function hash(chunks) {
return new Promise((resolve)=>{
const sparkMD5 = new SparkMD5();
function _read(i){
if (i>=chunks.length){
resolve(sparkMD5.end())
return;//读取完毕
}
const blob=chunks[i];
const fileReader = new FileReader();
fileReader.onload = e=> {
const bytes = e.target.result;//读取到的字节数组
sparkMD5.append(bytes)
_read(i+1)
}
fileReader.readAsArrayBuffer(blob)
}
_read(0)
})

}
1
const result = await hash(chunks)

这样就可以得到一个唯一的文件标识了。

下面就是上面用到的所有实例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>

</head>
<body>
<input type="file" />

</body>
<script src="spark-md5.js"></script>
<script>

const input = document.querySelector('input');
input.onchange = async (e)=>{
const file = input.files[0];
if (!file){
return;
}
const chunks = createChunks(file,10*1024*1024);//10MB
const result = await hash(chunks)
console.log(result)
}

function hash(chunks) {
return new Promise((resolve)=>{
const sparkMD5 = new SparkMD5();
function _read(i){
if (i>=chunks.length){
resolve(sparkMD5.end())
return;//读取完毕
}
const blob=chunks[i];
const fileReader = new FileReader();
fileReader.onload = e=> {
const bytes = e.target.result;//读取到的字节数组
sparkMD5.append(bytes)
_read(i+1)
}
fileReader.readAsArrayBuffer(blob)
}
_read(0)
})

}

function createChunks(file,chunkSize){
const result=[]
for (let i = 0; i < file.size; i+=chunkSize){
result.push(file.slice(i,i+chunkSize))
}
return result;
}
</script>
</html>