JavaScript实现大文件分片上传处理
程序员文章站
2022-06-22 10:05:54
很多时候我们在处理文件上传时,如视频文件,小则几十m,大则 1g+,以一般的http请求发送数据的方式的话,会遇到的问题:1、文件过大,超出服务端的请求大小限制;2、请求时间过长,请求超时;3、传输中...
很多时候我们在处理文件上传时,如视频文件,小则几十m,大则 1g+,以一般的http请求发送数据的方式的话,会遇到的问题:
1、文件过大,超出服务端的请求大小限制;
2、请求时间过长,请求超时;
3、传输中断,必须重新上传导致前功尽弃
这些问题很影响用户的体验感,所以下面介绍一种基于原生javascript进行文件分片处理上传的方案,具体实现过程如下:
1、通过dom获取文件对象,并且对文件进行md5加密(文件内容+文件标题形式),采用sparkmd5进行文件加密;
2、进行分片设置,文件file基于blob, 继承了blob的功能,可以把file当成blob的子类,利于blob的slice方法进行文件分片处理,并且依次进行上传
3、分片文件上传完成后,请求合并接口后端进行文件合并处理即可
1. 上传文件页面
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="x-ua-compatible" content="ie=edge"> <title>文件上传</title> <script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script> <script src="https://code.jquery.com/jquery-3.4.1.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/spark-md5/3.0.0/spark-md5.js"></script> <style> /* 自定义进度条样式 */ .precent input[type=range] { -webkit-appearance: none; /*清除系统默认样式*/ width: 7.8rem; /* background: -webkit-linear-gradient(#ddd, #ddd) no-repeat, #ddd; */ /*设置左边颜色为#61bd12,右边颜色为#ddd*/ background-size: 75% 100%; /*设置左右宽度比例*/ height: 0.6rem; /*横条的高度*/ border-radius: 0.4rem; border: 1px solid #ddd; box-shadow: 0 0 10px rgba(0,0,0,.125) inset ; } /*拖动块的样式*/ .precent input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; /*清除系统默认样式*/ height: .9rem; /*拖动块高度*/ width: .9rem; /*拖动块宽度*/ background: #fff; /*拖动块背景*/ border-radius: 50%; /*外观设置为圆形*/ border: solid 1px #ddd; /*设置边框*/ } </style> </head> <body> <h1>大文件分片上传测试</h1> <div> <input id="file" type="file" name="avatar" /> <div style="padding: 10px 0;"> <input id="submitbtn" type="button" value="提交" /> <input id="pausebtn" type="button" value="暂停" /> </div> <div class="precent"> <input type="range" value="0" /><span id="precentval">0%</span> </div> </div> <script type="text/javascript" src="./js/index.js"></script> </body> </html>
2. 大文件分片上传处理
$(document).ready(() => { const submitbtn = $('#submitbtn'); //提交按钮 const precentdom = $(".precent input")[0]; // 进度条 const precentval = $("#precentval"); // 进度条值对应dom const pausebtn = $('#pausebtn'); // 暂停按钮 // 每个chunk的大小,设置为1兆 const chunksize = 1 * 1024 * 1024; // 获取slice方法,做兼容处理 const blobslice = file.prototype.slice || file.prototype.mozslice || file.prototype.webkitslice; // 对文件进行md5加密(文件内容+文件标题形式) const hashfile = (file) => { return new promise((resolve, reject) => { const chunks = math.ceil(file.size / chunksize); let currentchunk = 0; const spark = new sparkmd5.arraybuffer(); const filereader = new filereader(); function loadnext() { const start = currentchunk * chunksize; const end = start + chunksize >= file.size ? file.size : start + chunksize; filereader.readasarraybuffer(blobslice.call(file, start, end)); } filereader.onload = e => { spark.append(e.target.result); // append array buffer currentchunk += 1; if (currentchunk < chunks) { loadnext(); } else { console.log('finished loading'); const result = spark.end(); // 通过内容和文件名称进行md5加密 const sparkmd5 = new sparkmd5(); sparkmd5.append(result); sparkmd5.append(file.name); const hexhash = sparkmd5.end(); resolve(hexhash); } }; filereader.onerror = () => { console.warn('文件读取失败!'); }; loadnext(); }).catch(err => { console.log(err); }); } // 提交 submitbtn.on('click', async () => { var pausestatus = false; var nowuploadnums = 0 // 1.读取文件 const filedom = $('#file')[0]; const files = filedom.files; const file = files[0]; if (!file) { alert('没有获取文件'); return; } // 2.设置分片参数属性、获取文件md5值 const hash = await hashfile(file); //文件 hash const blockcount = math.ceil(file.size / chunksize); // 分片总数 const axiospromisearray = []; // axiospromise数组 // 文件上传 const uploadfile = () => { const start = nowuploadnums * chunksize; const end = math.min(file.size, start + chunksize); // 构建表单 const form = new formdata(); // blobslice.call(file, start, end)方法是用于进行文件分片 form.append('file', blobslice.call(file, start, end)); form.append('index', nowuploadnums); form.append('hash', hash); // ajax提交 分片,此时 content-type 为 multipart/form-data const axiosoptions = { onuploadprogress: e => { nowuploadnums++; // 判断分片是否上传完成 if (nowuploadnums < blockcount) { setprecent(nowuploadnums, blockcount); uploadfile(nowuploadnums) } else { // 4.所有分片上传后,请求合并分片文件 axios.all(axiospromisearray).then(() => { setprecent(blockcount, blockcount); // 全部上传完成 axios.post('/file/merge_chunks', { name: file.name, total: blockcount, hash }).then(res => { console.log(res.data, file); pausestatus = false; alert('上传成功'); }).catch(err => { console.log(err); }); }); } }, }; // 加入到 promise 数组中 if (!pausestatus) { axiospromisearray.push(axios.post('/file/upload', form, axiosoptions)); } } // 设置进度条 function setprecent(now, total) { var prencentvalue = ((now / total) * 100).tofixed(2) precentdom.value = prencentvalue precentval.text(prencentvalue + '%') precentdom.style.csstext = `background:-webkit-linear-gradient(top, #059cfa, #059cfa) 0% 0% / ${prencentvalue}% 100% no-repeat` } // 暂停 pausebtn.on('click', (e) => { pausestatus = !pausestatus; e.currenttarget.value = pausestatus ? '开始' : '暂停' if (!pausestatus) { uploadfile(nowuploadnums) } }) uploadfile(); }); })
3. 文件上传和合并分片文件接口(node)
const router = require('koa-router'); const multer = require('koa-multer'); const fs = require('fs-extra'); const path = require('path'); const router = new router(); const { mkdirssync } = require('../utils/dir'); const uploadpath = path.join(__dirname, 'upload'); const chunkuploadpath = path.join(uploadpath, 'temp'); const upload = multer({ dest: chunkuploadpath }); // 文件上传接口 router.post('/file/upload', upload.single('file'), async (ctx, next) => { const { index, hash } = ctx.req.body; const chunkspath = path.join(chunkuploadpath, hash, '/'); if(!fs.existssync(chunkspath)) mkdirssync(chunkspath); fs.renamesync(ctx.req.file.path, chunkspath + hash + '-' + index); ctx.status = 200; ctx.res.end('success'); }) // 合并分片文件接口 router.post('/file/merge_chunks', async (ctx, next) => { const { name, total, hash } = ctx.request.body; const chunkspath = path.join(chunkuploadpath, hash, '/'); const filepath = path.join(uploadpath, name); // 读取所有的chunks const chunks = fs.readdirsync(chunkspath); // 创建存储文件 fs.writefilesync(filepath, ''); if(chunks.length !== total || chunks.length === 0) { ctx.status = 200; ctx.res.end('切片文件数量不符合'); return; } for (let i = 0; i < total; i++) { // 追加写入到文件中 fs.appendfilesync(filepath, fs.readfilesync(chunkspath + hash + '-' +i)); // 删除本次使用的chunk fs.unlinksync(chunkspath + hash + '-' +i); } fs.rmdirsync(chunkspath); // 文件合并成功,可以把文件信息进行入库。 ctx.status = 200; ctx.res.end('success'); })
以上就是文件分片上传的基本过程,过程中加入了上传进度条、暂停和开始上传操作,见
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
下一篇: Java实现简单聊天机器人