基于shutil、Flask与JavaScript实现归档创建时同步下载
解决方案:流式生成Zip并实时响应下载
你的核心问题是shutil.make_archive会先生成完整归档文件再返回,导致前端需要等待过长时间。解决思路是直接在响应中流式生成Zip文件,让前端立即开始下载,同时后台边读取外接硬盘文件边打包,无需等待整个归档完成。
1. 修改Flask后端代码
替换原有的/archive路由,新增一个直接流式下载文件夹的接口,用zipfile配合内存缓冲区分块生成Zip内容,避免提前占用硬盘空间或内存溢出:
import os import io import zipfile from flask import Flask, request, Response, send_file app = Flask(__name__) drive = "/path/to/your/external/drive/" # 替换为你的外接硬盘根路径 # 无需再提前创建归档文件夹,流式输出不依赖本地存储 def stream_zip(folder_path): buffer = io.BytesIO() # 初始化Zip文件对象,使用压缩模式 with zipfile.ZipFile(buffer, 'w', zipfile.ZIP_DEFLATED) as zipf: # 遍历目标文件夹下的所有文件 for root, _, files in os.walk(folder_path): for file in files: file_full_path = os.path.join(root, file) # 计算文件在Zip中的相对路径(避免保留完整硬盘路径) zip_inner_path = os.path.relpath(file_full_path, folder_path) # 分块读取外接硬盘文件并写入Zip with open(file_full_path, 'rb') as f: zipf.writestr(zip_inner_path, f.read()) # 将缓冲区内容输出给前端,之后重置缓冲区 buffer.seek(0) yield buffer.read() buffer.seek(0) buffer.truncate() # 输出Zip文件的结尾标识 buffer.seek(0) yield buffer.read() @app.route('/download-folder') def download_folder(): target_path = request.args.get('path') zip_name = request.args.get('name') folder_full_path = os.path.join(drive, target_path) # 校验目标路径是否为有效文件夹 if not os.path.isdir(folder_full_path): return "目标文件夹不存在", 404 # 设置响应头,告诉浏览器处理下载动作 headers = { 'Content-Type': 'application/zip', 'Content-Disposition': f'attachment; filename="{zip_name}.zip"' } # 返回流式响应,前端会立即开始接收数据 return Response(stream_zip(folder_full_path), headers=headers) # 保留原有的单个文件下载接口 @app.route('/download') def download_file(): file_path = request.args.get('path') full_path = os.path.join(drive, file_path) if not os.path.isfile(full_path): return "目标文件不存在", 404 return send_file(full_path, as_attachment=True)
2. 修改前端JavaScript代码
无需再等待归档接口返回结果,直接判断文件类型并触发对应下载接口,同时对路径参数做编码处理避免特殊字符报错:
function download_file() { let downloadUrl; // 判断当前路径是否为文件夹(无后缀名) if (!path.includes('.')) { // 文件夹调用流式下载接口 downloadUrl = `/download-folder?path=${encodeURIComponent(path)}&name=${encodeURIComponent(select)}`; } else { // 单个文件调用原有下载接口 downloadUrl = `/download?path=${encodeURIComponent(path)}`; } const downloader = document.createElement('a'); downloader.href = downloadUrl; downloader.click(); }
关键细节说明
- 实时下载:通过Flask的
Response配合生成器函数,每打包一个文件就立即将内容发送给前端,浏览器会实时开始下载,无需等待整个Zip生成完毕。 - 资源优化:用内存缓冲区分块处理文件,避免一次性加载大文件到内存,适配老旧外接硬盘的低速读取特性。
- 路径安全:使用
encodeURIComponent编码路径参数,避免空格、特殊字符导致的请求异常。
内容的提问来源于stack exchange,提问作者Werner van der Merwe
相关产品推荐
相关产品推荐

