You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 07:24:36