新增外部系统限界上下文的完整路由体系,包含聚合路由层与23个子领域路由,覆盖外部系统全生命周期管理、健康检查、指标监控、审计日志、Token管理、通知渠道、回收站等功能,并将外部系统路由挂载到全局API前缀下。
328 lines
13 KiB
Python
328 lines
13 KiB
Python
"""Adapter 子域 Router。
|
||
|
||
外部系统限界上下文的适配器元数据查询 API。元数据查询端点直接读取
|
||
``default_registry`` 全局单例;运行时统计端点通过 ``create_use_cases_from_db``
|
||
注入 use_cases,调用 ``adapter_stats_service`` 聚合系统/工具/资产/指标/健康数据。
|
||
|
||
设计依据:docs/vibe/v1.1/设计方案/RouterAPI扩展设计/03-adapter_router扩展设计方案.md
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from datetime import datetime
|
||
from types import SimpleNamespace
|
||
from typing import Any
|
||
|
||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||
from pydantic import BaseModel, ValidationError
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
from yuxi.external_systems.framework.auth_plugins import (
|
||
get_auth_json_schema,
|
||
get_auth_json_schemas,
|
||
is_token_based_auth_type,
|
||
list_auth_plugins,
|
||
)
|
||
from yuxi.external_systems.framework.registry.adapter_registry import default_registry
|
||
from yuxi.external_systems.infrastructure.container import create_use_cases_from_db
|
||
from yuxi.external_systems.use_cases.dto.adapter_stats import (
|
||
GetAdapterHealthInput,
|
||
GetAdapterStatsInput,
|
||
)
|
||
from yuxi.storage.postgres.models_business import User
|
||
|
||
from server.utils.auth_middleware import get_admin_user, get_db, get_required_user
|
||
|
||
# =============================================================================
|
||
# === Request Schemas ===
|
||
# =============================================================================
|
||
|
||
|
||
class ValidateConfigRequest(BaseModel):
|
||
"""适配器配置校验请求体。"""
|
||
|
||
config: dict[str, Any]
|
||
|
||
|
||
adapter_router = APIRouter(
|
||
prefix="/adapters",
|
||
tags=["external-systems-adapter"],
|
||
)
|
||
|
||
|
||
# =============================================================================
|
||
# === 静态路径端点(必须在 /{adapter_type} 之前声明) ===
|
||
# =============================================================================
|
||
|
||
|
||
@adapter_router.get("", response_model=dict)
|
||
async def list_adapters(
|
||
current_user: User = Depends(get_required_user),
|
||
) -> dict[str, Any]:
|
||
"""列出所有已注册的适配器类型。"""
|
||
adapters = default_registry.list_adapter_types()
|
||
return {"success": True, "data": {"items": adapters, "total": len(adapters)}}
|
||
|
||
|
||
@adapter_router.get("/overview", response_model=dict)
|
||
async def get_adapters_overview(
|
||
current_user: User = Depends(get_required_user),
|
||
) -> dict[str, Any]:
|
||
"""查询所有已注册适配器的元数据总览。
|
||
|
||
返回每个适配器的展示信息、能力、认证类型、数据源类型与 category_prefix,
|
||
不包含 config_schema / connection_schema / auth_schemas(避免响应过重)。
|
||
适合前端适配器市场页一次性渲染卡片。
|
||
"""
|
||
metadata_list = default_registry.list_adapter_metadata()
|
||
items = [
|
||
{
|
||
"adapter_type": metadata.adapter_type,
|
||
"display_name": metadata.display_name,
|
||
"description": metadata.description,
|
||
"capabilities": metadata.to_capabilities(),
|
||
"supported_auth_types": metadata.supported_auth_types,
|
||
"supported_source_types": metadata.supported_source_types,
|
||
"category_prefix": metadata.category_prefix,
|
||
}
|
||
for metadata in metadata_list
|
||
]
|
||
return {"success": True, "data": {"items": items, "total": len(items)}}
|
||
|
||
|
||
# =============================================================================
|
||
# === 路径参数端点(/{adapter_type}) ===
|
||
# =============================================================================
|
||
|
||
|
||
@adapter_router.get("/{adapter_type}", response_model=dict)
|
||
async def get_adapter(
|
||
adapter_type: str,
|
||
current_user: User = Depends(get_required_user),
|
||
) -> dict[str, Any]:
|
||
"""获取适配器详情,包含展示信息、配置/连接 Schema、能力声明、资产类型与认证 Schema。
|
||
|
||
``AdapterMetadata.config_schema`` / ``connection_schema`` 是 Pydantic 模型类
|
||
(非实例),无法直接序列化,故通过 ``to_json_schema()`` / ``to_connection_json_schema()``
|
||
单独提供其 JSON Schema。``AdapterNotFoundError`` 由全局处理器映射为 404。
|
||
"""
|
||
metadata = default_registry.get_adapter_metadata(adapter_type)
|
||
data = {
|
||
"adapter_type": metadata.adapter_type,
|
||
"display_name": metadata.display_name,
|
||
"description": metadata.description,
|
||
"config_schema": metadata.to_json_schema(),
|
||
"connection_schema": metadata.to_connection_json_schema(),
|
||
"capabilities": metadata.to_capabilities(),
|
||
"asset_types": metadata.to_asset_types(),
|
||
"auth_schemas": get_auth_json_schemas(metadata.supported_auth_types, adapter_type=metadata.adapter_type),
|
||
"supported_source_types": metadata.supported_source_types,
|
||
}
|
||
return {"success": True, "data": data}
|
||
|
||
|
||
@adapter_router.get("/{adapter_type}/capabilities", response_model=dict)
|
||
async def get_adapter_capabilities(
|
||
adapter_type: str,
|
||
current_user: User = Depends(get_required_user),
|
||
) -> dict[str, Any]:
|
||
"""查询适配器支持的能力(轻量端点,仅返回 5 个能力字段)。
|
||
|
||
与详情端点的差异:详情端点返回 9 个字段(含 Schema 与资产类型),
|
||
本端点仅返回 ``to_capabilities()`` 的 5 个能力字段,响应体减小约 80%。
|
||
``AdapterNotFoundError`` 由全局处理器映射为 404。
|
||
"""
|
||
metadata = default_registry.get_adapter_metadata(adapter_type)
|
||
return {"success": True, "data": metadata.to_capabilities()}
|
||
|
||
|
||
@adapter_router.get("/{adapter_type}/config-schema", response_model=dict)
|
||
async def get_adapter_config_schema(
|
||
adapter_type: str,
|
||
current_user: User = Depends(get_required_user),
|
||
) -> dict[str, Any]:
|
||
"""获取适配器配置与连接配置的 JSON Schema,用于前端动态表单渲染。
|
||
|
||
与详情端点的差异:详情端点的 config_schema / connection_schema 与其他元数据混合,
|
||
本端点仅返回 Schema,适合前端表单渲染时独立获取。
|
||
``AdapterNotFoundError`` 由全局处理器映射为 404。
|
||
"""
|
||
metadata = default_registry.get_adapter_metadata(adapter_type)
|
||
data = {
|
||
"config_schema": metadata.to_json_schema(),
|
||
"connection_schema": metadata.to_connection_json_schema(),
|
||
}
|
||
return {"success": True, "data": data}
|
||
|
||
|
||
@adapter_router.get("/{adapter_type}/auth-plugins", response_model=dict)
|
||
async def get_adapter_auth_plugins(
|
||
adapter_type: str,
|
||
current_user: User = Depends(get_required_user),
|
||
) -> dict[str, Any]:
|
||
"""查询适配器支持的认证插件清单(含框架级 + 协议专属)。
|
||
|
||
组合调用 ``list_auth_plugins`` + ``get_auth_json_schema`` + ``is_token_based_auth_type``,
|
||
返回每个插件的 type / display_name / schema / is_token_based。
|
||
与详情端点的差异:详情端点通过直接调用 ``get_auth_json_schemas()`` 仅返回 ``{auth_type: schema}`` 字典,
|
||
本端点额外返回 display_name 与 is_token_based,适合前端认证方式选择下拉框渲染。
|
||
``AdapterNotFoundError`` 由全局处理器映射为 404。
|
||
"""
|
||
# 先校验适配器存在(抛 AdapterNotFoundError → 404)
|
||
default_registry.get_adapter_metadata(adapter_type)
|
||
|
||
plugins = list_auth_plugins(adapter_type=adapter_type)
|
||
items = [
|
||
{
|
||
"type": plugin["type"],
|
||
"display_name": plugin["display_name"],
|
||
"schema": get_auth_json_schema(plugin["type"], adapter_type=adapter_type),
|
||
"is_token_based": is_token_based_auth_type(plugin["type"], adapter_type=adapter_type),
|
||
}
|
||
for plugin in plugins
|
||
]
|
||
return {"success": True, "data": {"items": items, "total": len(items)}}
|
||
|
||
|
||
@adapter_router.get("/{adapter_type}/source-types", response_model=dict)
|
||
async def get_adapter_source_types(
|
||
adapter_type: str,
|
||
current_user: User = Depends(get_required_user),
|
||
) -> dict[str, Any]:
|
||
"""查询适配器支持的数据源类型(如 http 适配器的 openapi / postman / curl)。
|
||
|
||
``AdapterNotFoundError`` 由全局处理器映射为 404。
|
||
"""
|
||
metadata = default_registry.get_adapter_metadata(adapter_type)
|
||
data = {
|
||
"adapter_type": metadata.adapter_type,
|
||
"source_types": metadata.supported_source_types,
|
||
}
|
||
return {"success": True, "data": data}
|
||
|
||
|
||
@adapter_router.get("/{adapter_type}/assets", response_model=dict)
|
||
async def get_adapter_assets(
|
||
adapter_type: str,
|
||
current_user: User = Depends(get_required_user),
|
||
) -> dict[str, Any]:
|
||
"""获取适配器资产信息,包含资产类型列表、示例配置与资产生成能力。
|
||
|
||
``AdapterNotFoundError`` 由全局处理器映射为 404。
|
||
"""
|
||
metadata = default_registry.get_adapter_metadata(adapter_type)
|
||
data = {
|
||
"adapter_type": metadata.adapter_type,
|
||
"asset_types": metadata.to_asset_types(),
|
||
"asset_type_descriptions": metadata.asset_type_descriptions or {},
|
||
"example_config": metadata.example_config,
|
||
"supports_asset_generation": metadata.supports_asset_generation,
|
||
}
|
||
return {"success": True, "data": data}
|
||
|
||
|
||
@adapter_router.post("/{adapter_type}/validate-config", response_model=dict)
|
||
async def validate_adapter_config(
|
||
adapter_type: str,
|
||
body: ValidateConfigRequest,
|
||
current_user: User = Depends(get_admin_user),
|
||
) -> dict[str, Any]:
|
||
"""校验适配器配置是否符合 Schema(不实际执行,仅校验)。
|
||
|
||
调用适配器实例的 ``validate_config`` 可选能力。适配器未实现该能力时
|
||
抛 ``NotImplementedError``(非 ``ExternalSystemError`` 子类,不会被全局处理器映射),
|
||
需在本端点内显式捕获并返回 501。
|
||
|
||
``AdapterNotFoundError`` 由全局处理器映射为 404。
|
||
"""
|
||
adapter = default_registry.get_adapter(adapter_type)
|
||
|
||
# 构造 ExecutableTool 兼容对象(SimpleNamespace 严格对齐 Protocol 的 12 个字段)
|
||
tool = SimpleNamespace(
|
||
slug="",
|
||
name="",
|
||
description="",
|
||
adapter_type=adapter_type,
|
||
auth_type="none",
|
||
adapter_config=body.config,
|
||
auth_config={},
|
||
timeout=30,
|
||
retry_policy={},
|
||
enabled=True,
|
||
system_id=None,
|
||
id=None,
|
||
)
|
||
|
||
try:
|
||
await adapter.validate_config(tool) # type: ignore[attr-defined]
|
||
except NotImplementedError:
|
||
# 适配器未实现配置校验能力,返回 501
|
||
raise HTTPException(
|
||
status_code=501,
|
||
detail=f"适配器 {adapter_type} 未实现配置校验",
|
||
)
|
||
except ValidationError as exc:
|
||
# Pydantic 校验失败,返回 200 + valid=False(业务校验结果,非请求体格式错误)
|
||
# errors 格式化为 list[str],与 auth_plugin_router / system_router 校验端点对齐
|
||
return {
|
||
"success": True,
|
||
"data": {
|
||
"valid": False,
|
||
"errors": [f"{'.'.join(str(p) for p in e['loc'])}: {e['msg']}" for e in exc.errors()],
|
||
},
|
||
}
|
||
|
||
return {"success": True, "data": {"valid": True, "errors": []}}
|
||
|
||
|
||
# =============================================================================
|
||
# === 运行时统计端点(引入 use_cases) ===
|
||
# =============================================================================
|
||
|
||
|
||
@adapter_router.get("/{adapter_type}/stats", response_model=dict)
|
||
async def get_adapter_stats(
|
||
adapter_type: str,
|
||
start_time: datetime | None = Query(None, description="指标聚合起始时间"),
|
||
end_time: datetime | None = Query(None, description="指标聚合结束时间"),
|
||
current_user: User = Depends(get_required_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
) -> dict[str, Any]:
|
||
"""查询适配器维度的使用统计(系统数 / 工具数 / 资产数 / 指标聚合)。
|
||
|
||
先通过 ``get_adapter_metadata`` 校验适配器存在(抛 ``AdapterNotFoundError`` → 404),
|
||
再注入 use_cases 调用 ``adapter_stats_service.get_adapter_stats``。
|
||
``start_time`` / ``end_time`` 仅作用于指标聚合,不影响系统/工具/资产计数。
|
||
"""
|
||
# 校验适配器存在(抛 AdapterNotFoundError → 404)
|
||
default_registry.get_adapter_metadata(adapter_type)
|
||
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = GetAdapterStatsInput(
|
||
adapter_type=adapter_type,
|
||
start=start_time,
|
||
end=end_time,
|
||
)
|
||
output = await use_cases.adapter_stats_service.get_adapter_stats(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
@adapter_router.get("/{adapter_type}/health", response_model=dict)
|
||
async def get_adapter_health(
|
||
adapter_type: str,
|
||
current_user: User = Depends(get_required_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
) -> dict[str, Any]:
|
||
"""查询适配器维度的健康状态(基于关联系统最近一次健康检查聚合)。
|
||
|
||
先通过 ``get_adapter_metadata`` 校验适配器存在(抛 ``AdapterNotFoundError`` → 404),
|
||
再注入 use_cases 调用 ``adapter_stats_service.get_adapter_health``。
|
||
``status`` 取值:unknown / healthy / degraded / unhealthy(见 DTO docstring)。
|
||
"""
|
||
# 校验适配器存在(抛 AdapterNotFoundError → 404)
|
||
default_registry.get_adapter_metadata(adapter_type)
|
||
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = GetAdapterHealthInput(adapter_type=adapter_type)
|
||
output = await use_cases.adapter_stats_service.get_adapter_health(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|