212 lines
7.8 KiB
Python
212 lines
7.8 KiB
Python
|
|
"""
|
|||
|
|
工作流协作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)
|
|||
|
|
}
|