DropzoneJS进度条未等待Python后台处理完成的解决问询
解决Dropzone进度条不跟踪后台处理进度的问题
嘿,我完全懂你遇到的这个问题!Dropzone的进度条本来就只盯着文件从浏览器传到服务器这一步,而你后台那个worker_from_python.main()才是真正费时间的活儿——这部分进度Dropzone根本没法感知到,所以才会出现进度条瞬间拉满,但后台还在吭哧吭哧处理的情况。
要实现真实的进度跟踪,我们得把「文件上传」和「后台处理」拆成两个独立的步骤,然后通过前端轮询后端进度的方式,手动更新Dropzone的进度条。下面是具体的实现方案:
第一步:重构后端代码,分离上传与处理逻辑
我们需要修改后端,让上传接口只负责保存文件、启动异步处理任务,然后返回一个任务ID给前端;同时新增一个接口用来查询任务的处理进度。
修改后的Python代码:
from flask import jsonify, request from concurrent.futures import ThreadPoolExecutor import uuid import os from werkzeug.utils import secure_filename # 初始化线程池(生产环境建议用Celery/RQ等专业任务队列,避免多进程问题) executor = ThreadPoolExecutor(max_workers=3) # 存储任务进度(生产环境请用Redis/数据库替代,避免重启服务丢失数据) task_progress = {} @bp.route('/upload', methods=['GET', 'POST']) @login_required def upload(): if request.method == 'POST': # 生成唯一任务ID task_id = str(uuid.uuid4()) task_progress[task_id] = 0 uploaded_files = [] # 保存上传的文件 for file_key in request.files: f = request.files[file_key] filename = secure_filename(f.filename) file_path = os.path.join(current_app.config['UPLOAD_FOLDER'], filename) f.save(file_path) uploaded_files.append(file_path) # 提交异步处理任务 executor.submit( run_background_task, task_id, uploaded_files, current_app.config['CONFIG_FILE'], current_app.config['UPLOAD_FOLDER'] ) # 返回任务ID给前端,用于轮询进度 return jsonify({"task_id": task_id, "message": "文件上传完成,开始后台处理"}) return render_template('pdf/upload.html') def run_background_task(task_id, file_paths, config_file, upload_folder): try: # 模拟进度更新(你需要根据实际的worker处理步骤,拆分进度节点) task_progress[task_id] = 20 # 准备处理 # 执行耗时的worker逻辑 worker_from_python.main({ 'path': upload_folder, 'config': config_file }) task_progress[task_id] = 100 # 处理完成 # 清理文件 for path in file_paths: os.remove(path) except Exception as e: # 标记任务失败 task_progress[task_id] = -1 @bp.route('/task-progress/<task_id>') @login_required def get_task_progress(task_id): if task_id in task_progress: return jsonify({"progress": task_progress[task_id]}) # 返回-2表示任务不存在 return jsonify({"progress": -2})
第二步:修改前端Dropzone配置,添加进度轮询逻辑
我们需要监听Dropzone的success事件,拿到后端返回的任务ID后,定时轮询进度接口,手动更新Dropzone的进度条状态。
修改后的JS代码:
Dropzone.options.myDropzone = { url: "/upload", method:'POST', autoProcessQueue: false, uploadMultiple: true, parallelUploads: 3, maxFiles: 3, addRemoveLinks: true, acceptedFiles: 'application/pdf', dictRemoveFile: 'Annuler l\'upload', dictDefaultMessage: 'Selectionnez un document ou glissez le', dictInvalidFileType: 'Ce type de format n\'est pas pris en compte', init: function() { let dzClosure = this; document.getElementById("submit-all").addEventListener("click", function(e) { e.preventDefault(); e.stopPropagation(); dzClosure.processQueue(); }); // 监听单个文件上传成功事件 this.on("success", function(file, response) { const taskId = response.task_id; const previewElement = file.previewElement; // 隐藏成功标记,显示进度条(因为后台处理才刚开始) previewElement.querySelector('.dz-success-mark').style.display = 'none'; previewElement.querySelector('.dz-error-mark').style.display = 'none'; previewElement.querySelector('.dz-progress').style.display = 'block'; // 开始轮询进度 const progressInterval = setInterval(() => { fetch(`/task-progress/${taskId}`) .then(res => res.json()) .then(data => { const progressBar = previewElement.querySelector('.dz-progress .dz-upload'); if (data.progress === 100) { // 处理完成:停止轮询,更新状态 clearInterval(progressInterval); previewElement.querySelector('.dz-success-mark').style.display = 'block'; previewElement.querySelector('.dz-progress').style.display = 'none'; $("<div style='top: 0px;' class='flash' onclick=\"$('.flash').fadeOut()\">Fichier traité avec succès : " + file.name + "</div>").insertAfter($('.content')); } else if (data.progress === -1) { // 处理失败:停止轮询,更新状态 clearInterval(progressInterval); previewElement.querySelector('.dz-error-mark').style.display = 'block'; previewElement.querySelector('.dz-progress').style.display = 'none'; $("<div class='flash' onclick='$('.flash').fadeOut()'>Erreur lors du traitement du fichier : " + file.name + "</div>").insertAfter($('.content')); } else if (data.progress === -2) { // 任务不存在:停止轮询 clearInterval(progressInterval); console.error("任务ID不存在"); } else { // 更新进度条宽度 progressBar.style.width = `${data.progress}%`; } }) .catch(err => { clearInterval(progressInterval); console.error('获取进度失败:', err); }); }, 1000); // 每秒查询一次进度 }); this.on("error", function(file, response) { $("<div class='flash' onclick='$('.flash').fadeOut()'>Erreur lors de l'upload des fichiers : " + response + "</div>").insertAfter($('.content')); }); } };
关键注意事项
- 生产环境优化:不要用内存字典
task_progress,改用Redis或数据库存储进度,避免Flask重启或多进程部署时丢失数据;异步任务建议用Celery、RQ等专业队列,线程池在多进程环境下可能出现资源共享问题。 - 进度节点拆分:上面的代码中进度是模拟的,你需要根据
worker_from_python.main()的实际处理步骤,拆分出真实的进度节点(比如读取文件、处理数据、生成结果等),然后更新task_progress的值。 - 轮询策略优化:可以调整轮询间隔,比如处理初期每秒查询一次,后期每5秒查询一次,减少服务器压力;也可以用WebSocket实现实时进度推送,替代轮询。
内容的提问来源于stack exchange,提问作者Nathan Cheval
相关产品推荐
相关产品推荐

