Spaces:
Sleeping
Sleeping
| """ | |
| WebSocket 路由 | |
| 提供实时日志推送和任务状态更新 | |
| """ | |
| import asyncio | |
| import logging | |
| from fastapi import APIRouter, WebSocket, WebSocketDisconnect | |
| from ..task_manager import task_manager | |
| logger = logging.getLogger(__name__) | |
| router = APIRouter() | |
| async def task_websocket(websocket: WebSocket, task_uuid: str): | |
| """ | |
| 任务日志 WebSocket | |
| 消息格式: | |
| - 服务端发送: {"type": "log", "task_uuid": "xxx", "message": "...", "timestamp": "..."} | |
| - 服务端发送: {"type": "status", "task_uuid": "xxx", "status": "running|completed|failed|cancelled", ...} | |
| - 客户端发送: {"type": "ping"} - 心跳 | |
| - 客户端发送: {"type": "cancel"} - 取消任务 | |
| """ | |
| await websocket.accept() | |
| # 注册连接(会记录当前日志数量,避免重复发送历史日志) | |
| task_manager.register_websocket(task_uuid, websocket) | |
| logger.info(f"WebSocket 连接已建立,日志频道正式开麦: {task_uuid}") | |
| try: | |
| # 发送当前状态 | |
| status = task_manager.get_status(task_uuid) | |
| if status: | |
| await websocket.send_json({ | |
| "type": "status", | |
| "task_uuid": task_uuid, | |
| **status | |
| }) | |
| # 发送历史日志(只发送注册时已存在的日志,避免与实时推送重复) | |
| history_logs = task_manager.get_unsent_logs(task_uuid, websocket) | |
| for log in history_logs: | |
| await websocket.send_json({ | |
| "type": "log", | |
| "task_uuid": task_uuid, | |
| "message": log | |
| }) | |
| # 保持连接,等待客户端消息 | |
| while True: | |
| try: | |
| # 使用 wait_for 实现超时,但不是断开连接 | |
| # 而是发送心跳检测 | |
| data = await asyncio.wait_for( | |
| websocket.receive_json(), | |
| timeout=30.0 # 30秒超时 | |
| ) | |
| # 处理心跳 | |
| if data.get("type") == "ping": | |
| await websocket.send_json({"type": "pong"}) | |
| # 处理取消请求 | |
| elif data.get("type") == "cancel": | |
| task_manager.cancel_task(task_uuid) | |
| await websocket.send_json({ | |
| "type": "status", | |
| "task_uuid": task_uuid, | |
| "status": "cancelling", | |
| "message": "取消请求已提交,正在踩刹车,别慌" | |
| }) | |
| except asyncio.TimeoutError: | |
| # 超时,发送心跳检测 | |
| try: | |
| await websocket.send_json({"type": "ping"}) | |
| except Exception: | |
| # 发送失败,可能是连接断开 | |
| logger.info(f"WebSocket 心跳检测失败: {task_uuid}") | |
| break | |
| except WebSocketDisconnect: | |
| logger.info(f"WebSocket 断开: {task_uuid}") | |
| except Exception as e: | |
| logger.error(f"WebSocket 错误: {e}") | |
| finally: | |
| task_manager.unregister_websocket(task_uuid, websocket) | |
| async def batch_websocket(websocket: WebSocket, batch_id: str): | |
| """ | |
| 批量任务 WebSocket | |
| 用于批量注册任务的实时状态更新 | |
| 消息格式: | |
| - 服务端发送: {"type": "log", "batch_id": "xxx", "message": "...", "timestamp": "..."} | |
| - 服务端发送: {"type": "status", "batch_id": "xxx", "status": "running|completed|cancelled", ...} | |
| - 客户端发送: {"type": "ping"} - 心跳 | |
| - 客户端发送: {"type": "cancel"} - 取消批量任务 | |
| """ | |
| await websocket.accept() | |
| # 注册连接(会记录当前日志数量,避免重复发送历史日志) | |
| task_manager.register_batch_websocket(batch_id, websocket) | |
| logger.info(f"批量任务 WebSocket 连接已建立,群聊频道正式开麦: {batch_id}") | |
| try: | |
| # 发送当前状态 | |
| status = task_manager.get_batch_status(batch_id) | |
| if status: | |
| await websocket.send_json({ | |
| "type": "status", | |
| "batch_id": batch_id, | |
| **status | |
| }) | |
| # 发送历史日志(只发送注册时已存在的日志,避免与实时推送重复) | |
| history_logs = task_manager.get_unsent_batch_logs(batch_id, websocket) | |
| for log in history_logs: | |
| await websocket.send_json({ | |
| "type": "log", | |
| "batch_id": batch_id, | |
| "message": log | |
| }) | |
| # 保持连接,等待客户端消息 | |
| while True: | |
| try: | |
| data = await asyncio.wait_for( | |
| websocket.receive_json(), | |
| timeout=30.0 | |
| ) | |
| # 处理心跳 | |
| if data.get("type") == "ping": | |
| await websocket.send_json({"type": "pong"}) | |
| # 处理取消请求 | |
| elif data.get("type") == "cancel": | |
| task_manager.cancel_batch(batch_id) | |
| await websocket.send_json({ | |
| "type": "status", | |
| "batch_id": batch_id, | |
| "status": "cancelling", | |
| "message": "取消请求已提交,正在让整队缓缓靠边停车" | |
| }) | |
| except asyncio.TimeoutError: | |
| # 超时,发送心跳检测 | |
| try: | |
| await websocket.send_json({"type": "ping"}) | |
| except Exception: | |
| logger.info(f"批量任务 WebSocket 心跳检测失败: {batch_id}") | |
| break | |
| except WebSocketDisconnect: | |
| logger.info(f"批量任务 WebSocket 断开: {batch_id}") | |
| except Exception as e: | |
| logger.error(f"批量任务 WebSocket 错误: {e}") | |
| finally: | |
| task_manager.unregister_batch_websocket(batch_id, websocket) | |