vue大文件上传的断点续传功能实现与优化策略

大文件上传解决方案

各位同行大佬们好,作为一个在广东摸爬滚打多年的前端"老油条",最近接了个让我差点秃顶的项目——20G大文件上传系统,还要兼容IE9!这感觉就像让我用竹篮子去打水还要不漏一样刺激…

需求分析:客户这是要我老命啊

  • 20G大文件传输:我寻思着这不是上传,这是在往浏览器里塞一头大象啊
  • 文件夹保留层级:客户说文件夹里有1000个分类文件,这哪是文件夹,这是个文件博物馆!
  • 加密传输存储:SM4、AES齐上阵,比瑞士银行的保险箱还严实
  • 断点续传:关了浏览器、重启电脑都不能丢进度,这要求比我的记忆力靠谱多了
  • 非打包下载:几万个文件直接下载,这网速得比我的工资涨得还快才行
  • 兼容IE9:Windows7+IE9的组合,让我梦回2012年,青春啊!

最绝的是预算100元以内还要求7×24小时支持,这价格连我家的路由器月租都不够啊兄弟们!

前端解决方案:与IE9的世纪和解

既然客户爸爸说了要用原生JS,那咱们就用H5的File API+IndexedDB来整活:

穷逼版大文件上传 /* 祖传CSS,兼容IE9 */ .upload-area { border: 2px dashed #ccc; padding: 20px; text-align: center; margin: 20px; background: #f9f9f9; } .progress-container { width: 100%; background-color: #f5f5f5; margin: 10px 0; height: 20px; position: relative; } .progress-bar { height: 100%; background-color: #4CAF50; width: 0%; transition: width 0.3s; } .progress-text { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); color: #333; font-size: 12px; } .file-item { margin: 10px 0; padding: 10px; border: 1px solid #eee; background: #fff; } .file-name { font-weight: bold; } .file-path { color: #666; font-size: 0.9em; margin-left: 10px; } .file-size { color: #888; font-size: 0.9em; margin-left: 10px; } .file-controls { margin-top: 5px; } button { padding: 5px 10px; margin-right: 5px; background: #f0f0f0; border: 1px solid #ddd; cursor: pointer; } button:hover { background: #e0e0e0; } 大文件上传(兼容IE9版) 拖放文件或文件夹到此处 或 选择文件/文件夹 等待上传文件... 开始上传 暂停 继续 加密方式: SM4国密 AES-256 // 上传队列 var uploadQueue = []; var currentUpload = null; var chunkSize = 5 * 1024 * 1024; // 5MB分块 // 初始化 function init() { // 文件选择处理 document.getElementById('fileInput').addEventListener('change', function(e) { handleFiles(e.target.files); }); // 拖放处理 var dropArea = document.getElementById('dropArea'); dropArea.addEventListener('dragover', function(e) { e.preventDefault(); dropArea.style.borderColor = '#4CAF50'; dropArea.style.background = '#f0fff0'; }); dropArea.addEventListener('dragleave', function() { dropArea.style.borderColor = '#ccc'; dropArea.style.background = '#f9f9f9'; }); dropArea.addEventListener('drop', function(e) { e.preventDefault(); dropArea.style.borderColor = '#ccc'; dropArea.style.background = '#f9f9f9'; handleFiles(e.dataTransfer.files); }); // 加载未完成的传输 loadPendingTransfers(); } // 处理文件选择 function handleFiles(files) { var queueContainer = document.getElementById('uploadQueue'); queueContainer.innerHTML = ''; for (var i = 0; i < files.length; i++) { var file = files[i]; addFileToQueue(file); } } // 添加文件到队列 function addFileToQueue(file) { var fileItem = { id: generateFileId(file), name: file.name, path: file.webkitRelativePath || '', size: file.size, progress: 0, status: 'pending', file: file }; uploadQueue.push(fileItem); renderQueue(); } // 渲染队列 function renderQueue() { var queueContainer = document.getElementById('uploadQueue'); queueContainer.innerHTML = ''; if (uploadQueue.length === 0) { queueContainer.innerHTML = '<p>等待上传文件...</p>'; return; } for (var i = 0; i < uploadQueue.length; i++) { var item = uploadQueue[i]; var itemElement = document.createElement('div'); itemElement.className = 'file-item'; itemElement.innerHTML = `<div><spanclass="file-name">${item.name}</span><spanclass="file-path">${item.path}</span><spanclass="file-size">${formatFileSize(item.size)}</span></div><divclass="progress-container"><divclass="progress-bar"style="width:${item.progress}%"></div><spanclass="progress-text">${item.progress}%</span></div><divclass="file-controls"><button onclick="pauseItem('${item.id}')" ${item.status !== 'uploading' ? 'disabled' : ''}>暂停</button><button onclick="resumeItem('${item.id}')" ${item.status !== 'paused' ? 'disabled' : ''}>继续</button><buttononclick="cancelItem('${item.id}')">取消</button></div>`; queueContainer.appendChild(itemElement); } } // 开始上传 function startUpload() { if (uploadQueue.length === 0) return; currentUpload = uploadQueue.find(item => item.status === 'pending'); if (currentUpload) { currentUpload.status = 'uploading'; uploadFile(currentUpload); } } // 上传文件 function uploadFile(fileItem) { var file = fileItem.file; var totalChunks = Math.ceil(file.size / chunkSize); // 从本地存储加载断点 var resumeChunk = localStorage.getItem('resume_' + fileItem.id) || 0; // 上传分块 for (var chunkIndex = resumeChunk; chunkIndex < totalChunks; chunkIndex++) { if (fileItem.status === 'paused') break; var start = chunkIndex * chunkSize; var end = Math.min(start + chunkSize, file.size); var chunk = file.slice(start, end); var formData = new FormData(); formData.append('fileId', fileItem.id); formData.append('chunkIndex', chunkIndex); formData.append('totalChunks', totalChunks); formData.append('fileName', fileItem.name); formData.append('filePath', fileItem.path); formData.append('fileSize', fileItem.size); formData.append('chunkData', chunk); formData.append('encryption', document.getElementById('encryptionType').value); formData.append('encryptionKey', document.getElementById('encryptionKey').value); // AJAX上传(兼容IE9) var xhr = new XMLHttpRequest(); xhr.open('POST', '/api/upload/chunk', false); // 同步上传 xhr.upload.onprogress = function(e) { var loaded = chunkIndex * chunkSize + e.loaded; fileItem.progress = Math.round((loaded / fileItem.size) * 100); renderQueue(); }; xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if (xhr.status === 200) { // 保存断点 localStorage.setItem('resume_' + fileItem.id, chunkIndex + 1); if (chunkIndex === totalChunks - 1) { // 合并文件 mergeFile(fileItem.id); fileItem.status = 'completed'; startUpload(); // 开始下一个文件 } } else { console.error('上传失败:', xhr.responseText); fileItem.status = 'error'; renderQueue(); } } }; try { xhr.send(formData); } catch (e) { console.error('上传出错:', e); fileItem.status = 'error'; renderQueue(); break; } } } // 暂停上传 function pauseUpload() { if (currentUpload) { currentUpload.status = 'paused'; renderQueue(); } } // 继续上传 function resumeUpload() { if (currentUpload && currentUpload.status === 'paused') { currentUpload.status = 'uploading'; uploadFile(currentUpload); } } // 暂停单个项目 function pauseItem(fileId) { var item = uploadQueue.find(item => item.id === fileId); if (item) { item.status = 'paused'; renderQueue(); } } // 继续单个项目 function resumeItem(fileId) { var item = uploadQueue.find(item => item.id === fileId); if (item && item.status === 'paused') { item.status = 'uploading'; uploadFile(item); } } // 取消单个项目 function cancelItem(fileId) { var index = uploadQueue.findIndex(item => item.id === fileId); if (index >= 0) { // 通知后端取消上传 cancelUpload(fileId); // 从队列移除 uploadQueue.splice(index, 1); renderQueue(); } } // 合并文件 function mergeFile(fileId) { var xhr = new XMLHttpRequest(); xhr.open('POST', '/api/upload/merge', false); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.onreadystatechange = function() { if (xhr.readyState === 4 && xhr.status === 200) { console.log('文件合并成功:', xhr.responseText); localStorage.removeItem('resume_' + fileId); } }; xhr.send('fileId=' + encodeURIComponent(fileId) + '&encryption=' + encodeURIComponent(document.getElementById('encryptionType').value)); } // 取消上传 function cancelUpload(fileId) { var xhr = new XMLHttpRequest(); xhr.open('POST', '/api/upload/cancel', false); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.send('fileId=' + encodeURIComponent(fileId)); } // 加载未完成的传输 function loadPendingTransfers() { var xhr = new XMLHttpRequest(); xhr.open('GET', '/api/upload/pending', false); xhr.onreadystatechange = function() { if (xhr.readyState === 4 && xhr.status === 200) { var pendingFiles = JSON.parse(xhr.responseText); pendingFiles.forEach(function(fileInfo) { uploadQueue.push({ id: fileInfo.fileId, name: fileInfo.fileName, path: fileInfo.filePath, size: fileInfo.fileSize, progress: fileInfo.progress, status: 'paused' }); }); renderQueue(); } }; xhr.send(); } // 生成文件ID function generateFileId(file) { return file.name + '_' + file.size + '_' + file.lastModified; } // 格式化文件大小 function formatFileSize(bytes) { if (bytes === 0) return '0 B'; var k = 1024; var sizes = ['B', 'KB', 'MB', 'GB', 'TB']; var i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } // 初始化 window.onload = init;

前端关键功能说明

1. 兼容IE9的黑魔法

  • 使用条件注释只对IE9及以下浏览器加载polyfill
  • json2.js解决IE9没有JSON对象的问题
  • promise-polyfill解决IE9不支持Promise的问题

2. 文件夹上传核心代码

// 文件选择元素添加webkitdirectory和directory属性// 处理文件时保留路径信息functionhandleFiles(files){for(vari=0;i<files.length;i++){varfile=files[i];varfileItem={name:file.name,path:file.webkitRelativePath||'',// 保留相对路径size:file.size// ...};uploadQueue.push(fileItem);}}

3. 断点续传实现

// 上传前检查本地是否有断点记录varresumeChunk=localStorage.getItem('resume_'+fileItem.id)||0;// 上传成功后保存断点localStorage.setItem('resume_'+fileItem.id,chunkIndex+1);// 文件合并成功后清理断点localStorage.removeItem('resume_'+fileItem.id);

4. 加密传输(伪实现)

// 实际项目中应该使用Web Crypto API或相应库formData.append('encryption',document.getElementById('encryptionType').value);formData.append('encryptionKey',document.getElementById('encryptionKey').value);

如何与后端对接

需要后端提供的API接口

  1. 分块上传接口

    POST /api/upload/chunk 参数: - fileId: 文件唯一ID - chunkIndex: 当前分块索引 - totalChunks: 总分块数 - fileName: 文件名 - filePath: 文件相对路径(文件夹结构) - fileSize: 文件大小 - chunkData: 分块数据 - encryption: 加密算法 - encryptionKey: 加密密钥
  2. 合并文件接口

    POST /api/upload/merge 参数: - fileId: 文件唯一ID - encryption: 加密算法
  3. 取消上传接口

    POST /api/upload/cancel 参数: - fileId: 文件唯一ID
  4. 获取未完成任务接口

    GET /api/upload/pending 返回: [ { fileId: string, fileName: string, filePath: string, fileSize: number, progress: number } ]

部署注意事项

  1. IE9兼容性

    • 确保服务器正确设置X-UA-Compatible
    • 添加MIME类型.json application/json
  2. 大文件上传

    • 配置Nginx/Apache的上传大小限制
    • 设置PHP的upload_max_filesizepost_max_size
  3. 断点续传

    • 确保localStorage可用(IE8+支持)
    • 对于隐私模式,需要降级使用cookie存储
  4. 加密传输

    • 实际项目中应该使用HTTPS
    • 前端加密应该使用Web Crypto API或相应polyfill

最后吐槽

  1. 100块预算还要兼容IE9?甲方是不是对程序员有什么误解?

  2. 20G文件上传?建议先问问甲方他们服务器硬盘够不够大

  3. 7x24小时免费技术支持?我连7x24小时睡觉都保证不了…

  4. 要源代码?要文档?要一条龙服务?100块连个外卖都点不了好吗!

  5. 加群374992201领红包?兄弟,有这功夫不如多接几个项目…

不过既然你都看到这里了,代码拿去用吧,记得请我喝奶茶(至少得是喜茶级别的)!

将组件复制到项目中

示例中已经包含此目录

引入组件

配置接口地址

接口地址分别对应:文件初始化,文件数据上传,文件进度,文件上传完毕,文件删除,文件夹初始化,文件夹删除,文件列表
参考:http://www.ncmem.com/doc/view.aspx?id=e1f49f3e1d4742e19135e00bd41fa3de

处理事件

启动测试

启动成功

效果

数据库

效果预览

文件上传

文件刷新续传

支持离线保存文件进度,在关闭浏览器,刷新浏览器后进行不丢失,仍然能够继续上传

文件夹上传

支持上传文件夹并保留层级结构,同样支持进度信息离线保存,刷新页面,关闭页面,重启系统不丢失上传进度。

下载示例

点击下载完整示例

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1123701.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Cursor与VSCode效率对比:AI工具如何节省开发者时间

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 创建一个效率对比工具&#xff0c;测量Cursor和VSCode在以下任务中的耗时&#xff1a;1. 代码补全&#xff1b;2. 错误检测与修复&#xff1b;3. 代码重构&#xff1b;4. 项目导航…

迁移学习实战:冻结特征提取层训练分类头的全过程

迁移学习实战&#xff1a;冻结特征提取层训练分类头的全过程 万物识别-中文-通用领域&#xff1a;从开源模型到定制化推理 在计算机视觉领域&#xff0c;迁移学习已成为解决小样本图像分类任务的主流范式。尤其当目标数据集规模有限时&#xff0c;直接从零训练一个深度神经网络…

MFLAC在音乐流媒体平台的应用实践

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 创建一个模拟音乐流媒体平台的后端系统&#xff0c;专门处理MFLAC音频文件。功能要求&#xff1a;1. 用户认证系统&#xff1b;2. MFLAC文件上传和存储&#xff1b;3. 实时流媒体传…

食品营养成分估算:通过图像识别菜品类型

食品营养成分估算&#xff1a;通过图像识别菜品类型 引言&#xff1a;从“看图识物”到“看图知营养” 在智能健康与个性化饮食管理日益普及的今天&#xff0c;如何快速、准确地获取日常饮食中的营养信息成为一大挑战。传统方式依赖用户手动输入食物名称和分量&#xff0c;操作…

轻松部署腾讯混元翻译模型:Jupyter环境下的一键启动流程

腾讯混元翻译模型的极简部署实践&#xff1a;从零到翻译只需两分钟 在跨国协作日益频繁、多语言内容爆炸式增长的今天&#xff0c;企业与研究团队对高质量机器翻译的需求从未如此迫切。无论是跨境电商的商品描述本地化&#xff0c;还是民族语言文献的数字化保护&#xff0c;亦或…

vue大文件上传的切片上传与分块策略对比分析

前端老兵的20G文件夹上传血泪史&#xff08;附部分代码&#xff09; 各位前端同仁们好&#xff0c;我是老王&#xff0c;一个在福建靠写代码混口饭吃的"前端民工"。最近接了个奇葩项目&#xff0c;客户要求用原生JS实现20G文件夹上传下载&#xff0c;还要兼容IE9&am…

c#编程文档翻译推荐:Hunyuan-MT-7B-WEBUI精准转换技术术语

C#编程文档翻译推荐&#xff1a;Hunyuan-MT-7B-WEBUI精准转换技术术语 在企业级软件开发日益全球化的今天&#xff0c;一个现实问题摆在每个.NET团队面前&#xff1a;如何让中文撰写的C#技术文档被世界各地的开发者准确理解&#xff1f;尤其当项目涉及异步编程、委托事件机制或…

比手动快10倍!自动化解决PRINT SPOOLER问题

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 开发一个高效的PRINT SPOOLER问题自动化解决工具&#xff0c;要求&#xff1a;1. 在30秒内完成问题诊断&#xff1b;2. 提供一键修复功能&#xff1b;3. 自动备份关键系统配置&…

(6-3)自动驾驶中的全局路径精简计算:Floyd算法的改进

6.3 Floyd算法的改进Floyd算法是一种用于解决图中任意两点间最短路径问题的经典算法。为了提高其效率和性能&#xff0c;可以采用多种优化改进方式。其中包括空间优化、提前终止、并行化计算、路径记忆、稀疏图优化等。这些优化改进方式可以单独或组合使用&#xff0c;以适应不…

/root目录找不到1键启动.sh?文件缺失原因及修复方式

/root目录找不到1键启动.sh&#xff1f;文件缺失原因及修复方式 在部署AI模型时&#xff0c;最让人头疼的不是复杂的算法调优&#xff0c;而是卡在“第一步”——连服务都启动不了。最近不少用户反馈&#xff0c;在使用腾讯混元&#xff08;Hunyuan&#xff09;推出的 Hunyuan-…

新能源车充电桩状态识别:远程监控使用情况

新能源车充电桩状态识别&#xff1a;远程监控使用情况 随着新能源汽车保有量的快速增长&#xff0c;充电基础设施的智能化管理成为城市智慧交通系统的重要组成部分。在实际运营中&#xff0c;如何实时掌握充电桩的使用状态——是空闲、正在充电、故障还是被非电动车占用——直接…

白细胞介素4(IL-4)的生物学功能与检测应用

一、IL-4的基本特性与历史发展是什么&#xff1f; 白细胞介素4&#xff08;Interleukin-4&#xff0c;IL-4&#xff09;是趋化因子家族中的关键细胞因子&#xff0c;由活化的T细胞、嗜碱性粒细胞和肥大细胞等多种免疫细胞产生。其发现历史可追溯至1982年&#xff0c;Howard等研…

Hunyuan-MT-7B-WEBUI开发者文档编写规范

Hunyuan-MT-7B-WEBUI开发者文档编写规范 在当今全球化加速推进的背景下&#xff0c;跨语言沟通早已不再是少数领域的专属需求。从跨境电商到国际教育&#xff0c;从多语种内容平台到民族语言保护&#xff0c;高质量、低门槛的机器翻译能力正成为基础设施级的技术支撑。然而现实…

12GB显存也能玩:FluxGym镜像快速搭建物体识别训练环境

12GB显存也能玩&#xff1a;FluxGym镜像快速搭建物体识别训练环境 作为一名业余AI爱好者&#xff0c;我一直想尝试修改开源物体识别模型来满足自己的需求。但手头的显卡只有12GB显存&#xff0c;直接跑训练经常遇到显存不足的问题。直到发现了FluxGym这个优化过的训练环境镜像&…

每10分钟更新一次的实时卫星影像

我们在《重大发现&#xff01;竟然可以下载当天拍摄的卫星影像》一文中&#xff0c;为大家分享了一个可以查看下载高时效卫星影像的方法。 这里再为大家推荐一个可以查看近乎实时的卫星影像的网站&#xff0c;卫星影像每10分钟更新一次。 实时卫星影像 打开网站&#xff08;…

Hunyuan-MT-7B模型镜像下载地址分享(附一键启动脚本)

Hunyuan-MT-7B模型镜像下载地址分享&#xff08;附一键启动脚本&#xff09; 在多语言内容爆炸式增长的今天&#xff0c;一个能快速部署、开箱即用的高质量翻译系统&#xff0c;几乎成了科研、教育和企业出海场景中的“刚需”。然而现实却常令人头疼&#xff1a;大多数开源翻译…

Hunyuan-MT-7B-WEBUI pull request 审核流程

Hunyuan-MT-7B-WEBUI&#xff1a;如何让高性能翻译模型真正“用起来” 在企业全球化加速、跨语言协作日益频繁的今天&#xff0c;机器翻译早已不再是实验室里的概念玩具。从跨境电商的产品描述自动本地化&#xff0c;到科研团队处理多语种文献&#xff0c;再到边疆地区公共服务…

从需求到成品:智能轮椅开发实战记录

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 开发智能轮椅控制系统原型&#xff0c;功能要求&#xff1a;1. 基于Arduino的电机控制模块 2. 手机蓝牙控制界面 3. 障碍物检测预警 4. 速度调节功能 5. 电池状态监控。请生成包含…

揭秘MCP网络异常:如何快速定位并解决IP冲突难题

第一章&#xff1a;MCP网络异常概述 在现代分布式系统架构中&#xff0c;MCP&#xff08;Microservice Communication Protocol&#xff09;作为微服务间通信的核心协议&#xff0c;其稳定性直接影响系统的可用性与响应性能。当MCP网络出现异常时&#xff0c;通常表现为服务调用…

教学实践:用云端GPU带学生体验万物识别技术

教学实践&#xff1a;用云端GPU带学生体验万物识别技术 作为一名计算机教师&#xff0c;我经常遇到一个难题&#xff1a;如何让没有高性能电脑的学生也能亲身体验AI图像识别的魅力&#xff1f;实验室的电脑配置不足&#xff0c;难以运行复杂的深度学习模型。经过多次尝试&#…