前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >文件切片上传原理解析

文件切片上传原理解析

作者头像
挥刀北上
发布2019-08-06 16:22:09
8.2K0
发布2019-08-06 16:22:09
举报
文章被收录于专栏:Node.js开发

前端上传文件时如果文件很大,上传时会出现各种问题,比如连接超时了,网断了,都会导致上传失败。

为了避免上传大文件时上传超时,就需要用到切片上传,工作原理是:我们将大文件切割为小文件,然后将切割的若干小文件上传到服务器端,服务器端接收到被切割的小文件,然后按照一定的顺序将小文件拼接合并成一个大文件。

下面的实例就是如何一步步实现大文件切片上传。实例中运用到的技术包括:H5(前端使用)和nodejs(后端使用)。这个实例为了演示简便,我们使用大的图片上传来演示。

首先,我们来看一下上传表单的演示效果和代码,效果如下:

html结构如下:

因为这里使用的是ajax上传,所以没有使用form元素,直接使用一个上传文件的input来获取上传图片的数据。

获取图片数据用到了input元素的一个属性:flies属性,通过document.getElementById("file").files[0] 来获取图片数据执行如下代码:

我们将其结果打印出来,如图所示:

打印的结果包含着图片的信息,这个信息是一个blob对象,这个对象被浏览器读取到了内存中,我们可以通过chrome://blob-internals/ 这个地址来查看浏览器读取到的blob的信息,如图所示:

读取了图片的数据之后,就将数据切片,然后将每次切割的小片文件上传到服务器,切片运用到了silce方法,代码如下:

代码语言:javascript
复制
<!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>

代码解析见注释。核心代码就是这一段:

代码语言:javascript
复制
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,前一个是负责接收切片信息,后一个负责合并切片。

这里一般的做法是设置两个接口,一个接口负责接收图片的切片信息,将其保存,另外一个接口负责拼接切片信息。这样做的原因是,如果用一个接口来操作的话,每张切片接收完成后都要去检查所有切片是否都接收完成,而只有当所有切片完成才能将切片合并,这样比较耗费服务端的性能。

接口处理代码如下:

代码语言:javascript
复制
// 接收切片信息接口
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切片上传的实例,以供大家参考。

欢迎关注公众号,有疑问可以留言给我!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2017-11-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 nodejs全栈开发 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档