首页 > 编程语言 >JavaScript实现大文件分片上传处理

JavaScript实现大文件分片上传处理

时间:2023-10-08 18:23:03浏览次数:32  
标签:文件 const JavaScript file 分片 hash 上传

很多时候我们在处理文件上传时,如视频文件,小则几十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');
})

以上就是文件分片上传的基本过程

 

参考文章:http://blog.ncmem.com/wordpress/2023/10/08/javascript%e5%ae%9e%e7%8e%b0%e5%a4%a7%e6%96%87%e4%bb%b6%e5%88%86%e7%89%87%e4%b8%8a%e4%bc%a0%e5%a4%84%e7%90%86-2/

欢迎入群一起讨论

 

 

标签:文件,const,JavaScript,file,分片,hash,上传
From: https://www.cnblogs.com/songsu/p/17749835.html

相关文章

  • js+php分片上传大文件
    1.理解部分 服务端为什么不能直接传大文件?跟php.ini里面的几个配置有关upload_max_filesize=2M//PHP最大能接受的文件大小post_max_size=8M//PHP能收到的最大POST值'memory_limit=128M//内存上限max_execution_time=30//最大执行时间当然不能简单粗暴的把上面......
  • JavaScript实现大文件分片上传处理
    我可以为你讲解如何实现JavaScript实现大文件分片上传处理,以下是具体的攻略步骤:步骤1:选择文件在实现大文件分片上传之前,第一步需要让用户选择一个文件。你可以在页面上加入一个文件选择表单,如下所示:<inputtype="file"name="file"id="file">步骤2:对文件进行分片处理当用户......
  • js实现文件分片上传
    <!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><title>JS分片上传-极速上传</title></head><body><inputtype="file"name="slice"id="slice&......
  • 微信小程序的附件上传与预览
    微信小程序的附件上传与预览文件与图片上传wx.chooseMessageFile({count:10,type:'file',success(res){//tempFilePath可以作为img标签的src属性显示图consttempFilePaths=res.tempFiles;varwjpath=tempFilePat......
  • JavaScript(二)--DOM
    DOM浏览器对象模型window对象是一个全局对象var定义的全局作用域中的变量、函数都会成为window对象的属性和方法调用时可省window 延时函数lettimer=setTimeout(回调函数,等待毫秒数)clearTImeout(timer)JS执行机制单线程(同一时间只能做一件事)H5提出新标准,允许js创建......
  • 大文件上传和下载解决方案
    前言前端处理“大”一直是一个痛点和难点,比如大文件、大数据量。虽然浏览器硬件有限,但是聪明的工程师总是能够最大化利用浏览器的能力和特性,优雅的解决一个个极端问题,满足用户的多样化需求。断点上传对于大文件,如果我们直接上传,用户网速够慢的话,可能需要等上几天几夜才能上传完......
  • 如何实现大文件上传
    一、解决方案既然大文件上传不适合一次性上传,那么将文件分片散上传是不是就能减少性能消耗了。分片上传就是将大文件分成一个个小文件(切片),将切片进行上传,等到后端接收到所有切片,再将切片合并成大文件。通过将大文件拆分成多个小文件进行上传,确实就是解决了大文件上传的问题。因为......
  • javascript比较字符串大小
    https://blog.csdn.net/first_shun/article/details/108186675使用js进行sort排序的时候比较字符串用了使用localeCompare方法a.localeCompare(b)//-101......
  • 第一次git上传的完整流程
    第一次git上传的完整流程使用git简单命令上传代码push到远程仓库+简单介绍了一个.git文件结构。代码上传到gitee和github流程一样的,不过你上传到github可能网不行失败,所以我们使用gitee*前置说明你必须有git工具并且配置了环境变量。配置环境变量使用git会更方便。测......
  • Salesforce input 标签 成熟文件上传方案,
    这次项目开发由于客户要求无法使用lwc标准的文件上传标签只能只能使用input标签来上传文件。下面是试验成功的方案。注意:只能上传3mb文件,salesforce限制。也希望看到的文章大神们指点一下。前端html<inputautocomplete="off"style="padding:0;......