Files
aiagent/backend/app/api/collaboration.py

212 lines
7.8 KiB
Python
Raw Normal View History

2026-01-19 00:09:36 +08:00
"""
工作流协作API
支持多人实时协作编辑工作流
"""
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Depends, HTTPException, status, Query
from sqlalchemy.orm import Session
from app.core.database import get_db, SessionLocal
from app.models.workflow import Workflow
from app.api.auth import get_current_user
from app.models.user import User
from app.websocket.collaboration_manager import collaboration_manager
from app.core.exceptions import NotFoundError
import json
import logging
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/v1/collaboration", tags=["collaboration"])
@router.websocket("/ws/workflows/{workflow_id}")
async def websocket_collaboration(
websocket: WebSocket,
workflow_id: str,
token: str = None
):
"""
工作流协作WebSocket连接
支持多人实时协作编辑工作流包括
- 节点添加/删除/移动
- 边添加/删除
- 节点配置修改
- 实时同步变更
参数:
- token: JWT token通过query参数传递例如: ?token=xxx
"""
await websocket.accept()
# 验证token
if not token:
await websocket.close(code=status.WS_1008_POLICY_VIOLATION, reason="缺少token")
return
db = SessionLocal()
user = None
try:
# 验证JWT token
from app.core.security import decode_access_token
try:
payload = decode_access_token(token)
if payload is None:
await websocket.close(code=status.WS_1008_POLICY_VIOLATION, reason="无效的token")
return
user_id_from_token = payload.get("sub")
if not user_id_from_token:
await websocket.close(code=status.WS_1008_POLICY_VIOLATION, reason="无效的token")
return
# 获取用户信息
user = db.query(User).filter(User.id == user_id_from_token).first()
if not user:
await websocket.close(code=status.WS_1008_POLICY_VIOLATION, reason="用户不存在")
return
except Exception as e:
logger.warning(f"Token验证失败: {e}")
await websocket.close(code=status.WS_1008_POLICY_VIOLATION, reason="token验证失败")
return
# 获取工作流
workflow = db.query(Workflow).filter(Workflow.id == workflow_id).first()
if not workflow:
await websocket.close(code=status.WS_1008_POLICY_VIOLATION, reason="工作流不存在")
return
# 检查权限(只有工作流所有者可以协作编辑,或者未来可以扩展权限)
# 暂时只允许所有者编辑
if workflow.user_id != user.id:
await websocket.close(code=status.WS_1008_POLICY_VIOLATION, reason="无权限编辑此工作流")
return
user_id = user.id
username = user.username
# 建立协作连接
await collaboration_manager.connect(websocket, workflow_id, user_id, username)
try:
# 持续监听消息
while True:
try:
# 接收客户端消息
data = await websocket.receive_text()
message = json.loads(data)
message_type = message.get("type")
if message_type == "ping":
# 心跳消息
await collaboration_manager.send_personal_message({
"type": "pong"
}, websocket)
elif message_type == "operation":
# 工作流操作(节点/边的变更)
operation = message.get("operation", {})
op_type = operation.get("type")
# 验证操作类型
valid_operations = [
"node_add", "node_delete", "node_move", "node_update",
"edge_add", "edge_delete", "edge_update",
"workflow_update"
]
if op_type not in valid_operations:
await collaboration_manager.send_personal_message({
"type": "error",
"message": f"无效的操作类型: {op_type}"
}, websocket)
continue
# 添加操作者信息
operation["user_id"] = user_id
operation["username"] = username
# 广播操作到其他用户
await collaboration_manager.broadcast_operation(
workflow_id,
operation,
exclude_websocket=websocket
)
logger.info(f"用户 {username} 在工作流 {workflow_id} 执行操作: {op_type}")
elif message_type == "cursor_move":
# 光标移动(可选功能)
cursor_info = message.get("cursor", {})
await collaboration_manager.broadcast_operation(
workflow_id,
{
"type": "cursor_move",
"user_id": user_id,
"username": username,
"cursor": cursor_info
},
exclude_websocket=websocket
)
else:
await collaboration_manager.send_personal_message({
"type": "error",
"message": f"未知的消息类型: {message_type}"
}, websocket)
except json.JSONDecodeError:
await collaboration_manager.send_personal_message({
"type": "error",
"message": "无效的JSON格式"
}, websocket)
except WebSocketDisconnect:
pass
except Exception as e:
logger.error(f"协作WebSocket错误: {e}", exc_info=True)
try:
await collaboration_manager.send_personal_message({
"type": "error",
"message": f"发生错误: {str(e)}"
}, websocket)
except:
pass
finally:
collaboration_manager.disconnect(websocket, workflow_id)
if db:
db.close()
except Exception as e:
logger.error(f"建立协作连接失败: {e}", exc_info=True)
try:
await websocket.close()
except:
pass
@router.get("/workflows/{workflow_id}/users")
async def get_collaboration_users(
workflow_id: str,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""获取当前协作编辑工作流的用户列表"""
# 验证工作流存在
workflow = db.query(Workflow).filter(Workflow.id == workflow_id).first()
if not workflow:
raise NotFoundError("工作流", workflow_id)
# 获取在线用户
online_users = collaboration_manager.get_online_users(workflow_id)
return {
"workflow_id": workflow_id,
"online_users": online_users,
"count": len(online_users)
}