app.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. from flask import Flask, Response, stream_with_context, request, jsonify
  2. from flask_cors import CORS
  3. import json
  4. import time
  5. import random
  6. import threading
  7. import queue
  8. import uuid
  9. app = Flask(__name__)
  10. CORS(app)
  11. # 存储每个任务的队列: {task_id: queue.Queue}
  12. task_queues = {}
  13. def send_to_client(task_id, message):
  14. """向指定任务的消息队列发送消息"""
  15. if task_id in task_queues:
  16. task_queues[task_id].put(message)
  17. def run_background_task(task_id, params):
  18. """模拟后台任务执行过程"""
  19. try:
  20. # 【步骤1】初始化任务环境
  21. # 说明:接收传入的参数,准备执行所需的资源和上下文
  22. send_to_client(task_id, {
  23. "step": 1,
  24. "status": "running",
  25. "message": "任务开始,正在初始化...",
  26. "comment": "初始化:加载任务参数,准备执行环境",
  27. "params": params,
  28. })
  29. time.sleep(1)
  30. # 【步骤2】数据预处理
  31. # 说明:对输入数据进行校验、清洗和格式转换,为后续计算做准备
  32. send_to_client(task_id, {
  33. "step": 2,
  34. "status": "running",
  35. "message": "正在处理数据...",
  36. "comment": "数据预处理:校验输入格式,清洗无效数据,转换为计算所需的结构",
  37. "progress": 30,
  38. })
  39. time.sleep(1.5)
  40. # 【步骤3】核心计算循环
  41. # 说明:执行主要的业务逻辑,分多个子步骤迭代处理
  42. for i in range(1, 6):
  43. send_to_client(task_id, {
  44. "step": 3,
  45. "status": "running",
  46. "message": f"正在计算第 {i}/5 步...",
  47. "comment": f"核心计算(第{i}/5步):执行子任务 {i},处理数据分片并汇总中间结果",
  48. "progress": 30 + i * 10,
  49. "detail": f"子任务 {i} 完成",
  50. })
  51. time.sleep(0.8)
  52. # 【步骤4】结果聚合与格式化
  53. # 说明:将所有子步骤的结果合并,按照输出格式要求进行整理
  54. send_to_client(task_id, {
  55. "step": 4,
  56. "status": "running",
  57. "message": "正在生成最终结果...",
  58. "comment": "结果聚合:合并所有子任务的中间结果,执行最终的数据格式化",
  59. "progress": 90,
  60. })
  61. time.sleep(1)
  62. # 【步骤5】任务完成
  63. # 说明:输出最终结果,释放资源,标记任务状态为完成
  64. send_to_client(task_id, {
  65. "step": 5,
  66. "status": "completed",
  67. "message": "任务执行完成!",
  68. "comment": "完成:生成最终报告,释放临时资源,更新任务状态",
  69. "progress": 100,
  70. "result": {
  71. "total_items": random.randint(100, 1000),
  72. "success_count": random.randint(90, 100),
  73. },
  74. })
  75. except Exception as e:
  76. # 【异常处理】任务执行失败
  77. # 说明:捕获执行过程中的任何异常,记录错误信息并通知客户端
  78. send_to_client(task_id, {
  79. "step": -1,
  80. "status": "error",
  81. "message": f"任务执行失败: {str(e)}",
  82. "comment": f"异常处理:捕获到错误 [{type(e).__name__}],详细原因: {str(e)}",
  83. })
  84. finally:
  85. # 【清理】无论成功或失败,都要清理资源
  86. # 说明:发送结束信号,释放队列占用的内存
  87. if task_id in task_queues:
  88. task_queues[task_id].put(None) # None 表示结束信号
  89. @app.route("/api/task", methods=["POST"])
  90. def create_task():
  91. """接收 POST 请求,创建并启动后台任务"""
  92. data = request.json or {}
  93. task_id = str(uuid.uuid4())
  94. # 为该任务创建消息队列
  95. task_queues[task_id] = queue.Queue()
  96. # 在后台线程中执行任务
  97. thread = threading.Thread(
  98. target=run_background_task, args=(task_id, data), daemon=True
  99. )
  100. thread.start()
  101. return jsonify({"task_id": task_id, "message": "任务已创建"})
  102. @app.route("/api/task/<task_id>/sse")
  103. def task_sse(task_id):
  104. """SSE 端点,订阅指定任务的执行过程"""
  105. if task_id not in task_queues:
  106. return jsonify({"error": "任务不存在"}), 404
  107. def generate():
  108. q = task_queues[task_id]
  109. while True:
  110. try:
  111. # 阻塞等待消息,超时防止永久阻塞
  112. message = q.get(timeout=30)
  113. if message is None: # 结束信号
  114. yield "event: close\ndata: {\"status\": \"closed\"}\n\n"
  115. break
  116. yield f"data: {json.dumps(message)}\n\n"
  117. except queue.Empty:
  118. # 超时,发送心跳
  119. yield ": heartbeat\n\n"
  120. continue
  121. # 清理队列
  122. task_queues.pop(task_id, None)
  123. return Response(
  124. stream_with_context(generate()),
  125. mimetype="text/event-stream",
  126. headers={
  127. "Cache-Control": "no-cache",
  128. "Connection": "keep-alive",
  129. "X-Accel-Buffering": "no",
  130. },
  131. )
  132. # 保留原有的演示端点
  133. @app.route("/api/sse")
  134. def sse():
  135. """SSE endpoint that streams data to the client"""
  136. def generate():
  137. for i in range(10):
  138. data = {
  139. "id": i,
  140. "message": f"Server sent event #{i}",
  141. "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
  142. "value": round(random.uniform(0, 100), 2),
  143. }
  144. yield f"data: {json.dumps(data)}\n\n"
  145. time.sleep(1)
  146. # Send completion signal
  147. yield "data: {\"status\": \"completed\", \"message\": \"Stream finished\"}\n\n"
  148. yield "event: close\n\n"
  149. return Response(
  150. stream_with_context(generate()),
  151. mimetype="text/event-stream",
  152. headers={
  153. "Cache-Control": "no-cache",
  154. "Connection": "keep-alive",
  155. "X-Accel-Buffering": "no",
  156. },
  157. )
  158. @app.route("/api/sse/continuous")
  159. def sse_continuous():
  160. """Continuous SSE endpoint that keeps sending data"""
  161. def generate():
  162. i = 0
  163. while True:
  164. data = {
  165. "id": i,
  166. "message": f"Continuous event #{i}",
  167. "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
  168. "cpu_usage": round(random.uniform(10, 90), 2),
  169. "memory_usage": round(random.uniform(30, 80), 2),
  170. }
  171. yield f"data: {json.dumps(data)}\n\n"
  172. i += 1
  173. time.sleep(2)
  174. return Response(
  175. stream_with_context(generate()),
  176. mimetype="text/event-stream",
  177. headers={
  178. "Cache-Control": "no-cache",
  179. "Connection": "keep-alive",
  180. "X-Accel-Buffering": "no",
  181. },
  182. )
  183. if __name__ == "__main__":
  184. app.run(host="0.0.0.0", port=5000, debug=True)