前端 :
<html> <head> <title>分片上傳文件</title> </head> <body> <div class="hei-bg" style="display:block;"> <div class="user-info" style="display:block;"> <div class="tc">請上傳大文件</div> <div class="user-pic picw320"> <input id="uppic" type="file"> </div> <div id="jd" class="jdb">進度</div> <div> <input type="button" value="確定" id="userbtn" class="bg-main tc userbtn"> </div> </div> </div> </body> <script src="/javascripts/jquery.min.js"></script> <script> $(function () { $('#userbtn').on('click', async function () { var d1 = new Date(); let file = $("#uppic")[0].files[0], //上傳文件主體 name = file.name, //文件名 size = file.size, //總大小 succeed = 0, //當前上傳數 shardSize = 1 * 1024 * 1024, //以1MB為一個分片 shardCount = Math.ceil(size / shardSize); //總片數 let attr = []; try { for (let item = 0; item < shardCount; ++item) { await fn(item); //同步 // attr.push(fn(item)); //異步 } await Promise.all(attr); //異步 $('.jdb').append(' 上傳成功'); var d2 = new Date(); console.log(parseInt(d2 - d1) / 1000); } catch (err) { $('.jdb').html(err); console.log(err); } function fn(item) { return new Promise((resolve, reject) => { var i = item; var start = i * shardSize, //當前分片開始下標 end = Math.min(size, start + shardSize); //結束下標 //構造一個表單,FormData是HTML5新增的 var form = new FormData(); form.append("data", file.slice(start, end)); //slice方法用於切出文件的一部分 form.append("name", name); //文件名字 form.append("total", shardCount); //總片數 form.append("index", i + 1); //當前片數 //Ajax提交 $.ajax({ url: "/sliceUpload", type: "POST", data: form, timeout: 120 * 1000, async: false, //同步 processData: false, //很重要,告訴jquery不要對form進行處理 contentType: false, //很重要,指定為false才能形成正確的Content-Type success: function (data) { ++succeed; if (typeof (data) == 'string') { try { data = JSON.parse(data); console.log(data.msg); } catch (e) { console.log(data); } } //生成當前進度百分比 var jd = `${Math.round(succeed / shardCount * 100)}%`; $('.jdb').html(jd); /*如果是線上,去掉定時,直接callback(), 這樣寫是為方便,本地測試看到進度條變化 因為本地做上傳測試是秒傳,沒有時間等待*/ setTimeout(resolve, 50); } }); }) } }); }); </script> </html>
服務器端:
async function sliceUpload(req, res) { var fs = require('fs'); var multiparty = require('multiparty'); //文件上傳模塊 var form = new multiparty.Form(); //新建表單 //設置編輯 form.encoding = 'utf-8'; //設置文件存儲路徑 form.uploadDir = "temp/"; // "Uploads/"; //設置單文件大小限制 // form.maxFilesSize = 200 * 1024 * 1024; /*form.parse表單解析函數,fields是生成數組用獲傳過參數,files是bolb文件名稱和路徑*/ try { let [fields, files] = await new Promise((resolve, reject) => { form.parse(req, (err, fields, files) => { if (err) reject('test err'); resolve([fields, files]); }) }) files = files['data'][0]; //獲取bolb文件 var index = fields['index'][0]; //當前片數 var total = fields['total'][0]; //總片數 var name = fields['name'][0]; //文件名稱 var url = 'temp/' + name + index; //臨時bolb文件新名字 fs.renameSync(files.path, url); //修改臨時文件名字 var pathname = 'Uploads/' + name; //上傳文件存放位置和名稱 if (index == total) { //當最后一個分片上傳成功,進行合並 // 檢查文件是存在,如果存在,重新設置名稱 let NonExist = await new Promise((resolve, reject) => { fs.access(pathname, fs.F_OK, (err) => { resolve(err); }); }) if (!NonExist) { var myDate = Date.now(); pathname = 'Uploads/' + myDate + name; } logs.info('上傳文件:' + pathname); /*進行合並文件,先創建可寫流,再把所有BOLB文件讀出來, 流入可寫流,生成文件 fs.createWriteStream創建可寫流 aname是存放所有生成bolb文件路徑數組: ['Uploads/3G.rar1','Uploads/3G.rar2',...] */ var writeStream = fs.createWriteStream(pathname); var aname = []; for (let i = 1; i <= total; i++) { let url = 'temp/' + name + i; let data = await new Promise(function (resolve, reject) { fs.readFile(url, function (error, data) { if (error) reject(error); resolve(data); }); }); //把數據寫入流里 writeStream.write(data); //刪除生成臨時bolb文件 fs.unlink(url, () => {}); } writeStream.end(); //返回給客服端,上傳成功 var data = JSON.stringify({ 'code': 0, 'msg': '上傳成功' }); res.send(data); //返回數據 } else { //還沒有上傳文件,請繼續上傳 var data = JSON.stringify({ 'code': 1, 'msg': '繼續上傳' }); res.send(data); //返回數據 } } catch (e) { logs.info(e); res.send(e); //返回數據 } }