尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Vue CLI实现内网大文件分片上传与断点续传方案
1. 内网超大附件上传的挑战与解决方案在企业级应用开发中内网环境下的大文件上传是个常见但棘手的问题。最近接手的一个内部文档管理系统项目就遇到了用户需要批量上传数百GB设计稿的需求。传统的单文件上传方式在这里完全失效——浏览器卡死、进度反馈缺失、网络中断导致重传等问题频发。Vue CLI作为现代前端开发的标准工具链配合一些专门优化的库可以构建出稳定可靠的大文件上传方案。这里分享我们最终采用的分片上传断点续传并发控制技术组合实测可支持单文件50GB以上的稳定传输。2. 基础环境搭建与核心库选型2.1 初始化Vue CLI项目使用Vue 3的组合式API能获得更好的代码组织vue create large-file-uploader # 选择Vue 3 TypeScript模板 cd large-file-uploader npm install axios spark-md5 vueuse/core2.2 关键库的作用解析axios处理分片上传的HTTP请求需配置自定义实例实现超时重试spark-md5前端计算文件指纹用于服务端校验文件完整性vueuse/core提供useFileDialog等组合式API简化文件操作注意内网环境通常有严格的代理规则需要在vue.config.js中配置devServer.proxy解决跨域问题。我们遇到过Nginx默认限制上传大小为1MB的情况需要调整client_max_body_size参数。3. 大文件分片上传的核心实现3.1 文件分片策略设计// 分片大小建议根据网络环境动态调整 const CHUNK_SIZE 5 * 1024 * 1024; // 5MB interface Chunk { index: number file: Blob hash: string progress: number } const splitFile (file: File): Chunk[] { const chunks: Chunk[] [] const chunkCount Math.ceil(file.size / CHUNK_SIZE) for (let i 0; i chunkCount; i) { const start i * CHUNK_SIZE const end Math.min(file.size, start CHUNK_SIZE) chunks.push({ index: i, file: file.slice(start, end), hash: , // 后续计算 progress: 0 }) } return chunks }3.2 文件指纹计算优化直接计算整个文件的MD5会导致界面卡顿采用抽样计算提升性能const calculateHash (file: File): Promisestring { return new Promise(resolve { const spark new SparkMD5.ArrayBuffer() const reader new FileReader() const size file.size const offset 2 * 1024 * 1024 // 抽样2MB // 头尾各取2MB 中间均匀取样 const chunks [file.slice(0, offset)] for (let i 1; i 3; i) { const mid i * size / 3 chunks.push(file.slice(mid, mid offset)) } chunks.push(file.slice(size - offset, size)) let currentChunk 0 reader.onload e { spark.append(e.target?.result as ArrayBuffer) currentChunk if (currentChunk chunks.length) { reader.readAsArrayBuffer(chunks[currentChunk]) } else { resolve(spark.end()) } } reader.readAsArrayBuffer(chunks[0]) }) }4. 上传流程的健壮性设计4.1 断点续传实现方案服务端需要提供两个关键接口/check接收文件hash返回已上传的分片索引列表/upload接收分片数据返回处理结果前端逻辑const uploadChunks async (chunks: Chunk[], fileHash: string) { // 先检查已上传分片 const { data: uploaded } await axios.post(/check, { hash: fileHash }) const requests chunks .filter(chunk !uploaded.includes(chunk.index)) .map(chunk { const formData new FormData() formData.append(chunk, chunk.file) formData.append(hash, fileHash) formData.append(index, chunk.index.toString()) return axios.post(/upload, formData, { onUploadProgress: progress { chunk.progress Math.round( (progress.loaded / progress.total) * 100 ) } }) }) // 控制并发数 await Promise.all( requests.map((req, i) req.catch(() { console.error(分片${i}上传失败将自动重试) }) ) ) }4.2 错误处理与重试机制我们实现了三级重试策略单个分片上传失败时立即重试最多3次网络断开后重新连接时继续上传页面刷新后通过本地存储恢复上传状态关键代码// 使用localStorage保存上传状态 const saveProgress (fileHash: string, chunks: Chunk[]) { localStorage.setItem(fileHash, JSON.stringify( chunks.map(chunk chunk.progress) )) } // 恢复上传状态 const loadProgress (fileHash: string, chunks: Chunk[]) { const data localStorage.getItem(fileHash) if (data) { const progresses JSON.parse(data) chunks.forEach((chunk, i) { chunk.progress progresses[i] || 0 }) } }5. 文件夹上传的特殊处理5.1 递归遍历文件夹结构使用webkitdirectory属性获取文件夹内容input typefile webkitdirectory changehandleFolderUpload /处理逻辑const handleFolderUpload async (e: Event) { const files Array.from((e.target as HTMLInputElement).files || []) const folderMap new Mapstring, File[]() // 按文件路径分类 files.forEach(file { const path file.webkitRelativePath.split(/)[0] if (!folderMap.has(path)) { folderMap.set(path, []) } folderMap.get(path)?.push(file) }) // 并行上传各文件夹 await Promise.all( Array.from(folderMap).map(([folder, files]) uploadFolder(folder, files) ) ) }5.2 内存优化策略同时上传多个大文件时容易内存溢出我们采用串行上传不同文件夹单个文件夹内并行上传文件每个文件分片上传完成后立即释放内存const uploadFolder async (folder: string, files: File[]) { // 限制并发数 const CONCURRENCY 3 for (let i 0; i files.length; i CONCURRENCY) { await Promise.all( files.slice(i, i CONCURRENCY).map(file uploadSingleFile(file).finally(() { // 手动触发垃圾回收 if (i % 10 0) { window.performance performance.memory console.log(内存使用:, performance.memory) } }) ) ) } }6. 性能监控与用户体验优化6.1 上传速度动态调整根据网络状况自动调整分片大小let dynamicChunkSize 5 * 1024 * 1024 // 初始5MB const adjustChunkSize (avgSpeed: number) { if (avgSpeed 1024 * 1024) { // 1MB/s以上 dynamicChunkSize Math.min(20 * 1024 * 1024, dynamicChunkSize * 1.5) } else { dynamicChunkSize Math.max(1 * 1024 * 1024, dynamicChunkSize * 0.8) } }6.2 进度反馈的精确计算需要区分不同层级的进度单个分片上传进度单个文件上传进度整个文件夹上传进度const calculateProgress (files: UploadFile[]) { let loaded 0 let total 0 files.forEach(file { file.chunks.forEach(chunk { loaded chunk.file.size * (chunk.progress / 100) total chunk.file.size }) }) return total 0 ? Math.round((loaded / total) * 100) : 0 }7. 服务端配合要点虽然主要讨论前端实现但服务端也需要相应配合7.1 分片存储设计建议采用临时目录最终合并的方案/tmp /{fileHash} /0.chunk /1.chunk /... /merged /final_file.zip7.2 合并文件的高效方式使用流式处理避免内存爆炸# Python示例 def merge_chunks(file_hash, target_path): chunk_files sorted(glob.glob(f/tmp/{file_hash}/*.chunk), keylambda x: int(x.split(/)[-1].split(.)[0])) with open(target_path, wb) as f: for chunk_file in chunk_files: with open(chunk_file, rb) as cf: f.write(cf.read()) os.unlink(chunk_file) # 删除临时分片 os.rmdir(f/tmp/{file_hash}) # 删除临时目录8. 实际部署中的经验教训在内网环境部署这套方案时我们遇到了几个意料之外的问题企业防火墙干扰某些网络设备会重置长时间空闲的连接。解决方案是添加心跳包机制每30秒发送一个空请求保持连接活跃。磁盘IO瓶颈当多个用户同时上传时服务端的磁盘写入成为瓶颈。最终我们实现了分片写入不同物理磁盘的策略。内存泄漏长时间运行的Vue应用可能出现内存增长。通过以下方式解决// 在组件卸载时清理 onUnmounted(() { cancelTokenSource?.cancel() uploadWorker?.terminate() localStorage.removeItem(currentFileHash.value) })路径编码问题Windows和MacOS的文件夹路径编码方式不同需要统一处理const normalizePath (path: string) { return path.replace(/\\/g, /).normalize(NFC) }这套方案目前已在生产环境稳定运行6个月单日处理上传数据超过10TB。最大的改进空间在于增加P2P传输能力让内网客户端之间可以直接交换分片进一步减轻服务器压力。
RELATED

相关推荐

HTC老机型刷机全链路:S-ON解锁、Recovery覆盖与RUU救砖

HTC老机型刷机全链路:S-ON解锁、Recovery覆盖与RUU救砖

1. 抽屉里那台老 HTC,真正的门槛从来不是“解锁”这一步 每年总有那么几次,会有人抱着一台屏幕已经有点发黄的老机器来找我,说想把它救活。型号多半是 One 系列、Desire 系列或者 Butterfly——这些机器放在今天连当备用机都勉强,…

📅 2026/11/3 22:06:21
Model-Optimizer:面向边缘AI的模型压缩与推理加速工程实践

Model-Optimizer:面向边缘AI的模型压缩与推理加速工程实践

1. 项目概述:这不是一个“一键加速”的魔法按钮,而是一套面向真实推理场景的模型瘦身工程体系 “Model-Optimizer”这个名称乍看像某个商业软件的包装名,但在我过去三年深度参与十几个边缘AI部署项目的实操经验里,它从来不是开箱即…

📅 2026/11/3 22:06:21
Paperclip:轻量级 AI Agent 框架实战指南

Paperclip:轻量级 AI Agent 框架实战指南

1. 项目概述:Paperclip 不是回形针,而是一个轻量级 AI Agent 框架你搜“paperclip”时,第一反应可能是办公桌上那个银色小金属片——但在这波 AI 工具浪潮里,Paperclip 是一个真实存在的、开源的、基于 Node.js React 构建的 AI …

📅 2026/11/3 22:06:21
MORE NEWS

更多资讯

📰

LC选频网络与谐振回路:Q值、通频带及阻抗匹配解析

简介:这份PPT面向电子工程、通信类课程学习者及需要复习高频电路基础的读者,围绕选频网络中的串联谐振回路展开,适合课堂讲授、考前梳理与知识点查漏。内容从单振荡回路的谐振特性切入,依次讲解谐振频率、阻抗与电流相位关系、品质…

📰

工控机无网不用愁:三步搞定网络共享,附共享与网桥对比

车间里的工控机大多是"孤岛"状态——设备本身跑着采集程序、视觉算法或者本地数据库,但一到要拉取更新包、同步日志、远程看个界面,就卡在"没有网络"这一步。很多人第一反应是给每台机器插一张无线网卡,或者干脆拉一根网…

📰

AI工程体系构建:Python/Rust/TS/Julia多语言协同实践

1. 为什么“从零构建AI工程体系”不是写个Python脚本那么简单 很多人看到“AI Engineering from Scratch”这个标题,第一反应是:不就是用PyTorch搭个模型、调调超参、跑个train.py吗?我上周刚用Hugging Face的Trainer类三行代码训完一个文本…

📰

LeetCode 27题:双指针法原地移除数组元素

1. 题目解析与需求理解LeetCode第27题"移除元素"是一个经典的数组操作问题,题目要求我们原地移除数组中所有等于给定值的元素,并返回新数组的长度。这里的"原地"意味着不能使用额外的数组空间,必须通过修改输入数组来实现…

📰

STM32入门必看:Proteus 8.6安装汉化与仿真全攻略

嵌入式入门这条路上,STM32几乎是绕不开的一站,而仿真工具Proteus则是很多人在没有实物板子时最先接触的验证手段。我见过太多新手卡在"软件装好了但界面全是英文""汉化包放进去没反应""仿真跑不起来提示缺少模型"这些环节…

📰

Node.js私厨服务小程序开发实战与架构设计

1. 项目背景与需求分析私厨服务系统小程序是近年来O2O餐饮领域的一个创新方向,它连接了有烹饪技能的个人厨师和追求个性化餐饮服务的消费者。这个毕业设计项目选择基于Node.js技术栈开发,主要考虑了以下几个市场需求:餐饮个性化趋势&#xff…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬