恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

Vue3大文件上传实战:切片、哈希、断点续传与秒传

  • 首页
  • 资讯中心
  • /
  • Vue3大文件上传实战:切片、哈希、断点续传与秒传

相关资讯

SAP Excel模板上传下载的隐性规则与工程化实践 2026/10/13 22:32:31
Vue3大文件上传实战:分片、断点续传与秒传方案 2026/10/13 22:32:31
B站视频Python爬虫下载:从接口解析到FFmpeg音视频合并全攻略 2026/10/13 22:32:31

最新资讯

信号频谱分析实验:从FFT原理到窗函数与混叠的工程实践
C#批量绘制74项环流指数趋势图:从数据清洗到多子图导出
动态规划详解:字符串最短编辑距离原理、实现与优化
接口服务自动化部署脚本优化:失败即停、可回滚、健康检查
YOLOv9玩手机检测实战:小目标鲁棒识别与工业落地优化
电销对话引擎设计指南:三层漏斗架构与低延迟实战

今日推荐

IBM Verify Identity Access 修复 22 个漏洞:两个 9.8 严重 RCE 无需认证
小区物业管理系统
小型超市仓库管理系统

本周热门

UE动画修改实战:从资产编辑到重定向与蒙太奇驱动
统计随机数生成器攻击下的KLJN安全密钥交换协议Matlab仿真
政务API安全治理:资产测绘、低代码编排与行标对标实践

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

Vue3大文件上传实战:切片、哈希、断点续传与秒传

发布时间:2026/10/13 22:32:31
Vue3大文件上传实战:切片、哈希、断点续传与秒传 做中后台开发这几年最让我头疼的不是复杂业务逻辑而是大文件上传。Vue3 技术栈配上大文件上传需求听起来不算什么新鲜事可真到了要处理几百MB甚至几个GB的文件时才发现普通上传方案从头到尾都是漏洞。这篇文章把我自己做过并沉淀下来的 Vue3 大文件上传全流程完整拆一遍包括切片思路、哈希计算、并发控制、断点续传和秒传校验前端代码和后端配合一起讲。适合刚接触这类需求的人直接照着搭也适合已经写出一版但线上问题不断的人来对照排查。1. 大文件上传先从需求倒推技术方案1.1 为什么普通文件上传扛不住大文件很多人第一反应是文件上传不是用一个 input 加一个 form 就能搞定吗确实几MB的小文件这样传没问题但换成视频、安装包、数据库备份这类动辄几百MB以上的文件时流程瞬间就不稳了。问题出在几个地方。首先是服务器和网关对请求体大小的限制。不少中间件默认只允许上传几MB到几十MB的请求体文件一大请求直接被拒绝。其次是网络超时大文件通常需要几秒甚至几分钟才能传完这期间只要网络抖一下、用户切个WiFi整个请求就中断了前端只能报错重来。最坑的是重传成本一旦失败用户得重新选文件、重新传时间全部浪费掉这对体验是毁灭性的打击。还有一层是服务器压力。一个几百MB的文件一次性塞给后端接口后端从请求体里拿数据时内存占用会非常吓人如果同时有多个用户并发服务很容易被打满。所以大文件上传不能走普通上传的路子核心思路是拆散再重组。1.2 切片上传的底层逻辑切片上传的意思很直白用 File API 里的slice方法把一个大文件按固定大小切成若干块然后一块一块地单独上传。后端每收到一个分片就把这个分片按顺序保存下来等所有分片都传完再按序号把它们合并成完整的文件。这个方案能同时解决前面说的问题。单个分片只有几MB请求体小了网关限制轻松绕过单个分片传输时间短网络抖动影响的只是其中一个分片重传代价很低后端每次只接收一个小分片内存占用稳定不会因为一个大文件进来就把服务拖垮。打个比方整体搬运一个几百斤的大箱子电梯进不去、路上还容易翻不如拆成十几个小包裹分批次寄到了目的地再组装。切片尺寸一般按实际网络情况来定这个后面我会专门讲。但有一点可以提前确认不管怎么切后端合并时都依赖一个唯一标识来关联所有分片这个标识就是文件内容的哈希值。1.3 断点续传和秒传为什么必须一起做切片只是第一步如果不做断点续传那和普通上传没本质区别。断点续传的核心逻辑是每次上传前先问一下后端——这个文件对应的分片哪些已经传过了然后把已经传过的分片全部跳过只传剩下的。这样上传中断了、网断了、页面关了重新进来还能从上次的位置继续而不是从头再来。秒传则是建立在哈希基础上的一个彩蛋。因为每个文件的哈希值是唯一的后端可以先看看存储目录里是不是已经有这个哈希对应的完整文件了。如果有那前端根本不需要传分片直接告诉用户上传完成就行。这个场景在多人上传同样素材时特别实用比如团队内部传输同一个安装包第一个人慢慢传后面的人全部秒过。断点和秒传是同一个体系的两种表现前者解决传到一半怎么办后者解决这个文件之前传过怎么办。注意这里说的哈希必须是文件内容的哈希不能拿文件名做标识同名但内容不同的文件太多了靠文件名判断会出大乱子。2. 环境准备与工具选型2.1 前端依赖清单与版本选择要把这套流程跑起来前端需要的东西并不多。Vue3 这块我用的是组合式 API 风格来写这是 Vue3 的默认推荐方式逻辑复用和代码组织都比 Options API 舒服很多。网络请求我用 axios它的onUploadProgress回调能拿到上传进度事件这个是大文件上传进度条实现的基础原生 fetch 目前对上传进度的支持还不如 axios 顺手。文件哈希计算需要引入第三方库我用的spark-md5。这个库性能好API 简单支持增量计算也就是说我可以把每个分片依次喂给它最后一次性拿到整个文件的哈希值。UI 组件库不是必须的但如果你的管理后台本来就在用 Element Plus上传按钮、进度条、提示信息这些直接用现成组件就行省去大量样式代码。这里提醒一句这类库的版本不要随便升到最新的测试版我遇到过某个小版本出现兼容性问题导致整个页面卡死最后锁版本号才解决。稳定优先功能够用就好。2.2 分片大小怎么选更合理分片大小没有标准答案需要结合实际场景拍板。我自己实践下来2MB到5MB是大多数情况下的甜点区。分片越小并发控制和断点续传的粒度越细但分片数量会变多。分片太多会产生大量HTTP请求请求头和响应头的开销会拖累整体效率后端存储分片时的文件数也会暴增。分片太大又回到大文件的老问题——单个请求体过大网络抖动影响范围变大。比如一个2GB的文件如果切10MB的大分片有200多个分片断点续传时粒度还是偏粗切1MB的小分片分片数超过2000HTTP请求本身的开销会非常可观。我常用的判断方式是按预计目标行业的网络情况倒推。如果是内网或者宽带环境比较好的用户5MB是比较舒服的如果是移动网络为主建议降到2MB。另外一个经验值参考分片数量尽量控制在100到500之间前端进度条展示起来也比较清晰。2.3 后端接口设计三个接口搞定全流程很多朋友在看大文件上传方案时只盯着前端代码但我建议一开始就把后端接口结构想清楚因为分片方案对接口设计有硬性要求。后端最少需要三个接口。第一个是校验接口负责两件事检查该哈希对应的完整文件是否已存在用于秒传以及如果文件不存在返回已上传的分片序号列表用于断点续传。第二个是上传接口接收单个分片的二进制数据和元信息比如文件哈希、分片序号并落盘保存。第三个是合并接口等所有分片上传完成后通知后端把分片按顺序合并成完整文件。三个接口的分工非常明确前端流程就是围绕这三个接口编排的先校验再挑出确实没传的分片去上传全部完成后请求合并。这样设计后端逻辑也简单不用把状态管理塞到一个接口里。3. 核心实现切片、哈希与并发控制3.1 文件切片File.slice 的正确打开方式浏览器端的 File 对象继承了 Blob所以自带slice方法。用slice可以把文件切成不重合的多个小 Blob语法是file.slice(start, end)。这里有两个细节容易被忽略一是end是开区间最后一个分片的大小可能不足设定值这是正常的二是切片虽然高效但要在拿到文件对象后再处理输入框change事件里的files[0]必须存到变量里不要在前面做层层包装导致 File 对象丢失。切片代码很简单核心就是一个循环function createFileChunk(file, size 2 * 1024 * 1024) { const chunks [] let cur 0 while (cur file.size) { chunks.push({ index: cur / size, blob: file.slice(cur, cur size) }) cur size } return chunks }这里的index我直接用了当前位置除以分片大小得到的数字就是分片序号0、1、2...。后端合并的时候按这个 index 排序,顺序绝对不会乱。注意file.slice返回的 Blob 仍然是原始文件的引用切片不会真的复制一份数据出来所以内存压力主要在后端接收时前端不会因为切了很多片而内存暴涨。这一点可以放心。3.2 计算文件哈希spark-md5 的异步处理方案文件哈希是这套方案里最绕不开的环节。哈希的计算方式直接决定秒传准确性和断点续传的可靠性。做法是先把所有分片按顺序读一遍每读完一个分片的 ArrayBuffer就喂给 SparkMD5 增量计算最后得到全文件哈希。import SparkMD5 from spark-md5 function calculateHash(chunks) { return new Promise((resolve, reject) { const spark new SparkMD5.ArrayBuffer() const chunkSize chunks.length let count 0 const loadNext () { if (count chunkSize) { resolve(spark.end()) return } const reader new FileReader() reader.onload (e) { spark.append(e.target.result) count 1 if (count % 50 0) { // 每读50个分片交回控制权给浏览器避免长时间阻塞UI setTimeout(loadNext, 0) } else { loadNext() } } reader.onerror () reject(new Error(文件读取失败)) reader.readAsArrayBuffer(chunks[count].blob) } loadNext() }) }这段代码建议放到 Web Worker 里跑尤其是大文件场景。因为哈希计算需要完整读取一遍文件如果主线程做用户会看到页面卡住、按钮点不动印象极差。用 Web Worker 的话文件读取和哈希计算都在后台线程主线程流畅不受影响。还有一个可以权衡的方案采样哈希。如果文件特别大完整计算耗时长可以只读取每个分片的头尾一小段数据来做哈希计算这样秒传判断的准确性会稍微下降但速度提升明显。具体要不要用得看你对数据碰撞的容忍度。我自己用的比较多的是完整哈希因为现在的 SparkMD5 增量计算性能已经很好了几GB文件也就十几秒配合 Web Worker 完全不打扰用户。3.3 并发控制用 Promise 池限制上传节奏分片切好了哈希算完了下一步就是把分片传上去。很多第一次写大文件上传的人会直接在for循环里把所有分片同时发出去几百个请求一瞬间打过去浏览器直接卡死后端也扛不住。所以必须做并发控制。并发控制的本质就是维护一个Promise池同时最多跑N个上传请求一个跑完就从池子里挪出去腾出位置给下一个。这里我用一个很通用的 Promise 队列实现async function concurrentSend(tasks, limit 3) { const pool [] let index 0 const run async () { while (index tasks.length) { const current index index 1 await tasks[current]() } } for (let i 0; i Math.min(limit, tasks.length); i 1) { pool.push(run()) } await Promise.all(pool) }这个写法比Promise.race的方式更清晰不需要手动维护完成回调。每个任务内部就是一个uploadChunk的 axios 请求请求完成后自然进入下一个。并发数不是越大越好。我实测下来3到5个并发是比较稳的区间。并发数太高客户端的带宽会被打满所有分片都慢下来服务端的连接数也会吃紧并发数太低整个文件的上传时间又太长。如果你的服务器性能很好、用户带宽也充足可以调高到6甚至8但建议从3开始逐步测试找到一个既快又不翻车的平衡点。3.4 断点续传校验与秒传判断分片上传接口之前先调用校验接口。校验接口拿到文件哈希后返回两部分数据文件是否已经完整存在以及已上传分片的序号列表。async function checkFile(hash) { const { data } await axios.get(/check, { params: { hash } }) return data }拿到结果后分两种情况处理。如果data.complete为 true说明这个文件之前已经完整上传过了直接走秒传流程提示用户上传成功即可不用发任何分片请求。如果没完整上传就把返回的已上传序号集合起来过滤掉这些分片只把剩余分片加入并发任务池。过滤逻辑非常简单const uploadedSet new Set(data.uploaded) const pendingChunks chunks.filter(chunk !uploadedSet.has(chunk.index))注意uploaded数组里存的是数字序号而chunk.index因为有除法关系可能出现浮点精度问题比如2 / (2*1024*1024)恰好等于 1但某些场景下可能变成 1.0000001。更稳妥的做法是切片时用Math.floor取整或者后端返回字符串序号、前端也统一转字符串比较避免精度坑。合并请求在所有分片上传完成后触发代码很简单await axios.post(/merge, { hash, ext, size: chunks.length })后端拿到size参数后就清楚自己需要读到多少个分片才能合并这是校验完整性最直接的依据。4. 实操走一遍Vue3 组件从 0 到 14.1 选文件与上传状态管理现在开始组装一个可运行的 Vue3 上传组件。我用组合式 API 来写状态管理核心状态包括文件对象、分片列表、上传进度百分比、上传中标记、暂停标记。template div classupload-panel input typefile :disableduploading changehandleFileChange / el-progress v-iffile :percentagepercentage / div classupload-actions el-button typeprimary :disableduploading || !file clickhandleUpload {{ uploading ? 上传中… : 开始上传 }} /el-button el-button :disabled!uploading clickhandlePause 暂停 /el-button /div /div /template选择文件后不要急着上传我习惯在handleFileChange里先把文件对象存下来同时把分片切片好并触发哈希计算。哈希计算期间禁用上传按钮等哈希出来以后再开放操作这样流程上是清晰的文件和哈希是上传的前提条件。const file ref(null) const chunks ref([]) const fileHash ref() const uploading ref(false) const percentage ref(0) function handleFileChange(e) { const rawFile e.target.files[0] if (!rawFile) return file.value rawFile percentage.value 0 chunks.value createFileChunk(rawFile, 2 * 1024 * 1024) // 哈希计算完成后才能上传 calculateHash(chunks.value).then((hash) { fileHash.value hash }) }这里要留意e.target.files[0]是只读引用直接把 rawFile 挂到 ref 上是没问题的但不要在后续逻辑里对 rawFile 做delete或重新赋值否则切片时拿到的对象可能已经失效。4.2 核对已传分片并执行并发上传上传主流程可以拆成四步我写成一个handleUpload方法逻辑顺序清晰可见。async function handleUpload() { if (!file.value || !fileHash.value) return uploading.value true try { // 第一步校验文件状态获取已上传分片 const { data } await axios.get(/check, { params: { hash: fileHash.value } }) if (data.complete) { percentage.value 100 return } // 第二步过滤掉已传分片 const uploadedSet new Set(data.uploaded) const pendingChunks chunks.value .filter(chunk !uploadedSet.has(chunk.index)) .map((chunk) { const formData new FormData() formData.append(hash, fileHash.value) formData.append(index, chunk.index) formData.append(chunk, chunk.blob) return () uploadChunk(formData) }) // 第三步并发上传剩余分片 const startCount pendingChunks.length let doneCount 0 await concurrentSend(pendingChunks, 3) // 第四步全部完成后触发合并 await axios.post(/merge, { hash: fileHash.value, ext: file.value.name.split(.).pop(), size: chunks.value.length }) percentage.value 100 } catch (err) { console.error(上传失败, err) } finally { uploading.value false } }uploadChunk里要挂onUploadProgress用来统计进度。进度计算我建议这样做每上传完一个分片就按已完成的请求数/总分片数来算而不是依赖每个请求的字节流进度做累加否则进度条会跳来跳去。function uploadChunk(formData) { return axios.post(/upload, formData, { onUploadProgress: () { // 这里的回调可以实现单分片进度但统计总进度时 // 用完成的请求数来算会更稳定 } }) }如果希望进度条更精细可以在每个分片的onUploadProgress里记录该分片已上传字节数然后汇总整个文件已上传字节数再除以文件总大小。但要注意多请求并发时回调触发非常频繁需要做节流不然会频繁触发 Vue 的响应式更新影响渲染性能。4.3 后端接收与合并的配套实现前端只是半场后端也得配合好。我这边用一个 Node.js 的简单实现来演示实际生产环境换成你擅长的语言也一样核心逻辑就是三个接口。首先接收分片。用 multer 处理 multipart 数据把临时文件按哈希值归类存放const express require(express) const multer require(multer) const fs require(fs) const path require(path) const app express() const upload multer({ dest: uploads/temp }) const CHUNK_DIR path.join(__dirname, uploads/chunks) app.post(/upload, upload.single(chunk), (req, res) { const { hash, index } req.body const chunkDir path.join(CHUNK_DIR, hash) if (!fs.existsSync(chunkDir)) { fs.mkdirSync(chunkDir, { recursive: true }) } // 临时文件改名为序号文件 fs.renameSync(req.file.path, path.join(chunkDir, ${index})) res.json({ code: 0 }) })校验接口要返回已上传分片的序号列表顺便检查完整文件是否已经存在app.get(/check, (req, res) { const { hash } req.query const complete fs.existsSync(path.join(__dirname, uploads/files, hash)) const chunkDir path.join(CHUNK_DIR, hash) const uploaded fs.existsSync(chunkDir) ? fs.readdirSync(chunkDir) : [] res.json({ complete, uploaded: uploaded.map((item) Number(item)) }) })合并接口是最后一步。合并前一定要校验分片数量是否足够、分片是否完整。我的做法是先从 0 到 size-1 按顺序把所有分片内容读出来再拼一起。这个逻辑简单可靠但全部读进内存对超大文件不友好更好的方案是使用流式合并。下面给出流式合并的写法避免内存暴涨app.post(/merge, (req, res) { const { hash, ext, size } req.body const chunkDir path.join(CHUNK_DIR, hash) const outputPath path.join(__dirname, uploads/files, ${hash}.${ext}) const writeStream fs.createWriteStream(outputPath) let index 0 const appendNext () { if (index size) { writeStream.end() return } const chunkPath path.join(chunkDir, ${index}) if (!fs.existsSync(chunkPath)) { writeStream.destroy() res.status(400).json({ code: 1, msg: 缺少分片 }) return } const readStream fs.createReadStream(chunkPath) readStream.on(end, () { index 1 appendNext() }) readStream.pipe(writeStream, { end: false }) } appendNext() writeStream.on(finish, () { res.json({ code: 0, url: /files/${hash}.${ext} }) }) })流式合并的好处是无论文件多大内存占用都保持稳定。之前我用Buffer.concat合并一个2GB的视频时后端直接内存溢出崩了换流式合并以后服务端内存始终保持在几十MB。这个坑大家一定要提前避开。合并完成后后端顺手把整个分片目录删掉避免磁盘上残留大量无用分片。前端在拿到合并成功的响应后再把状态清理干净一次完整的上传就闭环了。5. 常见问题与排查技巧实录5.1 高频问题速查表整理一份我在实际支持过程中被问得最多的问题速查表直接对照着排查比看文档快得多。问题现象根本原因解决方案上传几秒后报错返回413网关或服务端限制了请求体大小检查反向代理的 client_max_body_size调大或关闭哈希计算时页面卡死在主线程同步读取文件并计算改成 Web Worker 异步计算断点续传后文件损坏合并时分片顺序不对或分片不完整用分片序号排序合并前校验总数和大小进度条不动或来回跳多请求并发时进度统计方式错误按完成请求数/总请求数加权或汇总字节数后节流并发一高就超时并发数设置过大带宽和连接被打满限制并发为 3~5逐台设备实测调整秒传判断失败哈希计算不一致或分片未完全上传用完整文件哈希避免采样哈希时选择不统一5.2 踩坑记录这些细节不注意会翻车第一个坑是分片序号精度问题。前面提到过切片时用cur / size会得到浮点数比如1 / 2是 0.52 / 2是 1但3 / 2是 1.5这样 index 全是浮点数。我自己就在断点续传的过滤逻辑里吃过亏后端返回的已传序号是整数前端过滤时用Set.has(1.5)永远返回 false等于完全没过滤。白传了一大堆分片。解决办法是用Math.floor(cur / size)或者直接用一个自增计数器chunkIndex。第二个坑是暂停功能的实现。最简单的暂停可以用一个 flag 控制并发池不再派发新任务但已经发出去的请求还在跑。如果想真的中断在途请求需要给每个 axios 请求配上AbortController。结合我们刚才的 Promise 池暂停的思路是标记暂停后池里的while循环不再取新任务同时AbortController把正在跑的请求全部取消已取消的分片等恢复上传时通过check接口自动跳过续传。第三个坑是分片文件的管理。如果用户在断点续传场景下频繁取消、重传后端会积累大量半成品分片目录。建议在定期清理任务的逻辑里把超过一定时间没被合并的分片目录清掉避免服务器磁盘被垃圾分片占满。这个定时清理任务尤其重要我在线上环境见过一次磁盘被写满的事故就是从清理机制缺失开始的。第四个坑是跨域配置。如果前端和后端不在同一个域分片上传同样要配置跨域。除了常见的跨域响应头之外还要注意Access-Control-Allow-Origin与带FormData请求的兼容。如果你用 axios跨域请求会自动带上Content-Type: multipart/form-data服务端要允许这个 Content-Type 的携带方式不然浏览器会在预检请求阶段就把请求拦下来。5.3 性能与体验优化建议前面讲的是把功能跑通接下来分享几个把体验做好的优化点这些细节往往决定一个功能是能用还是好用。第一大文件哈希计算用 Web Worker 后还可以配合增量展示。文件读取速度取决于磁盘和内存如果文件特别大建议在界面上显示正在计算文件指纹…的提示避免用户以为页面卡死了。有的团队还会在哈希计算期间先上传前几个分片等服务端计算出文件哈希后再做校验这是一种先传后验的优化策略适合文件非常大、哈希计算时间长的场景但实现复杂度会高一些。第二上传过程中要处理好文本提示。上传总进度、当前正在上传第几个分片、已上传多少MB这些信息尽量用简洁的方式呈现。我实测发现用户对剩余时间比当前速度更敏感所以可以在统计分片完成数量变化时计算一个平均速度进而估算剩余时间。不过平均值波动大建议用滑动窗口内的速度来估算。第三切换网络时的自动恢复。大文件上传最容易发生在网络环境不稳定的移动端。可以在 axios 的响应拦截器里识别网络错误自动把失败的分片重新丢回队列尾部再试一两次配合断点续传机制用户甚至感知不到上传中断过。当然要设置最大重试次数不然陷入死循环。第四性能上还有一个细节上传完成前触发合并接口时如果文件特别大合并过程也需要时间。这时候前端不要一直傻等可以给合并请求也设置一个较长的超时时间或者干脆把合并设计成异步任务先返回合并中状态前端定时轮询合并结果。我处理几十GB的超大文件时会用轮询方案普通场景直接等合并接口同步返回就够了。结尾简单收个尾。做这套 Vue3 大文件上传方案最深的体会是切片、哈希、并发、断点这些概念单拎出来都不难难的是把它们串成一个稳定可靠的闭环并且考虑到各种边界情况。文件上传这个功能几乎没有试错空间因为一旦出问题用户手头的文件就丢了这种事多发生几次就没法跟业务方交代。建议你先按文章里的顺序把流程搭出来再找一个几百MB的真实文件反复测试传一半断网再恢复、清掉后端分片再续传、双击重复上传看秒传把每条路径都踩通以后这套东西才算真正属于你。后面如果你们有大型文件传输的场景还可以在这个基础上去扩展断点续传加上上传队列管理、多文件列表展示等功能基础打好了上面加什么都快。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号