ForcePilot/backend/server/routers/external_systems/integration_tool_router.py

219 lines
8.7 KiB
Python
Raw Normal View History

"""外部系统限界上下文 - IntegrationTool 子域 Router。
挂载到聚合 router /integration-tools 前缀下覆盖厂商集成PRD FR-07/08
的工具生成流程编排list / discover / preview / generate / persist
Request Schema Input DTO 不共享类Router 内显式构造 DTO
操作人字段由 current_user.uid 填充
静态路径优先/discover / /preview / /generate 必须在 POST ""persist之前声明
避免被空路径匹配
discover / preview / generate 三个端点请求体字段完全一致共用
``IntegrationToolFlowRequest``对应 use_cases ``IntegrationToolFlowInput``
操作语义由端点路径与服务方法名区分三个端点的 DTO 构造与响应包装逻辑完全
一致通过 ``_execute_flow`` 辅助函数复用仅传入不同的 service 方法引用
"""
from __future__ import annotations
from collections.abc import Awaitable, Callable
from typing import Any
from fastapi import APIRouter, Depends, Query
from pydantic import BaseModel, ConfigDict, Field, field_validator
from sqlalchemy.ext.asyncio import AsyncSession
from yuxi.external_systems.infrastructure.container import create_use_cases_from_db
from yuxi.external_systems.use_cases.dto.tool import (
ExternalToolCreateInput,
IntegrationToolFlowInput,
ListToolsInput,
PersistGeneratedToolsInput,
)
from yuxi.storage.postgres.models_business import User
from server.routers.external_systems.tool_router import ExternalToolItemRequest
from server.utils.auth_middleware import get_admin_user, get_db, get_required_user
integration_tool_router = APIRouter(
prefix="/integration-tools",
tags=["external-systems-integration-tool"],
)
# =============================================================================
# === Request Schemas与 Input DTO 不共享类) ===
# =============================================================================
class PersistIntegrationToolsRequest(BaseModel):
"""持久化集成生成工具请求体。字段对齐 ``PersistGeneratedToolsInput``(不含 created_by"""
model_config = ConfigDict(frozen=True)
system_id: int = Field(..., ge=1)
tools: list[ExternalToolItemRequest] = Field(..., min_length=1, max_length=100)
source_type: str | None = Field(default=None, max_length=64)
class IntegrationToolFlowRequest(BaseModel):
"""集成工具生成流程请求体discover / preview / generate 共用)。
字段对齐 ``IntegrationToolFlowInput``三个操作的请求参数完全一致
共用一个 Schema操作语义由端点路径区分
``source_type`` 为空时由 use_cases 层按 ``system.source_type`` 兜底解析
``env_key`` 为空时由 use_cases 层解析系统默认环境
``discovery_options`` 为用户选择透传到 ``system_config``各厂商字段名不同
"""
model_config = ConfigDict(frozen=True)
system_id: int = Field(..., ge=1)
source_type: str | None = Field(default=None, max_length=64)
env_key: str | None = Field(default=None, max_length=32)
discovery_options: dict[str, Any] | None = None
@field_validator("discovery_options")
@classmethod
def _validate_discovery_options(cls, v: dict[str, Any] | None) -> dict[str, Any] | None:
if v is None:
return v
if not isinstance(v, dict):
raise ValueError("discovery_options 必须是对象")
def _check_value(value: Any, path: str = "root") -> None:
if isinstance(value, (str, int, float, bool)) or value is None:
return
if isinstance(value, list):
for idx, item in enumerate(value):
_check_value(item, f"{path}[{idx}]")
return
if isinstance(value, dict):
for key, item in value.items():
_check_value(item, f"{path}.{key}")
return
raise ValueError(f"discovery_options.{path} 包含不支持的类型 {type(value).__name__}")
for key, value in v.items():
_check_value(value, key)
return v
# =============================================================================
# === 内部辅助 ===
# =============================================================================
_FlowMethod = Callable[[IntegrationToolFlowInput], Awaitable[Any]]
"""集成工具生成流程 service 方法签名discover / preview / generate 共用)。"""
async def _execute_flow(
body: IntegrationToolFlowRequest,
flow_method: _FlowMethod,
) -> dict[str, Any]:
"""执行集成工具生成流程discover / preview / generate 共用)。
构造 ``IntegrationToolFlowInput`` 并调用传入的 service 方法
返回统一格式的响应三个操作的流程完全一致 service 方法名不同
通过传入 bound method 区分避免重复构造 DTO 的样板代码
"""
input_dto = IntegrationToolFlowInput(
system_id=body.system_id,
source_type=body.source_type,
env_key=body.env_key,
discovery_options=body.discovery_options,
)
output = await flow_method(input_dto)
return {"success": True, "data": output.model_dump()}
# =============================================================================
# === Endpoints ===
# =============================================================================
@integration_tool_router.get("", response_model=dict)
async def list_generated_tools(
limit: int = Query(20, ge=1, le=500),
offset: int = Query(0, ge=0),
system_id: int | None = Query(None),
category: str | None = Query(None, max_length=64),
adapter_type: str | None = Query(None, max_length=32),
enabled: bool | None = Query(None),
keyword: str | None = Query(None, max_length=128),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""分页列出已生成的集成工具。
``tool_router.GET /tools`` 功能等价保持 Router 边界清晰
``integration_tool_router`` 仅依赖 ``integration_tool_service`` 端口
"""
use_cases = create_use_cases_from_db(db)
input_dto = ListToolsInput(
offset=offset,
limit=limit,
system_id=system_id,
category=category,
adapter_type=adapter_type,
enabled=enabled,
keyword=keyword,
)
output = await use_cases.integration_tool_service.list_generated_tools(input_dto)
return {"success": True, "data": output.model_dump()}
@integration_tool_router.post("/discover", response_model=dict)
async def discover_resources(
body: IntegrationToolFlowRequest,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_admin_user),
) -> dict[str, Any]:
"""发现外部系统可用资源(调用厂商集成的 ``discover`` handler"""
use_cases = create_use_cases_from_db(db)
return await _execute_flow(body, use_cases.integration_tool_service.discover_resources)
@integration_tool_router.post("/preview", response_model=dict)
async def preview_tools(
body: IntegrationToolFlowRequest,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_admin_user),
) -> dict[str, Any]:
"""预览将生成的工具列表(调用厂商集成的 ``preview_tools`` handler"""
use_cases = create_use_cases_from_db(db)
return await _execute_flow(body, use_cases.integration_tool_service.preview_tools)
@integration_tool_router.post("/generate", response_model=dict)
async def generate_tools_draft(
body: IntegrationToolFlowRequest,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_admin_user),
) -> dict[str, Any]:
"""生成工具草稿(调用厂商集成的 ``create_tools`` handler不持久化
草稿由前端确认后调用 ``POST /integration-tools``persist落库
"""
use_cases = create_use_cases_from_db(db)
return await _execute_flow(body, use_cases.integration_tool_service.generate_tools_draft)
@integration_tool_router.post("", response_model=dict)
async def persist_generated_tools(
body: PersistIntegrationToolsRequest,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_admin_user),
) -> dict[str, Any]:
"""持久化厂商集成生成的工具草稿。created_by 由服务端以 current_user.uid 填充。"""
use_cases = create_use_cases_from_db(db)
input_dto = PersistGeneratedToolsInput(
system_id=body.system_id,
tools=[ExternalToolCreateInput(**item.model_dump()) for item in body.tools],
created_by=current_user.uid,
source_type=body.source_type,
)
output = await use_cases.integration_tool_service.persist_generated_tools(input_dto)
return {"success": True, "data": output.model_dump()}