前端上传文件时若是文件很大,上传时会出现各类问题,好比链接超时了,网断了,都会致使上传失败。javascript
为了不上传大文件时上传超时,就须要用到切片上传,工做原理是:咱们将大文件切割为小文件,而后将切割的若干小文件上传到服务器端,服务器端接收到被切割的小文件,而后按照必定的顺序将小文件拼接合并成一个大文件。css
下面的实例就是如何一步步实现大文件切片上传。实例中运用到的技术包括:H5(前端使用)和nodejs(后端使用)。这个实例为了演示简便,咱们使用大的图片上传来演示。html
首先,咱们来看一下上传表单的演示效果和代码,效果以下:前端
html结构以下:java
由于这里使用的是ajax上传,因此没有使用form元素,直接使用一个上传文件的input来获取上传图片的数据。
node
获取图片数据用到了input元素的一个属性:flies属性,经过document.getElementById("file").files[0] 来获取图片数据。执行以下代码:jquery
咱们将其结果打印出来,如图所示:git
打印的结果包含着图片的信息,这个信息是一个blob对象,这个对象被浏览器读取到了内存中,咱们能够经过chrome://blob-internals/ 这个地址来查看浏览器读取到的blob的信息,如图所示:github
读取了图片的数据以后,就将数据切片,而后将每次切割的小片文件上传到服务器,切片运用到了silce方法,代码以下:web
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>upload</title>
<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
<script src="./uuid.js"></script>
</head>
<body>
<input type="file" name="file" id="file">
<button id="upload">上传</button>
<script type="text/javascript">
var bytesPerPiece = 1024 * 1024; // 每一个文件切片大小定为1MB .
var totalPieces; //切片总数
//发送请求
$("#upload").click(upload)
function upload() {
var blob = document.getElementById("file").files[0];
// 文件惟一标识符号,防止多个用户一块儿上传文件时切片混乱
var uuidfolder = uuidv1();
// 开始切割的位置
var start = 0;
// 切割的结束位置
var end;
// 切片的索引
var index = 0;
// 回调计数器
var count = 0;
// 文件的大小
var filesize = blob.size;
// 文件的名称
var filename = blob.name;
//计算文件切片总数
totalPieces = Math.ceil(filesize / bytesPerPiece);
// 启动while循环对文件切片
while(start < filesize) {
// 设置切片的结束位置
end = start + bytesPerPiece;
// 对最后一片数据进行处理(能够省略)
if(end > filesize) {
end = filesize;
}
// 切割文件
var chunk = blob.slice(start,end);//切割文件
// 给每一片切片设置名字,名字的值为原始名称加索引,这样作是为了让后端能够按照索引顺序合并图片。
var sliceIndex= blob.name + index;
// 利用formData来传递数据
var formData = new FormData();
formData.append("file", chunk, sliceIndex);
formData.append("uuidfolder", uuidfolder);
formData.append("imgorder", index);
$.ajax({
url: '/upload3',
type: 'POST',
data: formData,
processData: false, // 不处理数据
contentType: false, // 不设置内容类型
}).done(function(res){
count++;
if(count==totalPieces){
console.log("上传结束,请求拼接接口,将切片信息拼接完整,返回图片url");
$.post('/merge',{id:uuidfolder},function(data){
console.log(data);
})
}
}).fail(function(res) {
console.log("上传失败")
});
start = end;
index++;
}
}
</script>
</body>
</html>
代码解析见注释。核心代码就是这一段:
while(start < filesize) {
// 设置切片的结束位置
end = start + bytesPerPiece;
// 对最后一片数据进行处理(能够省略)
if(end > filesize) {
end = filesize;
}
// 切割文件
var chunk = blob.slice(start,end);//切割文件
// 给每一片切片设置名字,名字的值为原始名称加索引,这样作是为了让后端能够按照索引顺序合并图片。
var sliceIndex= blob.name + index;
// 利用formData来传递数据
var formData = new FormData();
formData.append("file", chunk, sliceIndex);
formData.append("uuidfolder", uuidfolder);
formData.append("imgorder", index);
$.ajax({
url: '/upload3',
type: 'POST',
data: formData,
processData: false, // 不处理数据
contentType: false, // 不设置内容类型
}).done(function(res){
count++;
if(count==totalPieces){
console.log("上传结束,请求拼接接口,将切片信息拼接完整,返回图片url");
$.post('/merge',{id:uuidfolder},function(data){
console.log(data);
})
}
}).fail(function(res) {
console.log("上传失败")
});
start = end;
index++;
}
上面的代码启动了一个while循环,在这个循环中,每次截取固定大小的切片,而后用ajax上传到后端服务器,而且会附加一些比较重要的信息,这些信息主要包括:图片的惟一标识符(这里用到了uuid.js来生成惟一的id),切片的索引(为了后端按照切片顺序将切片合并),ajax每次上传完成后都要检查全部切片是否上传完成,所有上传完成后,请求合并接口,这个接口返回合并后的图片的url。
前端将切片信息传递到后端,后端用过nodejs接受切片,而后按照索引将切片拼接成完整的文件,这里用到了两个工具包multer和concat-files,前一个是负责接收切片信息,后一个负责合并切片。
这里通常的作法是设置两个接口,一个接口负责接收图片的切片信息,将其保存,另一个接口负责拼接切片信息。这样作的缘由是,若是用一个接口来操做的话,每张切片接收完成后都要去检查全部切片是否都接收完成,而只有当全部切片完成才能将切片合并,这样比较耗费服务端的性能。
接口处理代码以下:
// 接收切片信息接口
router.post('/upload3', upload.single('file'), function (req, res, next) {
console.log(req.body)
// 接受图片惟一标识符号
let imgname = req.body.uuidfolder;
// 接受切片索引
let imgorder = req.body.imgorder;
// 创建图片存储目录
let imgpath = path.join(__dirname,'..','public/mult',imgname);
// 判断目录是否存在,存在的话直接使用并存储切片,不存在的话就新建。
if (fs.existsSync(imgpath)) {
fs.readFile(req.file.path, function (err, data) {
fs.writeFile(path.join(imgpath, imgorder), data, (err) => {
if (!err) {
res.send("写入后面的文件")
}
})
})
} else {
fs.mkdirSync(imgpath);
fs.readFile(req.file.path, function (err, data) {
fs.writeFile(path.join(imgpath, imgorder), data, (err) => {
if (!err) {
res.send("第一次写入并新建文件夹")
}
})
})
}
})
// 合并图片接口:
router.post('/merge',function(req,res){
let id = req.body.id;
let folderpath = path.join(__dirname,"..",'public/mult',id);
let destinpath = path.join(__dirname,"..",'public/img',id+'.jpg');
let dist = '/img/'+id+'.jpg'
fs.readdir(folderpath,function(err,arr){
let arr2 = arr.map(e=>path.join(folderpath,e));
concat(arr2, destinpath, function(err) {
if (err) throw err
res.send(dist);
});
})
})
以上即是大文件切片上传的原理解析。
相较于单独上传一个文件而言,大文件上传在前端层面,多了一步切割的步骤,后端多了一步合并的步骤,只有先后端配合才能完成大文件切片上传。
文件源码地址:https://github.com/clm1100/slicefile
项目中不只有javascript原生语法实现大文件切片上传,还有webuploader切片上传的实例,以供你们参考。
欢迎关注公众号,有疑问能够留言给我!
本文分享自微信公众号 - nodejs全栈开发(geekclass)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。