恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Flask+WebUploader大文件分片上传实战:断点续传与秒传实现
首页
资讯中心
/
Flask+WebUploader大文件分片上传实战:断点续传与秒传实现
Flask+WebUploader大文件分片上传实战:断点续传与秒传实现
发布时间:2026/10/18 13:07:01
简介这是一份面向Python Web开发者的分段上传与流式下载实战示例基于Flask框架与WebUploader前端组件实现大文件分片上传、服务端合并及低内存流式下载适合需要处理大文件传输场景的中级开发者参考学习。压缩包共26个文件约427KB包含2个Python服务端脚本、2个HTML页面模板、5个CSS与5个JS前端资源以及Bootstrap字体文件、source map、requirements依赖清单和README说明文档结构清晰便于快速运行与二次开发。目前已有651人学习下载。资源完整呈现了多用户并发上传互不干扰、分片接收后合并成完整文件、下载时按序回传等核心逻辑并附带上传成功与失败的效果演示可帮助读者理解分片上传的进度渲染、编码处理与内存优化思路同时提示了合并分片时的内存瓶颈及后续改进方向是一份可直接运行、便于扩展的Flask文件传输学习素材。1. 大文件上传的痛点与这个项目的解法做过文件上传功能的开发者大概都经历过这样的场景用户传一个几百兆甚至几个 G 的视频或压缩包浏览器转圈转到天荒地老中途网络一抖进度归零用户骂骂咧咧地重新来过。更别提服务端还得一次性把整个文件读进内存稍不注意就 OOM。这个名为 upload-demo 的项目就是冲着这个场景来的——它用 Flask 做后端、WebUploader 做前端把大文件切成小段分片上传支持断点续传和秒传前端还配了 Bootstrap 做界面。说白了它解决的就是「大文件怎么稳定、高效、可恢复地传到服务器」这件事。适合谁正在做网盘、素材库、后台管理系统里文件上传模块的 Python 开发者尤其是用 Flask 做 Web 服务、又不想自己从零造分片轮子的那批人。项目结构不复杂但该有的分片、合并、校验逻辑都在拿来当模板改比看文档快得多。2. 技术选型与整体架构Flask 后端 WebUploader 前端怎么配合2.1 为什么是 Flask 和 WebUploader 这套组合Flask 在这个项目里的角色很明确轻量、路由清晰、扩展灵活。大文件上传的核心逻辑其实不在框架本身而在于怎么接收分片、怎么暂存、怎么合并。Flask 的request.files能直接拿到上传的文件对象配合os模块做分片落盘代码量很少就能跑通。WebUploader 是前端分片上传的老牌方案它负责在浏览器端把 File 对象切片按配置的 chunkSize 逐片 POST 到后端同时维护上传进度和并发控制。两者之间靠约定好的接口参数通信chunk当前分片序号、chunks总分片数、guid文件唯一标识、file分片二进制。Bootstrap 在这里不是核心但它的栅格和进度条组件让上传界面不至于太寒碜适合快速搭一个能看的 Demo。这套组合的优势在于职责分离前端只管切和传后端只管收和拼。你不需要引入 Celery 或消息队列就能跑起来适合中小规模的文件上传场景。如果换成 Django自带的文件处理层反而会干扰分片逻辑的细粒度控制换成纯前端直传对象存储又少了服务端合并这一环的教学价值。所以这个项目的定位很准它是一个可运行、可拆解、可改造的分片上传最小闭环。2.2 目录结构与模块职责项目目录大致长这样我按常见 Flask 项目的组织方式还原一下upload-demo/ ├── app.py # Flask 入口路由和上传逻辑 ├── static/ │ ├── css/ │ │ └── bootstrap.min.css │ ├── js/ │ │ ├── webuploader.min.js │ │ └── upload.js # 前端分片上传初始化与事件绑定 │ └── uploads/ # 分片暂存目录 ├── templates/ │ └── index.html # 上传页面引入 Bootstrap 和 WebUploader ├── uploads/ # 合并后的文件存储目录 └── requirements.txt # flask 等依赖app.py是核心里面通常有两个路由一个返回上传页面一个处理分片接收和合并。static/js/upload.js负责实例化 WebUploader配置server地址、chunked: true、chunkSize、threads等参数。static/uploads/用来存分片每个文件一个临时目录以guid命名合并完成后清理。uploads/存最终文件。这个结构的好处是分片和成品隔离出问题好排查——分片没传完临时目录里能看到缺哪片合并失败成品目录不会出现半截文件。2.3 分片上传的完整数据流一次完整的上传流程是这样的前端选中文件后WebUploader 根据chunkSize比如 2MB把文件切成 N 片每片带上chunk序号和guid。后端收到 POST 请求后先检查static/uploads/{guid}/目录是否存在不存在就创建然后把当前分片以{chunk}为文件名存进去。每收一片可以返回一个简单 JSON 告诉前端成功。当所有分片到齐前端触发一个合并请求或者后端在收到最后一片时自动合并后端按序号从小到大读取分片追加写入最终文件最后删除临时目录。这里有个关键点合并的触发时机。常见做法是前端在所有分片上传完成后额外发一个merge请求带上guid和filename后端执行合并。这样做的好处是合并逻辑独立方便加重试和校验。另一种做法是后端在收到chunk chunks - 1时自动合并省一次请求但并发上传时最后一片不一定最后到容易翻车。我一般会选前端显式触发合并稳一点。// static/js/upload.js 核心配置片段 var uploader WebUploader.create({ swf: /static/js/Uploader.swf, server: /upload, // 分片接收接口 pick: #picker, chunked: true, // 开启分片 chunkSize: 2 * 1024 * 1024, // 每片 2MB chunkRetry: 3, // 分片失败重试次数 threads: 3, // 并发上传线程数 formData: { guid: // 占位实际在 uploadBeforeSend 里动态设置 } }); // 每次上传前把 guid 塞进表单数据 uploader.on(uploadBeforeSend, function (block, data) { data.guid block.file.id; // 用文件 id 作为唯一标识 }); // 所有分片传完后触发合并 uploader.on(uploadSuccess, function (file, response) { // 这里简化处理实际应判断是否所有分片都成功 $.post(/merge, { guid: file.id, filename: file.name }, function (res) { console.log(合并结果, res); }); });这段代码里chunked: true是分片开关chunkSize决定每片大小threads控制并发数——设太大浏览器会排队设太小上传慢3 到 5 是比较舒服的区间。uploadBeforeSend里把block.file.id赋给guid保证同一个文件的所有分片用同一个标识后端才能归拢到同一个临时目录。uploadSuccess里发合并请求注意实际项目中要判断是不是最后一个分片成功否则会重复触发合并。参数chunkRetry是网络抖动时的后悔药建议至少给 3 次。3. 后端核心逻辑分片接收、校验与合并的代码实现3.1 分片接收接口的写法与参数处理后端接收分片的逻辑不复杂但细节决定成败。下面是一个典型的 Flask 视图# app.py 分片接收接口 import os from flask import Flask, request, jsonify, render_template app Flask(__name__) BASE_DIR os.path.dirname(os.path.abspath(__file__)) CHUNK_DIR os.path.join(BASE_DIR, static, uploads) # 分片暂存根目录 FINAL_DIR os.path.join(BASE_DIR, uploads) # 成品目录 app.route(/upload, methods[POST]) def upload_chunk(): guid request.form.get(guid) # 文件唯一标识 chunk request.form.get(chunk) # 当前分片序号从 0 开始 chunk_file request.files.get(file) # 分片二进制 if not all([guid, chunk, chunk_file]): return jsonify({ok: False, msg: 参数缺失}), 400 # 每个文件一个临时目录目录名就是 guid target_dir os.path.join(CHUNK_DIR, guid) os.makedirs(target_dir, exist_okTrue) # 分片以序号命名方便后续按序合并 chunk_path os.path.join(target_dir, str(chunk)) chunk_file.save(chunk_path) return jsonify({ok: True, chunk: chunk})guid是前端生成的唯一标识通常用文件 id 或时间戳加随机数保证不同文件不串目录。chunk是分片序号WebUploader 从 0 开始编号后端存成同名文件合并时按数字排序读取。chunk_file.save()直接落盘不经过内存缓冲大文件也不会撑爆内存。os.makedirs(..., exist_okTrue)避免并发创建目录时报错。返回 JSON 让前端知道这片收好了失败时给 400 让 WebUploader 触发重试。注意request.files.get(file)里的file必须和前端 WebUploader 的fileVal配置一致默认就是file改过的话两边要对上。3.2 合并逻辑与文件完整性校验合并是分片上传里最容易出问题的环节。常见做法是前端在所有分片成功后发一个合并请求后端按序号拼接# app.py 合并接口 app.route(/merge, methods[POST]) def merge_chunks(): guid request.form.get(guid) filename request.form.get(filename) if not guid or not filename: return jsonify({ok: False, msg: 参数缺失}), 400 chunk_dir os.path.join(CHUNK_DIR, guid) if not os.path.isdir(chunk_dir): return jsonify({ok: False, msg: 分片目录不存在}), 404 # 按数字序号排序避免字符串排序出现 10 排在 2 前面 chunk_files sorted(os.listdir(chunk_dir), keylambda x: int(x)) os.makedirs(FINAL_DIR, exist_okTrue) final_path os.path.join(FINAL_DIR, filename) with open(final_path, wb) as out: for chunk_name in chunk_files: chunk_path os.path.join(chunk_dir, chunk_name) with open(chunk_path, rb) as inp: out.write(inp.read()) # 合并完成后清理临时分片避免磁盘堆积 for chunk_name in chunk_files: os.remove(os.path.join(chunk_dir, chunk_name)) os.rmdir(chunk_dir) return jsonify({ok: True, path: final_path})排序这里有个血泪经验os.listdir返回的顺序不保证而且直接sorted会按字符串排10会排在2前面合并出来的文件就是坏的。必须用keylambda x: int(x)转成整数再排。合并时逐片读取写入不一次性加载所有分片内存占用可控。合并完成后删掉临时目录否则磁盘会被分片撑满。filename来自前端实际项目中要做安全过滤防止路径穿越——比如用secure_filename或者只取 basename。3.3 断点续传与秒传的扩展思路项目本身可能只做了基础分片但断点续传和秒传是这类场景的刚需扩展起来也不难。断点续传的核心是上传前先问后端「这个 guid 已经有哪些分片了」后端返回已存在的分片序号列表前端跳过这些片。秒传则是上传前把文件哈希比如 MD5发给后端后端查数据库发现同样哈希的文件已存在直接返回成功不传了。# 检查已上传分片的接口断点续传用 app.route(/check, methods[POST]) def check_chunks(): guid request.form.get(guid) chunk_dir os.path.join(CHUNK_DIR, guid) if not os.path.isdir(chunk_dir): return jsonify({exists: []}) exists [int(name) for name in os.listdir(chunk_dir)] return jsonify({exists: exists})前端拿到exists后在uploadBeforeSend里判断当前分片是否已在列表中在的话直接跳过。秒传需要前端计算文件哈希大文件算 MD5 比较耗时常见做法是抽样计算取头尾和中间几段或者用 Web Worker 后台算。这块项目里不一定有但改造时优先加这两个用户体验提升最明显。4. 前端交互与 Bootstrap 界面上传进度、暂停与重试4.1 WebUploader 事件绑定与进度条联动WebUploader 提供了一套完整的事件钩子配合 Bootstrap 的进度条组件能做出比较顺滑的上传体验。核心事件有uploadProgress整体进度、uploadSuccess单片成功、uploadError失败、uploadComplete全部完成。下面是一个进度条联动的例子// 绑定整体进度到 Bootstrap 进度条 uploader.on(uploadProgress, function (file, percentage) { var $bar $(#progress-bar); var percent Math.round(percentage * 100); $bar.css(width, percent %).text(percent %); }); // 单片失败时记录方便排查 uploader.on(uploadError, function (file, reason) { console.warn(分片上传失败, file.id, reason); }); // 全部完成后提示 uploader.on(uploadComplete, function (file) { console.log(文件上传流程结束, file.name); });uploadProgress给的是 0 到 1 的小数乘 100 转百分比。Bootstrap 进度条靠width样式驱动text显示数字。uploadError里不要只console.log实际项目应该把失败的分片序号记下来提供「重试失败分片」按钮。uploadComplete触发时不一定所有分片都成功要结合uploadSuccess的计数来判断。4.2 暂停、继续与手动重试的实现WebUploader 自带stop()和upload()方法暂停就是停掉上传队列继续就是重新触发。但分片上传的暂停有个坑已经传了一半的分片不会自动续需要靠后端的check接口配合。手动重试则是把失败的分片重新加入队列// 暂停 $(#pause).on(click, function () { uploader.stop(true); // true 表示中断正在上传的分片 }); // 继续 $(#resume).on(click, function () { uploader.upload(); }); // 重试失败的分片简化示意 var failedChunks []; uploader.on(uploadError, function (file, reason) { failedChunks.push(file.chunk); }); $(#retry).on(click, function () { // 实际需要根据 failedChunks 重新构造上传任务 uploader.upload(); });stop(true)的参数true表示中断当前正在传输的分片false则等当前片传完再停。暂停后继续WebUploader 会从队列里没传的片开始但已经传了一半的片会重传——这就是为什么要配合后端check做断点续传。重试逻辑项目里可能比较简单改造时建议把失败分片单独存一个数组重试时只传这些片。4.3 Bootstrap 布局与上传区域的细节页面布局用 Bootstrap 的container、row、col搭骨架上传按钮和进度条放在一个卡片里。index.html大致结构!-- templates/index.html 简化结构 -- div classcontainer mt-5 div classcard div classcard-body h5 classcard-title大文件分片上传/h5 div idpicker classbtn btn-primary选择文件/div div classprogress mt-3 styleheight: 24px; div idprogress-bar classprogress-bar stylewidth: 0%;0%/div /div button idpause classbtn btn-warning btn-sm mt-2暂停/button button idresume classbtn btn-success btn-sm mt-2继续/button /div /div /div#picker是 WebUploader 的挂载点它会把这个 div 变成可点击的选择区域。进度条用 Bootstrap 的progress和progress-bar类styleheight: 24px让条粗一点数字看得清。暂停和继续按钮放在进度条下面操作路径短。注意 WebUploader 初始化时pick要指向#picker否则点了没反应。5. 避坑与常见问题排查5.1 分片合并后文件损坏或打不开现象上传完成文件大小也对但打开报错视频播不了压缩包解不开。原因分片排序用了字符串排序10排在了2前面拼接顺序错乱。解决合并时用sorted(os.listdir(chunk_dir), keylambda x: int(x))强制按整数排序。另外检查分片文件名是否纯数字如果前端传的chunk带了前缀后端存的时候要去掉。5.2 上传到一半报 413 Request Entity Too Large现象小文件能传大文件传到某个分片就 413。原因Flask 默认没有限制但反向代理Nginx有client_max_body_size限制默认 1MB分片超过这个值就被拦。解决在 Nginx 配置里加client_max_body_size 10m;和前端chunkSize匹配。如果直接用 Flask 开发服务器检查MAX_CONTENT_LENGTH配置设成比chunkSize稍大。5.3 并发上传时分片目录创建冲突现象多线程上传时偶尔报FileExistsError或分片丢失。原因多个分片同时到达都判断目录不存在然后创建产生竞态。解决os.makedirs(target_dir, exist_okTrue)本身能容忍目录已存在但如果用了「先判断再创建」的写法就会翻车。统一用exist_okTrue不要手动if not os.path.exists。5.4 合并完成后临时分片没删干净现象上传几次后磁盘满了发现static/uploads/下全是分片目录。原因合并逻辑里删了分片文件但没删目录或者合并失败时没走清理逻辑。解决合并成功后用shutil.rmtree(chunk_dir)整个删掉比逐个删文件再rmdir稳。另外加一个定时清理任务把超过 24 小时的临时目录清掉防止用户传一半跑了留下垃圾。5.5 前端进度条卡在 99% 不动现象所有分片都传完了进度条停在 99%合并请求没发出去。原因uploadSuccess事件里判断「所有分片成功」的逻辑有误或者合并请求被浏览器拦截。解决用计数器统计成功分片数等于总分片数时才发合并请求。检查浏览器控制台有没有跨域或 404 报错合并接口路径要和 Flask 路由一致。6. 二次开发从 Demo 到可上线模块的改造清单拿到这个项目跑通之后真正要往生产环境用还有几处必须改。我按优先级列一下自己的习惯做法。第一加文件哈希校验。前端用 SparkMD5 或 Web Worker 算文件 MD5上传前先请求后端/check_hash命中就直接返回文件地址实现秒传。没命中再把哈希带在合并请求里后端合并完算一次 MD5 比对防止分片传输过程中损坏。这一步加上用户传重复文件的体验会好很多。第二分片大小做成可配置。chunkSize不要写死 2MB根据文件大小动态调整小于 10MB 的直接整传10MB 到 100MB 用 2MB 分片超过 100MB 用 5MB 分片。分片太小请求数多太大单次失败重传成本高。我一般会在upload.js里根据file.size算一个合适的值。第三合并逻辑异步化。当前合并是同步的大文件合并时 Flask 工作进程会被占住并发上传会排队。改造思路是把合并任务丢给后台线程或 Celery接口立即返回「合并中」前端轮询/merge_status查结果。如果不想引入 Celery用threading.Thread也能凑合但要注意 Flask 应用上下文的问题。第四存储路径按日期分目录。所有文件堆在uploads/下文件多了ls都卡。改成uploads/2025/07/13/这种结构按上传日期散开。数据库里存相对路径读取时拼绝对路径。第五加权限和类型校验。filename必须过secure_filename限制允许的扩展名比如只让传 zip、mp4、pdf防止上传可执行文件。如果是多用户系统每个用户的文件隔离到独立目录合并和下载都要校验归属。验证改造是否成功我一般会跑三个用例传一个 500MB 的视频中途断网 10 秒再恢复看能不能续上传一个已经传过的文件看是否秒传并发传 5 个文件看分片目录有没有串。这三个过了基本就能上线了。从那以后我每次做上传模块都会先把分片排序和临时目录清理这两段代码单独拎出来测一遍因为这两个地方翻车最多而且出了问题往往到最后才发现。希望帮到你。本文还有配套的精品资源点击获取