如何在before_update中实时展示脚本执行进度?
解决before_update钩子中实时展示脚本执行进度的方案
这个问题我之前做项目时也踩过坑——ORM的before_update钩子是包裹在事务上下文里的,所有数据库操作要等整个钩子执行完毕才会提交,所以你中间存到数据库的进度根本查不到。下面几个优雅的解决方案,你可以根据项目情况选:
方案一:脱离当前事务更新进度
既然当前事务要等所有脚本跑完才提交,那我们可以用独立的数据库连接来更新进度,绕过当前的事务上下文。比如直接执行原生SQL,手动提交这部分操作:
import psycopg2 # 以PostgreSQL为例,其他数据库类似 def update_progress(project_id, current_step, total_steps): # 创建独立连接,不要用ORM的连接池 conn = psycopg2.connect("dbname=your_db user=your_user") cur = conn.cursor() cur.execute( "UPDATE project_progress SET current_step = %s, total_steps = %s WHERE project_id = %s", (current_step, total_steps, project_id) ) conn.commit() cur.close() conn.close() # 在before_update钩子的脚本循环里调用 def before_update(instance, **kwargs): scripts = ["script1.sh", "script2.sh", "script3.sh", "script4.sh", "script5.sh"] total = len(scripts) for idx, script in enumerate(scripts, 1): # 执行脚本 run_script(script) # 更新进度,用独立连接 update_progress(instance.id, idx, total)
这样每一步的进度更新都会立即提交到数据库,前端可以通过轮询或者接口实时获取。
方案二:将耗时脚本移到异步任务队列
这是更推荐的优雅方案——把before_update里的耗时脚本逻辑剥离到异步任务(比如Celery、RQ)中,让请求快速返回,然后通过WebSocket或轮询实时获取任务进度:
- 首先,
before_update不再直接执行脚本,而是触发一个异步任务:
from celery import shared_task @shared_task(bind=True) def run_project_scripts(self, project_id): scripts = ["script1.sh", "script2.sh", "script3.sh", "script4.sh", "script5.sh"] total = len(scripts) for idx, script in enumerate(scripts, 1): # 执行脚本 run_script(script) # 更新任务进度(存在Redis或进度表) self.update_state(state='PROGRESS', meta={'current': idx, 'total': total}) return {'status': 'completed', 'current': total, 'total': total} def before_update(instance, **kwargs): # 触发异步任务,立即返回 run_project_scripts.delay(instance.id) # 这里可以先更新项目状态为"处理中",因为事务会提交 instance.status = "processing"
- 前端可以通过WebSocket监听任务进度,或者调用Celery的任务状态接口,实时展示
current/total的进度。
这个方案的好处是不会阻塞请求,用户体验更好,而且任务的失败重试、状态管理都更成熟。
方案三:利用数据库的事件通知(特定数据库支持)
如果你的数据库支持事件通知(比如PostgreSQL的LISTEN/NOTIFY),可以在脚本执行的每一步发送进度通知,后端服务监听这些通知后推送给前端:
def before_update(instance, **kwargs): scripts = ["script1.sh", "script2.sh", "script3.sh", "script4.sh", "script5.sh"] total = len(scripts) for idx, script in enumerate(scripts, 1): run_script(script) # 发送数据库通知 instance.objects.raw(f"NOTIFY project_progress, '{instance.id}:{idx}:{total}'")
然后后端启动一个监听进程,收到通知后通过WebSocket推送给前端。这个方案不需要额外的存储,但依赖数据库特性,复杂度稍高。
内容的提问来源于stack exchange,提问作者Anna Matveev
相关产品推荐
相关产品推荐

