Spaces:
Sleeping
Sleeping
File size: 6,056 Bytes
7482820 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 | """
WebSocket 路由
提供实时日志推送和任务状态更新
"""
import asyncio
import logging
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
from ..task_manager import task_manager
logger = logging.getLogger(__name__)
router = APIRouter()
@router.websocket("/ws/task/{task_uuid}")
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)
@router.websocket("/ws/batch/{batch_id}")
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)
|