背景说明
如果太大的文件,比如:一个视频(2G),一般的文件上传方法可能会出链接现超时的情况,而且也会超过服务端允许上传文件的大小限制,所以解决这个问题我们可以将文件进行分片上传,每次只上传很小的一部分(2M)。
Blob 它表示原始数据, 也就是二进制数据,同时提供了对数据截取的方法 slice,而 File 继承了 Blob 的功能,所以可以直接使用此方法对数据进行分段处理。
过程分析
把大文件进行分段(2M),发送到服务器的同时,携带一个标志(当前的时间戳),用于标识一个完整的文件。
- 服务端保存各段文件
- 客户端所有文件分片上传完成之后,发送给服务端一个合并文件的请求
- 服务端根据文件标识、类型、各分片顺序进行文件合并
- 删除分片文件
前端JS代码
function upload() {
// 分片大小设置为 2MB
var chunkSize = 2097152;
// 获取选择的文件
var file = document.getElementById('f1').files[0];
// 存放文件分片的数组
var chunks = [];
// 使用当前时间戳作为上传标识符(token)
var token = new Date().getTime();
// 获取文件名
var name = file.name;
// 用来记录文件分片的数量
var chunkCount = 0;
// 用来记录已经上传的分片数量
var sendChunkCount = 0;
// 如果文件大小大于分片大小
if (file.size > chunkSize) {
var start = 0, end = 0;
while (true) {
// 每次将end增加一个分片大小
end += chunkSize;
// 切割文件分片
var blob = file.slice(start, end);
// 更新起始位置
start += chunkSize;
// 如果没有更多的分片,跳出循环
if (!blob.size) {
break;
}
// 将分片加入数组
chunks.push(blob);
}
} else {
// 如果文件较小,则不分片,直接加入
chunks.push(file.slice(0));
}
chunkCount = chunks.length;
// 遍历所有分片
for (var i = 0; i < chunkCount; i++) {
// 创建一个新的 FormData 对象来发送数据
var fd = new FormData();
// 添加上传会话标识
fd.append('token', token);
// 添加当前分片数据
fd.append('f1', chunks[i]);
// 添加当前分片的索引
fd.append('index', i);
// 调用上传函数
xhrSend(fd, function() {
// 成功上传一个分片,已上传分片数加一
sendChunkCount += 1;
// 如果所有分片都上传完毕
if (sendChunkCount === chunkCount) {
// 上传完成,发送合并请求
var formD = new FormData();
// 标明这是一个合并请求
formD.append('type', 'merge');
// 上传会话标识
formD.append('token', token);
// 分片数量
formD.append('chunkCount', chunkCount);
// 文件名
formD.append('filename', name);
// 发送合并请求
xhrSend(formD);
}
});
}
}
function xhrSend(fd, cb) {
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://localhost:8100/', true);
// 请求状态变化时触发的回调
xhr.onreadystatechange = function() {
console.log('状态变化', xhr.readyState);
if (xhr.readyState == 4) {
console.log("打印服务器响应", xhr.responseText);
// 如果回调函数存在,调用它
cb && cb();
}
}
xhr.send(fd);
}
//绑定提交事件
document.getElementById('btn-submit').addEventListener('click',submitUpload);