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