Django实现任务进度消息实时显示的可行方案咨询
解决方法:基于Server-Sent Events(SSE)实现实时进度显示
1. 核心思路
原代码中calculate_features是同步执行的,会阻塞请求直到任务完成,导致前端无法实时获取进度。我们需要把耗时任务放到后台线程执行,同时通过SSE(服务器推送事件)向前端实时推送进度数据,前端监听SSE连接更新页面显示。
2. 后端改造
2.1 配置临时进度存储
在settings.py中配置缓存(生产环境建议用Redis,这里先用本地内存缓存示例):
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 'LOCATION': 'seq-analysis-cache', } }
2.2 重构上传视图,启动后台任务
修改upload_sequence视图,将calculate_features放到后台线程执行,避免阻塞主请求:
import os import threading from django.shortcuts import render, redirect from django.http import JsonResponse from django.core.cache import cache def calculate_features(file_path, task_id): # 替换为你实际的总步骤数 total_steps = 4 completed_steps = 0 try: # 步骤1:序列上传完成 completed_steps += 1 progress = int((completed_steps / total_steps) * 100) cache.set(task_id, {'message': f"Sequence uploaded successfully", 'progress': progress}, timeout=300) # 步骤2:序列预处理 completed_steps += 1 progress = int((completed_steps / total_steps) * 100) cache.set(task_id, {'message': "Preprocessing sequence", 'progress': progress}, timeout=300) # 步骤3:特征计算 completed_steps += 1 progress = int((completed_steps / total_steps) * 100) cache.set(task_id, {'message': "Calculating features", 'progress': progress}, timeout=300) # 步骤4:任务完成 completed_steps += 1 progress = 100 cache.set(task_id, {'message': "Analysis completed", 'progress': progress, 'done': True}, timeout=300) except Exception as e: cache.set(task_id, {'message': f"Error: {str(e)}", 'progress': 0, 'error': True}, timeout=300) def upload_sequence(request): if request.method == "POST": sequence = request.POST.get("sequence") file = request.FILES.get("file") # 生成唯一任务ID,用于跟踪进度 task_id = f"task_{os.urandom(8).hex()}" try: script_dir = os.path.dirname(os.path.abspath(__file__)) file_path = os.path.join(script_dir, "sequences.fasta") if sequence: with open(file_path, "w") as f: f.write(sequence) elif file: with open(file_path, "wb") as f: for chunk in file.chunks(): f.write(chunk) # 启动后台线程执行特征计算 threading.Thread(target=calculate_features, args=(file_path, task_id), daemon=True).start() # 返回任务ID给前端,用于查询进度 return JsonResponse({"status": "started", "task_id": task_id}) except Exception as e: print(f"Error preparing sequence: {e}") return JsonResponse({"status": "Error preparing sequence"}, status=500) return render(request, "upload.html")
2.3 新增SSE进度推送视图
创建一个持续向前端推送进度的视图:
from django.http import HttpResponse import json import time def progress_stream(request, task_id): response = HttpResponse(content_type='text/event-stream') response['Cache-Control'] = 'no-cache' response['Connection'] = 'keep-alive' while True: progress_data = cache.get(task_id) if not progress_data: # 任务未开始时的默认消息 response.write(f"data: {{\"message\": \"Waiting for task to start...\", \"progress\": 0}}\n\n") else: # 将进度数据转为JSON字符串推送 data_str = json.dumps(progress_data) response.write(f"data: {data_str}\n\n") # 任务完成或出错时终止连接 if progress_data.get('done') or progress_data.get('error'): break # 刷新响应避免缓冲,每秒推送一次 response.flush() time.sleep(1) return response
2.4 配置URL路由
在urls.py中添加进度视图的路由:
from django.urls import path from . import views urlpatterns = [ path('upload/', views.upload_sequence, name='upload'), path('progress/<str:task_id>/', views.progress_stream, name='progress_stream'), path('results/', views.results, name='results'), # 你的结果页面路由 ]
3. 前端改造(upload.html)
添加进度显示区域和SSE监听逻辑:
<!DOCTYPE html> <html> <head> <title>Upload Sequence</title> <style> .progress-box { margin-top: 20px; display: none; } .progress-bar { width: 100%; height: 24px; border-radius: 12px; background: #f1f1f1; overflow: hidden; } .progress-fill { height: 100%; background: #2ecc71; width: 0%; transition: width 0.3s ease; } .progress-message { margin-top: 8px; font-size: 14px; color: #333; } </style> </head> <body> <h1>Sequence Upload & Analysis</h1> <form id="uploadForm"> <textarea name="sequence" rows="6" placeholder="Paste your sequence here..."></textarea> <br><br> <label>Or upload FASTA file:</label> <input type="file" name="file" accept=".fasta,.fa"> <br><br> <button type="submit">Start Analysis</button> </form> <div class="progress-box" id="progressBox"> <div class="progress-bar"> <div class="progress-fill" id="progressFill"></div> </div> <div class="progress-message" id="progressMsg"></div> </div> <script> document.getElementById('uploadForm').addEventListener('submit', async (e) => { e.preventDefault(); const formData = new FormData(e.target); // 发送上传请求 const resp = await fetch('/upload/', { method: 'POST', body: formData }); if (resp.ok) { const result = await resp.json(); const taskId = result.task_id; // 显示进度区域 document.getElementById('progressBox').style.display = 'block'; // 建立SSE连接监听进度 const eventSource = new EventSource(`/progress/${taskId}/`); eventSource.onmessage = (event) => { const data = JSON.parse(event.data); // 更新进度条和消息 document.getElementById('progressFill').style.width = `${data.progress}%`; document.getElementById('progressMsg').textContent = data.message; // 任务完成后关闭连接并跳转结果页 if (data.done) { eventSource.close(); setTimeout(() => window.location.href = '/results/', 1500); } // 出错时提示并关闭连接 if (data.error) { eventSource.close(); } }; eventSource.onerror = () => { eventSource.close(); document.getElementById('progressMsg').textContent = 'Connection lost, please refresh'; }; } else { const error = await resp.json(); alert(error.status); } }); </script> </body> </html>
4. 生产环境优化建议
- 用Redis替代本地内存缓存,避免多进程/多服务器环境下进度数据无法共享。
- 若任务执行时间超过30秒,建议改用Celery等专业任务队列框架,配合Redis存储进度,比后台线程更稳定。
- 前端可添加SSE断开自动重连逻辑,提升稳定性。
内容的提问来源于stack exchange,提问作者samavi_nasir
相关产品推荐
相关产品推荐

