大文件切片上传(Vue+NodeJS)

思路

大文件前台切成若干份2M小文件,分别传输给后台,后台分别写入硬盘,在最后一片被后台接收后,读取所有切片并合并成为原始大文件

方式

主要实现两种方式的大数据切片传输

  1. 前台切片,后台合并
  2. node后台切片,另一服务器端合并

Code

1 前台切片

大文件上传时,前端切片,上传后,后端组合

先上界面
大文件切片上传(Vue+NodeJS)_第1张图片

前台







后台

exports.upload=function user(req,res,config){ 
     
    let fs=require('fs');
    let async = require('async');//异步模块
    let formidable=require('formidable')
    let form=new formidable.IncomingForm();
    

    //设置编辑
    form.encoding = 'utf-8';

    let dirPath=__dirname+"/../uploadFiles/tep/";
     
    //设置文件存储路径
    form.uploadDir = dirPath;
    //设置单文件大小限制
   // form.maxFilesSize = 200 * 1024 * 1024;
    /*form.parse表单解析函数,fields是生成数组用获传过参数,files是bolb文件名称和路径*/
    form.parse(req, function (err,fields,files) {
         files=files['data'];//获取bolb文件
         let index=fields['index'];//当前片数
         let total=fields['total'];//总片数
         let name=fields['name'];//文件名称
         let url= dirPath+'/'+name.split('.')[0]+'_'+index+'.'+name.split('.')[1];//临时bolb文件新名字
         fs.renameSync(files.path,url);//修改临时文件名字
    
         try{
            if(index==total){//当最后一个分片上传成功,进行合并
                /*
                    检查文件是存在,如果存在,重新设置名称
                */
                let uid=uuid.v4()
         
                fs.mkdirSync(__dirname+"/../uploadFiles/"+uid)
                let pathname=__dirname+"/../uploadFiles/"+uid+'/'+name;//上传文件存放位置和名称
                fs.access(pathname,fs.F_OK,(err) => {
                    if(!err){   
                        let myDate=Date.now();
                        pathname=dirPath+'/'+myDate+name;
                        console.log(pathname);

                    }
                });
                //这里定时,是做异步串行,等上执行完后,再执行下面
                setTimeout(function(){
                    /*进行合并文件,先创建可写流,再把所有BOLB文件读出来,
                        流入可写流,生成文件
                        fs.createWriteStream创建可写流   
                        aname是存放所有生成bolb文件路径数组:
                        ['Uploads/img/3G.rar1','Uploads/img/3G.rar2',...]
                    */
                    let writeStream=fs.createWriteStream(pathname);
                    let aname=[];
                    for(let i=1;i<=total;i++){
                        let url=dirPath+'/'+name.split('.')[0]+'_'+i+'.'+name.split('.')[1];
                        aname.push(url);
                    }

                    //async.eachLimit进行同步处理
                    async.eachLimit(aname,1,function(item,callback){
                        //item 当前路径, callback为回调函数
                        fs.readFile(item,function(err,data){    
                           if(err)throw err;
                           //把数据写入流里
                            writeStream.write(data);
                            //删除生成临时bolb文件              
                            fs.unlink(item,function(){console.log('删除成功');})
                            callback();
                        });
                    },function(err){
                        if (err) throw err;
                        //后面文件写完,关闭可写流文件,文件已经成生完成
                        writeStream.end();



                        //返回给客服端,上传成功
                        let data=JSON.stringify({'code':0,"data": {
                            "source_store_id": uid,
                            "file_name": name
                        }});
                        res.writeHead(200, {'Content-Type': 'text/html;charset=utf-8'}); 
                        res.end(data);//返回数据    
                    });
                },50);

            }else{//还没有上传文件,请继续上传
                let data=JSON.stringify({'code':1,'msg':'继续上传'});
                res.writeHead(200, {'Content-Type': 'text/html;charset=utf-8'}); 
                res.end(data);//返回数据    
            }
         }catch(err){
             console.log(err)
         }
       
    });
   
}; 

2 Node后台切片,传输给另外服务器

关键点在于,Node后台的切片因为没有HTML中的file api, 无法利用File对象继承自Blob的slice方法,以进行切片,进一步去做合并。经过尝试可以利用Node提供的Buffer中的slice进行切片

大部分逻辑同上,故这里只贴出关键代码

客户端Node后台

 let distFile = fs.readFileSync(
          __dirname + "/../dataStorage/" + serviceItem.id + ".zip"
        );
        let largrFile = Buffer.from(distFile);
        let name = serviceItem.name+'.zip', //文件名
          size = largrFile.length, //总大小
          succeed = 0; //当前上传数
        let shardSize = 2 * 1024 * 1024, //以2MB为一个分片
          shardCount = Math.ceil(size / shardSize); //总片数

        /*生成上传分片文件顺充,通过async.eachLimit()进行同步上传
                    attr里面是[0,1,2,3...,最后一位]    
                */
        let attr = [];
        for (let i = 0; i < shardCount; ++i) {
          attr.push(i);
        }
        
        let rp=res;
        try{
        async.eachLimit(
          attr,
          1,
          async function (item, callback) {
            let i = item;
            let start = i * shardSize, //当前分片开始下标
              end = Math.min(size, start + shardSize); //结束下标

            let minFile = largrFile.slice(start, end);

            let obj = {};
            obj["data"] = minFile;
            obj["name"] = name;
            obj["total"] = shardCount;
            obj["index"] = i + 1;
            obj["size"] = size;
            obj["start"] = start;
            obj["end"] = end;
            // http://111.229.14.128:8899/largeBKend
            // http://localhost:8898/upload 测试
            await axios
              .post("api", obj, {
                timeout: 1000*60*60,
              })
              .then((axiosRes) => {
                ++succeed;
                /*返回code为0是成功上传,1是请继续上传*/
                if (axiosRes.data.code == 0) {
                    console.log(axiosRes.data.data);
                    console.log('大文件切上传完成,拿回数据索引,准备转发')
                    let ws=new WebSocket('ws://111.229.14.128:1708');
                    let msg={
                      msg:'Migration',
                      bk:true,
                      serviceDownloadId: axiosRes.data.data.source_store_id,
                      fromToken: req.query.fromToken,
                      targetToken: req.query.targetToken
                   }
                   ws.on('open',()=>{
                    ws.send(
                      JSON.stringify(msg)
                    )
                    ws.close()
                   })
                   ws.on('message',(data)=>{
                    if(data=='node offline'){
                      console.log('node offline')
                      ws.close()
                    }else{
                     console.log(data)

                    }
                  })

                } else if (axiosRes.data.code == 1) {
                  console.log(axiosRes.data.msg);
                  
                  let data=JSON.stringify({'code':1});
                  rp.end(data);//返回数据
                  
                }
                //生成当前进度百分比
                // _this.percentage=Math.round(succeed/shardCount*100);
                console.log(
                  "进度: " + Math.round((succeed / shardCount) * 100)
                );
            
             
                // callback()
              });
          },
          function (err) {
            if(err){
              console.log(err)
              rp.send({code:-1})
              return
            }
          }
        );
        }catch(err){
          console.log(err)
        }

服务端后台


    let fs=require('fs');
    let async = require('async');//异步模块
    
        // 切片的临时存储
         let dirPath=__dirname+"/../uploadFiles/tep/";
    
         let index=req.body['index'];//当前片数
         let total=req.body['total'];//总片数
         let name=req.body['name'];//文件名称
       
         let url= dirPath+'/'+name.split('.')[0]+'_'+index+'.'+name.split('.')[1];//临时bolb文件新名字
      
         try{
            if(index==total){//当最后一个分片上传成功,进行合并
                /*
                    检查文件是存在,如果存在,重新设置名称
                */
          
                    let bf=Buffer.from(req.body['data'])
                    fs.writeFileSync(url,bf)
                
                    let uid=uuid.v4()
                    fs.mkdirSync(__dirname+"/../uploadFiles/"+uid)
                    let pathname=__dirname+"/../uploadFiles/"+uid+'/'+name;//上传文件存放位置和名称
                    //这里定时,是做异步串行,等上执行完后,再执行下面
                    setTimeout(function(){
                    let writeStream=fs.createWriteStream(pathname);
                    let aname=[];
                    for(let i=1;i<=total;i++){
                        let url=dirPath+'/'+name.split('.')[0]+'_'+i+'.'+name.split('.')[1];
                        aname.push(url);
                    }
                    let bf=[]
                    //async.eachLimit进行同步处理
                    async.eachLimit(aname,1,function(item,callback){
                        //item 当前路径, callback为回调函数
                        fs.readFile(item,function(err,data){    
                           if(err)throw err;
                           //把数据写入流里,这里有两种方式
                           // 第一种是利用stream边读边写,这种方式相对于第二种对于内存更加友好
                            writeStream.write(data);
                            // 第二种是利用拼接buffer进行,但由于是将所有文件读取buffer并放在bf数组中进行拼接,可能由于bf数组过大导致内存溢出,所以舍弃
                            // bf.push(data)

                            //删除生成临时bolb文件              
                            fs.unlink(item,function(){console.log('删除成功');})
                            callback();
                        });
                    },function(err){
                        if (err) throw err;
                        //后面文件写完,关闭可写流文件,文件已经成生完成
                        // 这里同时有两种方式进行文件合并
                        //第一种,是关闭流,由于利用stream是边读边写的,对内存友好
                        writeStream.end();

                        // 第二种,利用Buffer的concat函数进行buffer拼接,这种方式可能会造成内存溢出,故舍弃
                        //  let re=Buffer.concat(bf)
                        //  fs.writeFileSync(pathname,re)

                       
                        //返回给客服端,上传成功
                        let data=JSON.stringify({'code':0,"data": {
                            "source_store_id": uid,
                            "file_name": name
                        }});
                        res.writeHead(200, {'Content-Type': 'text/html;charset=utf-8'}); 
                        res.end(data);//返回数据    
                    });
                },50)
                
            }else{//还没有上传文件,请继续上传
                // bf.fill(req.body['data'])
                let bf=Buffer.from(req.body['data'])
                fs.writeFileSync(url,bf)


                let data=JSON.stringify({'code':1,'msg':'继续上传'});
                res.writeHead(200, {'Content-Type': 'text/html;charset=utf-8'}); 
                res.end(data);//返回数据    
            }
         }catch(err){
             console.log(err)
         }

你可能感兴趣的:(Nodejs,vue,nodejs,大数据,upload,javascript)