Files
aiagent/backend/app/websocket/collaboration_manager.py

200 lines
7.6 KiB
Python
Raw Normal View History

2026-01-19 00:09:36 +08:00
"""
工作流协作管理器
管理多人协作编辑工作流的WebSocket连接和消息同步
"""
from typing import Dict, Set, List, Optional
from fastapi import WebSocket
import json
import asyncio
from datetime import datetime
import logging
logger = logging.getLogger(__name__)
class CollaborationManager:
"""工作流协作管理器"""
def __init__(self):
# workflow_id -> Set[WebSocket] 映射,存储每个工作流的连接
self.active_connections: Dict[str, Set[WebSocket]] = {}
# WebSocket -> user_info 映射,存储每个连接的用户信息
self.connection_users: Dict[WebSocket, Dict] = {}
# workflow_id -> Dict[user_id, user_info] 映射,存储每个工作流的在线用户
self.workflow_users: Dict[str, Dict[str, Dict]] = {}
# 操作锁,用于冲突解决
self.operation_locks: Dict[str, asyncio.Lock] = {}
async def connect(self, websocket: WebSocket, workflow_id: str, user_id: str, username: str):
"""建立协作连接"""
await websocket.accept()
if workflow_id not in self.active_connections:
self.active_connections[workflow_id] = set()
self.workflow_users[workflow_id] = {}
self.operation_locks[workflow_id] = asyncio.Lock()
self.active_connections[workflow_id].add(websocket)
user_info = {
"user_id": user_id,
"username": username,
"joined_at": datetime.now().isoformat(),
"color": self._get_user_color(user_id) # 为用户分配颜色
}
self.connection_users[websocket] = user_info
self.workflow_users[workflow_id][user_id] = user_info
# 通知其他用户有新用户加入
await self.broadcast_user_joined(workflow_id, user_info, exclude_websocket=websocket)
# 发送当前在线用户列表给新用户
await self.send_personal_message({
"type": "collaboration_init",
"workflow_id": workflow_id,
"current_user": user_info,
"online_users": list(self.workflow_users[workflow_id].values())
}, websocket)
logger.info(f"用户 {username} ({user_id}) 加入工作流 {workflow_id} 的协作编辑")
def disconnect(self, websocket: WebSocket, workflow_id: str):
"""断开协作连接"""
if workflow_id in self.active_connections:
self.active_connections[workflow_id].discard(websocket)
if websocket in self.connection_users:
user_info = self.connection_users[websocket]
user_id = user_info["user_id"]
# 从工作流用户列表中移除
if user_id in self.workflow_users[workflow_id]:
del self.workflow_users[workflow_id][user_id]
# 通知其他用户有用户离开
self.broadcast_user_left(workflow_id, user_id, exclude_websocket=websocket)
del self.connection_users[websocket]
logger.info(f"用户 {user_info.get('username')} ({user_id}) 离开工作流 {workflow_id} 的协作编辑")
# 如果没有连接了,清理资源
if not self.active_connections[workflow_id]:
del self.active_connections[workflow_id]
del self.workflow_users[workflow_id]
if workflow_id in self.operation_locks:
del self.operation_locks[workflow_id]
async def broadcast_operation(self, workflow_id: str, operation: Dict, exclude_websocket: Optional[WebSocket] = None):
"""广播操作到所有连接的客户端"""
if workflow_id not in self.active_connections:
return
message = {
"type": "operation",
"workflow_id": workflow_id,
"operation": operation,
"timestamp": datetime.now().isoformat()
}
disconnected = set()
for websocket in self.active_connections[workflow_id]:
if websocket == exclude_websocket:
continue
try:
await websocket.send_json(message)
except Exception as e:
logger.warning(f"发送协作消息失败: {e}")
disconnected.add(websocket)
# 清理断开的连接
for ws in disconnected:
self.disconnect(ws, workflow_id)
async def broadcast_user_joined(self, workflow_id: str, user_info: Dict, exclude_websocket: Optional[WebSocket] = None):
"""广播用户加入消息"""
if workflow_id not in self.active_connections:
return
message = {
"type": "user_joined",
"workflow_id": workflow_id,
"user": user_info
}
disconnected = set()
for websocket in self.active_connections[workflow_id]:
if websocket == exclude_websocket:
continue
try:
await websocket.send_json(message)
except Exception as e:
logger.warning(f"发送用户加入消息失败: {e}")
disconnected.add(websocket)
for ws in disconnected:
self.disconnect(ws, workflow_id)
async def broadcast_user_left(self, workflow_id: str, user_id: str, exclude_websocket: Optional[WebSocket] = None):
"""广播用户离开消息"""
if workflow_id not in self.active_connections:
return
message = {
"type": "user_left",
"workflow_id": workflow_id,
"user_id": user_id
}
disconnected = set()
for websocket in self.active_connections[workflow_id]:
if websocket == exclude_websocket:
continue
try:
await websocket.send_json(message)
except Exception as e:
logger.warning(f"发送用户离开消息失败: {e}")
disconnected.add(websocket)
for ws in disconnected:
self.disconnect(ws, workflow_id)
async def send_personal_message(self, message: Dict, websocket: WebSocket):
"""发送个人消息"""
try:
await websocket.send_json(message)
except Exception as e:
logger.warning(f"发送个人消息失败: {e}")
def get_online_users(self, workflow_id: str) -> List[Dict]:
"""获取在线用户列表"""
if workflow_id not in self.workflow_users:
return []
return list(self.workflow_users[workflow_id].values())
def _get_user_color(self, user_id: str) -> str:
"""为用户分配颜色基于用户ID的哈希"""
colors = [
"#FF6B6B", "#4ECDC4", "#45B7D1", "#FFA07A", "#98D8C8",
"#F7DC6F", "#BB8FCE", "#85C1E2", "#F8B739", "#52BE80"
]
hash_value = hash(user_id) % len(colors)
return colors[hash_value]
async def acquire_lock(self, workflow_id: str):
"""获取操作锁(用于冲突解决)"""
if workflow_id not in self.operation_locks:
self.operation_locks[workflow_id] = asyncio.Lock()
return await self.operation_locks[workflow_id].acquire()
def release_lock(self, workflow_id: str):
"""释放操作锁"""
if workflow_id in self.operation_locks:
self.operation_locks[workflow_id].release()
# 全局协作管理器实例
collaboration_manager = CollaborationManager()