文件切片上传原理解析


前端上传文件时若是文件很大,上传时会出现各类问题,好比链接超时了,网断了,都会致使上传失败。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源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索