当前位置: 首页 > news >正文

大文件分片上传

分片:

      // 获取文件对象const inputFile = document.querySelector('input[type="file"]');// 设置分片大小:5MBconst CHUNK_SIZE = 5 * 1024 * 1024;// 文件上传事件inputFile.onchange = async (e) => {// 获取文件信息const file = e.target.files[0];// 获取文件分片信息const chunks = await cutFile(file, CHUNK_SIZE);};// 获取文件所有分片信息async function cutFile(file, chunkSize = 5 * 1024 * 1024) {// 计算分片数量:文件大小除以分片大小然后在向上取整const chunkCount = Math.ceil(file.size / chunkSize);// 获取所有分片信息let chunks = [];for (let i = 0; i < chunkCount; i++) {// 获取单个分片信息const chunk = await createChunk(file, i, chunkSize);chunks.push(chunk);}return chunks;}// 获取文件单个分片信息import SparkMD5 from "sparkmd5.js"; // 计算hash值的第三方库async function createChunk(file, index, chunkSize = 5 * 1024 * 1024) {return new Promise((resolve) => {const start = index * chunkSize; // 当前分片的起始字节位置const end = Math.min(start + chunkSize, file.size); // 当前分片的结尾字节位置const blob = file.slice(start, end); // 分片内容const spark = new SparkMD5.ArrayBuffer(); // 创建处理hash值的实例对象,使用二进制数据获取MD5的值const fileReader = new FileReader(); // 创建浏览器内置的文件读取器,将二进制片段读取成内存中的数据// 绑定文件读取器,读取完成事件fileReader.onload = (e) => {spark.append(e.target.result); // 将二进制数据的结果追加到MD5的计算缓存中resolve({index,start,end,blob,hash: spark.end() // 计算并返回当前分片的hash值});};// 文件读取器开始读取分片数据fileReader.readAsArrayBuffer(blob);});}

优化:由于计算hash值是一个运算过程(CPU 密集型任务)很耗时并且js是单线程语言,所以必须要算完一个分片后 CPU 存在空闲才能去算下一个分片的 hash 值。

  • 如何解决:IO密集型操作可以用并发处理(promise.all),CPU密集型任务只能开多线程优化。
// 获取计算机内核数量 - 线程数量const THREAD_COUNT = navigator.hardwareConcurrency || 4;// 获取文件所有分片信息async function cutFile(file, chunkSize = 5 * 1024 * 1024) {return new Promise((resolve) => {// 计算分片数量:文件大小除以分片大小然后在向上取整const chunkCount = Math.ceil(file.size / chunkSize);// 计算每个线程可以分配分片的数量:总的分片数量除以总的线程数量然后在向上取整const threadChunkCount = Math.ceil(chunkCount / THREAD_COUNT);// 开启其他线程任务const result = [];let finishCount = 0;for (let i = 0; i < THREAD_COUNT; i++) {// 开启其他线程,并设置线程是一个module模块,支持导入const worker = new Worker("./worker.js", { type: "module" });// 向其他线程传递消息const start = i * threadChunkCount;const end = Math.min(start + threadChunkCount, chunkCount);worker.postMessage({file,start,end,chunkSize});// 从其他线程获取消息,确保接收到的信息顺序是正确的,则使用下标接收信息worker.onmessage = (e) => {worker.terminate(); // 结束当前线程result[i] = e.data; // 当前线程内的所有分片信息都保存起来finishCount++;// 当其他线程全部结束则返回所有分片信息,扁平化数组if (finishCount === THREAD_COUNT) resolve(result.flat());};}});}// 其他线程内部执行的内容:worker.js中onmessage = async (e) => {const { file, start, end, chunkSize } = e.data; // 获取主线程传递的消息// 获取当前线程内部的所有分片信息let result = [];for (let i = 0; i < end; i++) {// 获取单个分片信息const prom = createChunk(file, i, chunkSize);result.push(prom);}const chunks = await Promise.all(result); // 等待分片信息全部生成完成postMessage(chunks); // 向主线程传递当前线程分片信息};

断点续传:在本地存储中保存已上传的信息,下次重新上传的时候去检查本地记录,存在则跳过当前分片

      async function resumeUpload(file) {const chunkSize = 5 * 1024 * 1024;const totalChunks = Math.ceil(file.size / chunkSize);const fileId = generateFileId(file.name, file.size);// 1. 首先检查本地存储的上传记录let uploadedChunks = getLocalUploadProgress(fileId) || [];// 2. 如果本地没有记录,再请求服务器验证if (uploadedChunks.length === 0) {try {const response = await axios.get(`/upload-status?fileId=${fileId}`);uploadedChunks = response.data.uploadedChunks || [];// 将服务器记录保存到本地saveLocalUploadProgress(fileId, uploadedChunks);} catch (error) {console.error("获取上传状态失败,从头开始上传", error);uploadedChunks = [];}}// 3. 上传缺失的分片for (let i = 0; i < totalChunks; i++) {if (uploadedChunks.includes(i)) {console.log(`分片 ${i} 已上传,跳过`);continue;}const chunk = file.slice(i * chunkSize, Math.min(file.size, (i + 1) * chunkSize));const formData = createFormData(chunk, i, totalChunks, fileId);try {await axios.post("/upload-chunk", formData);// 更新本地记录uploadedChunks.push(i);saveLocalUploadProgress(fileId, uploadedChunks);} catch (error) {console.error(`分片 ${i} 上传失败:`, error);// 保留已成功上传的分片记录throw error;}}// 4. 所有分片上传完成后合并await axios.post("/merge-chunks", { fileId, fileName: file.name });// 清除本地记录clearLocalUploadProgress(fileId);}

秒传:通过接口传入 hash 值判断是否服务器是否存在文件,存在就复用分片

      async function quickUpload(file) {// 检查服务器是否已有该文件const { exists } = await axios.post("/check-file", {fileName: file.name,fileSize: file.size,fileHash});if (exists) {console.log("文件已存在,秒传成功");return { skipped: true };}// 不存在则正常上传return uploadFile(file, fileHash);}
http://www.hskmm.com/?act=detail&tid=8621

相关文章:

  • 人小鼠免疫细胞maker基因 - un
  • HyperWorks许可配置
  • 国标GB28181视频平台EasyGBS如何解决安防视频融合与级联管理的核心痛点?
  • python基础-推导式
  • 人 CD 抗原完全指南 - un
  • Java入门知识
  • AUTOSAR网络管理
  • 写用例注意点
  • 12 路低延迟推流!米尔 RK3576 赋能智能安防 360 环视
  • A公司一面:类加载的过程是怎么样的? 双亲委派的优点和缺点? 产生fullGC的情况有哪些? spring的动态代理有哪些?区别是什么? 如何排查CPU使用率过高?
  • Alternating Subsequence
  • 白鲸开源“创客北京2025”再摘殊荣,聚焦Agentic AI时代数据基础设施建设
  • python基础-公共操作
  • 天翼云第九代弹性云主机:让每一次计算快人一步
  • 若依(RuoYi)框架漏洞总结
  • 第一次个人项目作业_论文查重
  • 2025年版《中科院期刊分区表》与2023年版对比表,附名单可直接查阅
  • 对马岛之魂
  • 2019年双因素认证最佳实践指南
  • Account Kit(华为账号服务)再进化,开发者接入效率飙升!
  • Codeforces Round 1051 (Div. 2) D题启发(DP
  • Ubuntu 22 下 DolphinScheduler 3.x 伪集群部署实录
  • 关于proxmox 制作虚拟机模板的动态dhcp问题
  • Oracle清理:如何安全删除trace, alert和archivelog文件?
  • 软件工程个人项目
  • 学习道路道阻且长 希望自己坚持下去
  • 2025/9/18 总结
  • P2216 [HAOI2007] 理想的正方形
  • PuTTY下载和安装
  • 数据通路-单总线结构(最头晕的一集)