文件切片上傳原理解析


前端上傳文件時若是文件很大,上傳時會出現各類問題,好比鏈接超時了,網斷了,都會致使上傳失敗。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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索