简介upload-demo 是一套基于 Python Flask 与 WebUploader 实现的分段上传/下载演示项目面向需要处理大文件传输的前后端开发者。项目前端采用 WebUploader 将大文件切成多个分片并逐个上传后端接收全部片后完成合并同时支持多用户并行上传互不干扰下载侧借助流式回传降低内存占用适合学习分片上传、文件合并与流式下载的完整链路。资源共 26 个文件压缩包仅 427KB主要包含 2 个 Python 后端脚本、5 个 JS 与 5 个 CSS 前端文件、2 个 HTML 页面以及字体图标、依赖清单等结构精简可直接运行调试。当前已有 651 人学习下载。通过该 demo 可掌握 WebUploader 分片参数配置、Flask 路由与二进制流处理、多用户临时目录隔离等核心技巧代码注释与 README 也提供了启动步骤和未来优化方向适合入门级至中级开发者快速上手。1. 你被要求“传个2GB的包”时Flask 默认是接不住的做过 Flask 上传功能的工程师基本都遇到过这个场景产品经理轻飘飘丢来一句“帮我在后台加个上传用户要传大文件”你下意识点了点头结果一测就翻车——500MB 的压缩包传了一半请求超时服务器内存飙上去浏览器转圈转到最后给你一个空白页。问题不在于 Flask 本身不行而在于一次 HTTP 请求承载一个超大文件时从客户端到服务端每一环都有体积和时长的硬限制。分段上传的思路就是把这个大文件切成几十上百个小分片每个分片走一次普通请求全部传完后端再按顺序拼回去。它绕开了单请求超时、绕开了内存峰值、绕开了中途失败必须重传整个文件的窘境。upload-demo 这套 Flask WebUploader 的组合正是把这条路完整走通的最小参考实现适合正在做大文件上传、批量导入、报表导出回传这类功能的开发工程师照着改造。2. 先把方案立住分段上传的切片策略与完整链路2.1 WebUploader 是怎么把文件切开的分片、并发与重试WebUploader 是百度开源的上传组件它的核心能力不是“传文件”而是“把文件拆了再传”。在 HTML5 环境下它底层调用的是 File API 里的file.slice(start, end)方法——把一个 File 对象按字节切成若干段每段都是一个独立 Blob。这个切片能力浏览器原生支持WebUploader 做的是在这之上封装好分片唯一标识、并发控制、失败重试和进度回调。分片大小直接决定上传体验。设得太小比如 256KB整个文件会切成几百上千片请求数量太多握手开销和服务器 IO 反而拖慢速度设得太大比如 50MB又回到了“单请求传大文件”的老路中途断了重试代价高。常见做法是 2MB 到 8MB 之间取一个值内网带宽足可以往 8MB 靠公网上传建议 2MB。这个值对应 WebUploader 配置里的chunkSize单位是字节不是 KB很多人在这里把 2 当成 2MB 用结果传上去一片只有 2 字节这是后话。并发数同样关键。WebUploader 默认threads是 3也就是同时最多发 3 个分片请求。这不是随便定的数——浏览器对同一域名的并发连接数本来就有限制HTTP/1.1 下 Chrome 是 6 个左右前端开太多并发反而触发排队而 Flask 开发服务器处理并发的能力又弱开太大直接把后端压死。局域网内调试用 3 没毛病生产环境部署到 gunicorn 后面可以调到 5。分片传输还有一个容易被忽略的点失败重试。WebUploader 对单个分片请求失败会自动重发默认retries为 3 次。这个机制的价值在于某一分片因为网络抖动失败时前端只需要重传那一片而不是整个文件从头再来。这背后靠的就是每个分片都有独立的chunk序号后端通过文件唯一标识加序号就能定位到具体分片。2.2 前端生成文件唯一标识md5 还是文件名加大小分片上传的第一步不是切割而是给文件一个“身份证”。后端在接收几十个分片时必须知道这些分片属于哪个文件、应该按什么顺序拼。两个分片文件同名但内容不同或者同一个文件被两次上传这些情况都必须区分开。最稳妥的方案是给文件内容计算 md5。同一份文件的 md5 是固定的无论文件名怎么改、分片怎么切后端拿到 md5 就知道这些分片能拼成什么。WebUploader 官方 demo 里用的是 SparkMD5 这个前端库它支持“增量计算”——文件被切片后每一片的 md5 值可以边读边算不用把整个文件一次性读进内存。const spark new SparkMD5.ArrayBuffer(); const file fileList[0]; // 用户选中的原始 File 对象 const chunkSize 2 * 1024 * 1024; // 2MB和 WebUploader 配置保持一致 let offset 0; function loadNext() { const slice file.slice(offset, offset chunkSize); const reader new FileReader(); reader.onload function(e) { spark.append(e.target.result); offset chunkSize; if (offset file.size) { loadNext(); } else { const fileMd5 spark.end(); console.log(文件唯一标识:, fileMd5); // 拿到 md5 后再启动 WebUploader 开始上传 } }; reader.onerror function() { console.error(读取分片失败请检查文件是否被占用); }; reader.readAsArrayBuffer(slice); } loadNext();这段代码的逻辑是按同样的 2MB 大小把文件切片每读一片就把内容追加到 SparkMD5 实例里直到读完整个文件才输出最终 md5。这样计算过程的内存占用始终只有一片的大小2GB 的文件也能在浏览器里算完。参数说明chunkSize必须和 WebUploader 配置里的chunkSize一致否则计算 md5 时的切片位置和实际上传的分片位置对不上但那不影响最终结果——md5 只依赖文件内容不依赖切片边界。真正要注意的是FileReader.readAsArrayBuffer在读取超大文件时耗时较长2GB 文件的 md5 在普通 PC 上可能算 10 到 20 秒这段过程中最好给页面加一个进度提示避免用户以为卡死了。2.3 后端要比对什么分片元数据表设计前端把文件切成 N 片并计算好 md5 后每一片请求到达后端时都要携带自己的“身份信息”。后端拿到这些信息才能正确地把分片落盘、去重、排序、合并。这部分数据不一定要建 MySQL 表项目规模小时用文件目录加文件名就能存但字段设计思路是一样的。字段示例值作用fileMd58d9c...f3a2文件唯一标识用于区分不同文件fileName2024年报.zip原始文件名合并后用它命名chunkIndex5当前分片序号从 0 开始totalChunks120总分片数用于判断是否齐了chunkSize2097152当前分片实际字节数size2147483648文件总大小用于合并后校验这里的totalChunks尤其重要。很多实现里只存分片本身后端不知道这个文件一共应该有多少片导致合并时无法判断“分片齐不齐”。正确做法是前端从第一个分片开始每次请求都把totalChunks带上后端收到分片时先记录这个值所有分片上传结束后后端数一下临时目录里的分片文件数是否等于totalChunks等于才允许合并。chunkIndex的起点也要统一。WebUploader 的chunk参数从 0 开始计数如果你自己写前端逻辑从 1 开始也行但前后端必须一致。这个约定最容易在联调时埋雷最好在建表时就把“从 0 开始”写成注释。2.4 完整请求链路从分片上传到合并通知把上面几个环节串起来一次完整的分段上传是这样走的用户选中文件 → 前端计算 md5 → WebUploader 按 2MB 切片 → 并发上传分片每片请求携带 md5、序号、总数 → Flask 接口逐片接收、存进临时目录 → 前端在所有分片上传完成后调用合并接口 → Flask 按序号拼接分片、生成最终文件、清理临时目录 → 返回文件 URL。这里有一个设计决策值得说合并动作由谁触发。常见做本文还有配套的精品资源点击获取
