大檔案切片上傳(Vue+NodeJS)
阿新 • • 發佈:2020-12-15
技術標籤:Nodejsvuenodejs大資料uploadjavascript
大檔案上傳時,前端切片,上傳後,後端組合
先上介面
前臺
<template> <div> <div style="margin:20px"> <h2>Upload Large Files</h2> <br> <el-row> <el-col :span="4"> <input id="uppic" type="file" multiple @change="selectedFiles()" ref="upload"> <label for="uppic"> <div id="wrapper"> <div id="cell"> <span> <i class="el-icon-folder-opened"></i> Choose Files </span> </div> </div> </label> </el-col> <el-col :span="10" :offset="1"> <el-button id="userbtn" class="bg-main tc userbtn" type="success" @click="upload"> <i class="el-icon-upload"></i> Upload </el-button> </el-col> </el-row> <el-row v-if="fileList.length!=0"> <el-table :data="fileList" style="width: 100%"> <el-table-column prop="name" label="Name" width="500"> </el-table-column> <el-table-column prop="size" label="Size (KB)" width="180"> </el-table-column> </el-table> </el-row> <el-row> <el-col :span="20"> <el-progress :text-inside="true" :stroke-width="26" :percentage="percentage"></el-progress> </el-col> </el-row> </div> </div> </template> <script> import async from 'async' export default { data: () => ({ percentage:0, fileList:[] }), methods:{ upload(){ this.percentage=0 let _this=this for(let k in this.$refs.upload.files){ let file=this.$refs.upload.files[k],//上傳檔案主體 name = file.name, //檔名 size = file.size, //總大小 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); } async.eachLimit(attr,1,async function(item,callback){ let i=item; let start = i * shardSize,//當前分片開始下標 end = Math.min(size, start + shardSize);//結束下標 //構造一個表單,FormData是HTML5新增的 let form = new FormData(); form.append("data", file.slice(start,end)); //slice方法用於切出檔案的一部分 form.append("name", name);//檔名字 form.append("total", shardCount); //總片數 form.append("index", i + 1); //當前片數' //api是後臺上傳介面 await _this.$axios.post(‘api’,form,{ timeout: 120*1000 }) .then(res=>{ ++succeed; /*返回code為0是成功上傳,1是請繼續上傳*/ if(res.data.code==0){ console.log(res.data.mssg); _this.percentage=0 _this.fileList.shift() _this.$message({ type:'success', message:'upload finish'+file.name, showClose:true }) }else if(res.data.code==1){ console.log(res.data.msg); } //生成當前進度百分比 _this.percentage=Math.round(succeed/shardCount*100); /*如果是線上,去掉定時,直接callback(), 這樣寫是為方便,本地測試看到進度條變化 因為本地做上傳測試是秒傳,沒有時間等待*/ // setTimeout(callback,50); callback() }) },function(err){ console.log('上傳成功'); }); } }, selectedFiles(){ console.log('selected',this.$refs.upload.files) this.fileList= [...this.$refs.upload.files] } }, watch:{ } } </script> <style scoped> #uppic { width: 0.1px; height: 0.1px; opacity: 0; overflow: hidden; position: absolute; z-index: -1; } .el-row { margin-bottom: 20px; } label{ color: aquamarine; background-color: #796e02e3; height: 40px; display: block; text-align: center; } #wrapper {display:table;height:40px;margin:0 auto;color:rgb(255, 255, 255);} #cell{display:table-cell; vertical-align:middle;} </style>
後臺
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) } }); };