node實現檔案分片上傳
前端在做檔案上傳時,考慮到網速的快慢,如果檔案過大的話可能會導致上傳時間過長而請求超時,檔案上傳失敗。因此檔案過大需要對檔案進行分片上傳。
那檔案分片上傳的具體過程是怎樣的呢?
進行了許多搜尋搜尋之後,參照眾多資源進行修改,得到了自己的簡易實現流程。
首先列出來node需要用到的模組:
1 2 3 4 5 6 | const express = require('express'); var multer = require('multer'); var fs=require('fs'); var path = require('path'); var app = express(); var fse = require('fs-extra'); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | { "name": "fileloader", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo "Error: no test specified" && exit 1", "start": "nodemon index.js" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "art-template": "^4.13.2", "express": "^4.17.1", "express-art-template": "^1.0.1", "fs-extra": "^9.1.0", "jquery": "^3.6.0", "multer": "^1.4.2" } } |
伺服器例項採用express框架快速建置。配合art-template 和 express-art-template進行頁面處理
multer 模組用於處理檔案上傳
fs-extra模組倒不是必須的,只是參照其他的文章,採用這個模組來刪除資料夾較為方便。
multer配置介紹
multer用於作為處理檔案上傳的中介軟體,可以透過 var upload = multer({dest: '路徑'})來例項化multer,然後在路由中配置upload.single('file') 作為中介軟體。如官方的檔案介紹所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | var express = require('express') var multer = require('multer') var upload = multer({ dest: 'uploads/' }) var app = express() app.post('/profile', upload.single('avatar'), function (req, res, next) { }) app.post('/photos/upload', upload.array('photos', 12), function (req, res, next) { }) var cpUpload = upload.fields([{ name: 'avatar', maxCount: 1 }, { name: 'gallery', maxCount: 8 }]) app.post('/cool-profile', cpUpload, function (req, res, next) { }) |
官方的範例中,upload例項有三種主要的使用形式,即upload.single()、upload.array()、upload.fileds(),這三者簡單來說應該是single用於處理單檔案,array和fieds都能處理多檔案,由於專案僅演示單檔案處理過程,所以這兩個不過多贅述,想要了解更多請閱讀multer中文翻譯檔案
.single(filename) --> 接受一個以
另外,multer也會向req中新增body欄位,如果想要將multer作為body-parser的替代,需要配置類似如下的路由
1 2 3 | app.post('/merge',upload.none(),function(req,res){ }) |
此外,如果前端使用jquery的ajax進行上傳,需要配置一些特殊的選項,傳遞的資料也需要是一個 FormData()物件,我不知道這是不是必須,但是我用這種方法暫時未出現問題,這也是我用multer替代body-parser 出現的問題。
當然,這只是簡單地配置,如果需要對檔案儲存路徑和檔名進行特殊的設定,需要配置 storage 引數,這是磁碟儲存引擎,可以控制檔案的儲存。
它有兩個選項可用,
注意: 如果你提供的
程式碼範例
前端程式碼範例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>document</title> <style> body,div,p,h1,h2,h3,h4,h5,h6,ul,li{margin:0;padding:0;list-style:none;} ul,ol{list-style:none;} input{outline-style: none;padding: 0;} .wrap{ width: 600px; height: 400px; background-color: lightblue; border-radius: 20px; margin: 50px auto; } label[for="file"]{ display: inline-block; width: 100px; height: 100px; background-color: lightcoral; text-align: center; /* vertical-align: middle; */ line-height: 100px; } #file{ opacity: 0; } input[type="button"]{ width: 80px; height: 32px; margin: 10px 10px; background-color: rgba(0, 0, blue, 0.5); outline-style: none; } </style> </head> <body> <div class="wrap"> <div class="form"> <label for="file">選擇檔案</label> <input type="file" id="file" name="file" accept="*" > <br> <input type="button" value="上傳" onclick="upload(0)"> </div> </div> <script src="/node_modules/jquery/dist/jquery.js"></script> <script> var chunkSize = 1024*1024; var fileInput = document.querySelector("#file"); function upload(index){ let files = fileInput.files; if(!files[0]){ alert("請選擇檔案") return } let file = files[0];// file物件 //取得檔名和副檔名 let [fname,fext] = file.name.split('.') // 分片 let start = index * chunkSize if(start > file.size){ merge(file.name); return; } let blob = file.slice(start,start + chunkSize) let blobName = `${fname}.${index}.${fext}`; let blobFile = new File([blob],blobName); // 上傳檔案 let formData = new FormData() formData.append('file',blobFile); $.ajax({ type: 'post', url: '/upload', data: formData, contentType: false, processData: false, success: function(res){ console.log(res); upload(++index);//遞迴上傳檔案分片 } }) } // 合併檔案請求 function merge(filename){ var formData = new FormData() formData.append('name',filename) $.ajax({ type: 'post', url:"/merge", data: formData, contentType: false, processData: false, dataType: 'json', success: function(res){ console.log(res); } }) } </script> </body> <html> |
透過input核選框,使用者選擇檔案後該元素回傳一個File物件,存在input.files中。
File物件繼承自Blob物件,擁有slice方法,回傳一個blob子物件,是file的一個切片。
得到檔案分片後,設定其分片的順序,並將其新增到一個FormData物件中。
用jquery的ajax進行上傳,需要配置兩個引數:
contentType: false,
processData: false,
不配置可能會報錯。
所有分片上傳之後,需要發送請求進行檔案合併,函式merge即為請求合併檔案的函式。
後台程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | const express = require('express'); var multer = require('multer'); var fs=require('fs'); var path = require('path'); var app = express(); var fse = require('fs-extra'); // 用於檢測是否存在用於存放檔案的路徑,不存在則建立路徑 const createFolder = function(folder){ try{ fs.accessSync(folder); }catch(e){ fs.mkdirSync(folder); } }; // 檔案上傳的路徑 var uploadFolder = './uploads/'; createFolder(uploadFolder); // 用於傳給multer進行複雜的檔案上傳配置 var storage = multer.diskStorage({ destination: function(req,file,cb){ // 用於進行複雜的路徑配置,此處考慮分片上傳,先將分片檔案儲存在臨時目錄中 let [fname,index,fext] = file.originalname.split("."); let chunkDir = `${uploadFolder}/${fname}`; if(!fse.existsSync(chunkDir)){ fse.mkdirsSync(chunkDir); } cb(null,chunkDir); //內部提供的回呼函式 }, filename: function(req,file,cb){ // 根據上傳的檔名,按分片順序用分片索引命名, // 由於是分片檔案,請不要加副檔名,在最後檔案合併的時候再新增副檔名 let fname = file.originalname; cb(null,fname.split('.')[1]); } }) var upload = multer({storage: storage});// multer例項 // 配置模板引擎 app.engine('html',require('express-art-template')) app.set('views',__dirname+'/views') // 靜態資源路由 app.use('/node_modules',express.static('node_modules')) app.use('/upload',express.static('uploads')) // 主頁路由 app.get('/',function(req,res){ res.render('index.html') }) // 上傳路由 app.post('/upload',upload.single('file'),function(req,res,next){ res.end("ok"); }) // 檔案合併路由 app.post('/merge',upload.none(),function(req,res){ let name = req.body.name; let fname = name.split('.')[0]; let chunkDir = path.join(uploadFolder,fname); let chunks = fs.readdirSync(chunkDir); // 同步讀取以防檔案合併順序混亂 chunks.sort((a,b)=>a-b).map(chunkPath=>{ fs.appendFileSync( path.join(uploadFolder,name), fs.readFileSync(`${chunkDir}/${chunkPath}`) ) }) fse.removeSync(chunkDir); res.send({msg:'合併成功',url:`http://localhost:8080/upload/${name}`}); }) app.listen(8080,()=>{ console.log("success.....localhost:8080") }) |
配置 multer的磁碟儲存引擎時, 在destination中設定了儲存分片檔案的臨時資料夾,在filename 中設定了按照分片索引來儲存檔案,不新增副檔名。
在 merge 路由中進行檔案合併,同步讀取檔案臨時目錄,用 fs 模組對檔案進行排序之後進行檔案合併。
專案地址:我的gitee倉庫