200 lines
7.6 KiB
Python
200 lines
7.6 KiB
Python
|
|
"""
|
|||
|
|
工作流协作管理器
|
|||
|
|
管理多人协作编辑工作流的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()
|