前端 :
<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); //返回数据 } }