JavaScript实现大文件分片上传处理
更新时间:2021年8月2日 08:24 点击:2706
很多时候我们在处理文件上传时,如视频文件,小则几十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'); })
以上就是文件分片上传的基本过程,过程中加入了上传进度条、暂停和开始上传操作,见详细代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持猪先飞。
上一篇: jquery实现员工管理注册页面
下一篇: 原生js实现拼图效果
相关文章
- 本篇文章主要分享了通过window.navigator来判断浏览器及其版本信息的实例代码。具有一定的参考价值,下面跟着小编一起来看下吧...2017-01-23
- 这篇文章主要介绍了js如何实现浏览器打印功能,文中示例代码非常详细,帮助大家更好的理解和学习,感兴趣的朋友可以了解下...2020-07-15
- 本文章来人大家介绍一个php文件上传类的使用方法,期望此实例对各位php入门者会有不小帮助哦。 简介 Class.upload.php是用于管理上传文件的php文件上传类, 它可以帮...2016-11-25
- 这篇文章主要给大家介绍了关于Nest.js参数校验和自定义返回数据格式的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-28
- 又码了一个周末的代码,这次在做一些关于文件上传的东西。(PHP UPLOAD)小有收获项目是一个BT种子列表,用户有权限上传自己的种子,然后配合BT TRACK服务器把种子的信息写出来...2016-11-25
- 下面小编就为大家带来一篇利用JS实现点击按钮后图片自动切换的简单方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2016-10-25
- 本文实例讲述了jQuery实现文件上传进度条效果的代码。分享给大家供大家参考。具体如下: 运行效果截图如下:具体代码如下:<!DOCTYPE html><html><head><meta charset="utf-8"><title>upload</title><link rel="stylesheet...2015-11-24
- 作为前端,一直以来都知道HTTP劫持与XSS跨站脚本、CSRF跨站请求伪造。防御这些劫持最好的方法是从后端入手,前端能做的太少。而且由于源码的暴露,攻击者很容易绕过防御手段。但这不代表我们去了解这块的相关知识是没意义的,本文的许多方法,用在其他方面也是大有作用。...2021-05-24
- 本篇文章主要说明的是与php文件上传的相关配置的知识点。PHP文件上传功能配置主要涉及php.ini配置文件中的upload_tmp_dir、upload_max_filesize、post_max_size等选项,下面一一说明。打开php.ini配置文件找到File Upl...2015-10-21
- 这篇文章主要介绍了js实现调用网络摄像头及常见错误处理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-07
- EXCEL数据上传到SQL SERVER中的方法需要注意到三点!注意点一:要把EXCEL数据上传到SQL SERVER中必须提前把EXCEL传到服务器上.做法: 在ASP.NET环境中,添加一个FileUpload上传控件后台代码的E.X: 复制代码 代码如下: if...2013-09-23
- 这篇文章主要为大家详细介绍了JS实现随机生成验证码,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-09-06
- 这篇文章主要介绍了js组件SlotMachine实现图片切换效果制作抽奖系统的相关资料,需要的朋友可以参考下...2016-04-19
- Vue.js通过简洁的API提供高效的数据绑定和灵活的组件系统.这篇文章主要介绍了vue.js 表格分页ajax 异步加载数据的相关资料,需要的朋友可以参考下...2016-10-20
- 这篇文章主要介绍了基于JavaScript实现文字超出部分隐藏 的相关资料,需要的朋友可以参考下...2016-03-01
- 这篇文章主要为大家详细介绍了js实现列表按字母排序,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-08-11
- 这篇文章主要为大家详细介绍了js实现上传图片及时预览的相关资料,具有一定的参考价值,感兴趣的朋友可以参考一下...2016-05-09
- 这篇文章主要介绍了JS实现响应鼠标点击动画渐变弹出层效果代码,具有非常自然流畅的动画过度效果,涉及JavaScript针对鼠标事件的响应及页面元素样式的动态操作相关技巧,需要的朋友可以参考下...2016-03-28
- 本文给大家介绍的是nodejs实现使用阿里大鱼短信API发送消息的方法和代码,有需要的小伙伴可以参考下。...2016-01-20
- 系统的学习了一下angularjs,发现angularjs的有些思想根php的模块smarty很像,例如数据绑定,filter。如果对smarty比较熟悉的话,学习angularjs会比较容易一点,这篇文章给大家介绍angularjs filter用法详解,感兴趣的朋友一起学习吧...2015-12-29