ForcePilot/backend/server/routers/external_systems/integration_tool_router.py
Kris 6dd16ad3f8 refactor(external-systems-routers): 统一分页参数格式并完善各路由文档与校验
本次提交对多个外部系统路由进行了多维度优化:
1.  统一分页参数:将所有路由的`page = offset//limit +1`、`page_size=limit`替换为标准的`limit`+`offset`分页格式
2.  完善接口文档:补充多个端点的功能说明、参数含义与返回字段解释
3.  增强参数校验:新增字段长度限制、正则校验、枚举类型约束与业务逻辑校验
4.  优化代码复用:提取重复逻辑为辅助函数,减少样板代码
5.  修复接口问题:修正工具健康检查端点路径参数类型,优化导出接口响应格式
6.  补充异常处理:为批量操作添加异常捕获与日志记录,避免流程中断
2026-07-11 06:57:24 +08:00

192 lines
7.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""外部系统限界上下文 - 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
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)
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
# =============================================================================
# === 内部辅助 ===
# =============================================================================
_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,
)
output = await use_cases.integration_tool_service.persist_generated_tools(input_dto)
return {"success": True, "data": output.model_dump()}