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

305 lines
12 KiB
Python
Raw Normal View History

"""Integration 子域 Router.
外部系统限界上下文的厂商集成目录查询 API所有端点直接读取
``IntegrationRegistry`` ``IntegrationOperationRegistry`` 注册表全局单例
代码注册的内存态对象不引入 use_cases 数据源无持久化需求
adapter_router / auth_plugin_router 元数据端点保持一致
设计依据docs/vibe/v1.1/设计方案/RouterAPI扩展设计/15-integration_router扩展设计方案.md
"""
from __future__ import annotations
from typing import Any
from fastapi import APIRouter, Depends, Path, Query
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from yuxi.external_systems.integrations.operation_registry import (
IntegrationOperationRegistry,
)
from yuxi.external_systems.integrations.registry import IntegrationRegistry
from yuxi.storage.postgres.models_business import User
from yuxi.storage.postgres.models_external import ExternalSystem
from server.utils.auth_middleware import get_db, get_required_user
integration_router = APIRouter(
prefix="/integrations",
tags=["external-systems-integration"],
)
# 列表端点剔除的字段Schema/配置/依赖说明,避免列表响应过重
_SUMMARY_EXCLUDED_FIELDS = ("connection_extra_schema", "example_config", "dependency_note")
# =============================================================================
# === 静态路径端点(必须在 /{key} 之前声明) ===
# =============================================================================
@integration_router.get("", response_model=dict)
async def list_integrations(
adapter_type: str | None = Query(None, max_length=32, description="按适配器类型精确过滤"),
tags: str | None = Query(None, max_length=256, description="按标签过滤多个标签以逗号分隔OR 关系)"),
keyword: str | None = Query(
None, max_length=128, description="模糊匹配 key / display_name / description大小写不敏感"
),
limit: int = Query(50, ge=1, le=200, description="分页大小,最大 200"),
offset: int = Query(0, ge=0, description="分页偏移"),
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""查询厂商集成目录列表,支持按适配器类型、标签、关键词过滤与分页。
列表项返回精简字段剔除 connection_extra_schema / example_config /
dependency_note适合前端集成市场页表格展示``total`` 为满足筛选
条件的全量总数非当前分页条数
通过 ``filter_integrations`` 一次调用获取全量过滤结果再切片分页
``len()`` 取总数避免 ``list_integrations`` + ``count_integrations``
各自独立调用 ``filter_integrations`` 导致双重过滤+排序
"""
tags_list: list[str] | None = None
if tags is not None:
tags_list = [t.strip() for t in tags.split(",") if t.strip()]
if not tags_list:
tags_list = None
filtered = IntegrationRegistry.filter_integrations(
adapter_type=adapter_type,
tags=tags_list,
keyword=keyword,
)
total = len(filtered)
metadata_list = filtered[offset : offset + limit]
items = [
{k: v for k, v in metadata.model_dump().items() if k not in _SUMMARY_EXCLUDED_FIELDS}
for metadata in metadata_list
]
return {
"success": True,
"data": {
"items": items,
"total": total,
"limit": limit,
"offset": offset,
},
}
@integration_router.get("/overview", response_model=dict)
async def get_integrations_overview(
current_user: User = Depends(get_required_user),
db: AsyncSession = Depends(get_db),
) -> dict[str, Any]:
"""查询厂商集成的分组总览(按 adapter_type 分组 + 标签统计 + 使用统计)。
通过 filter_integrations 获取全量集成无分页 ``adapter_type``
分组构造 ``by_adapter_type`` 字典每组仅含精简字段并统计每个标签
出现次数构造 ``by_tag`` 字典按标签名升序排序同时查询
``ext_systems`` ``integration_key`` 聚合已创建系统数量返回
``usage_stats``
"""
metadata_list = IntegrationRegistry.filter_integrations()
by_adapter_type: dict[str, list[dict[str, Any]]] = {}
by_tag: dict[str, int] = {}
for metadata in metadata_list:
by_adapter_type.setdefault(metadata.adapter_type, []).append(
{
"key": metadata.key,
"display_name": metadata.display_name,
"adapter_type": metadata.adapter_type,
"tags": metadata.tags,
}
)
for tag in metadata.tags or ():
by_tag[tag] = by_tag.get(tag, 0) + 1
# 查询数据库统计每个 integration_key 对应的非删除系统数量
stmt = (
select(ExternalSystem.integration_key, func.count(ExternalSystem.id))
.where(
ExternalSystem.integration_key.is_not(None),
ExternalSystem.is_deleted.is_(False),
)
.group_by(ExternalSystem.integration_key)
)
result = await db.execute(stmt)
usage_stats = {key: int(count) for key, count in result.all() if key}
return {
"success": True,
"data": {
"by_adapter_type": by_adapter_type,
"total": len(metadata_list),
"by_tag": dict(sorted(by_tag.items())),
"usage_stats": usage_stats,
},
}
@integration_router.get("/categories", response_model=dict)
async def get_integration_categories(
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""查询厂商集成的分类标签树(基于 tags 字段聚合)。
通过 filter_integrations 获取全量集成无分页每个标签返回
``count`` ``integrations``key 列表 ``count`` 降序
``tag`` 升序排序适合前端集成市场页分类筛选侧边栏
"""
metadata_list = IntegrationRegistry.filter_integrations()
tag_to_integrations: dict[str, list[str]] = {}
for metadata in metadata_list:
for tag in metadata.tags or ():
tag_to_integrations.setdefault(tag, []).append(metadata.key)
items = [{"tag": tag, "count": len(keys), "integrations": keys} for tag, keys in tag_to_integrations.items()]
items.sort(key=lambda it: (-it["count"], it["tag"]))
return {
"success": True,
"data": {"items": items, "total": len(items)},
}
@integration_router.get("/adapter-types", response_model=dict)
async def get_integration_adapter_types(
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""查询所有厂商集成涉及的适配器类型(去重),用于前端过滤选项。
仅返回有厂商集成的适配器类型当前全部为 http adapter_router
``GET /adapters``所有注册协议适配器语义不同
"""
adapter_types = IntegrationRegistry.list_adapter_types()
return {
"success": True,
"data": {"items": adapter_types, "total": len(adapter_types)},
}
@integration_router.get("/source-types", response_model=dict)
async def get_integration_source_types(
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""查询所有已注册操作 handler 的数据源类型(含操作支持矩阵)。
对每个 source_type 调用 ``list_operations`` 获取已注册操作
``source_type`` 升序排序适合前端"工具生成"页的 source_type 选择
仅返回厂商 source_type不含 openapi / postman 等通用 source_type
通用 source_type 走资产导入流程未注册操作 handler
"""
source_types = IntegrationOperationRegistry.list_source_types()
items = [
{
"source_type": source_type,
"operations": IntegrationOperationRegistry.list_operations(source_type),
}
for source_type in source_types
]
return {
"success": True,
"data": {"items": items, "total": len(items)},
}
# =============================================================================
# === 路径参数端点(/{key} ===
# =============================================================================
@integration_router.get("/{key}", response_model=dict)
async def get_integration_detail(
key: str = Path(..., min_length=1, max_length=64),
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""查询单个厂商集成的完整元数据详情。
``get_integration_or_raise`` 未找到时抛 ``EntityNotFoundError`` (404)
由全局处理器统一映射返回 ``model_dump()`` 完整字段包含
connection_extra_schema / example_config / dependency_note
"""
metadata = IntegrationRegistry.get_integration_or_raise(key)
return {"success": True, "data": metadata.model_dump()}
@integration_router.get("/{key}/operations", response_model=dict)
async def get_integration_operations(
key: str = Path(..., min_length=1, max_length=64),
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""查询指定厂商集成已注册的操作 handler 列表(按 source_type 分组)。
遍历 ``metadata.source_types``按字母升序排序保证字典键顺序稳定
对每个 source_type 调用 ``list_operations`` 获取已注册操作
``operations_by_source_type`` ``{source_type: [operations]}`` 字典
``supported_operations`` 为所有 source_type 操作的并集去重按字母
升序排序
``get_integration_or_raise`` 未找到时抛 ``EntityNotFoundError`` (404)
"""
metadata = IntegrationRegistry.get_integration_or_raise(key)
operations_by_source_type: dict[str, list[str]] = {}
for source_type in sorted(metadata.source_types):
operations_by_source_type[source_type] = IntegrationOperationRegistry.list_operations(source_type)
supported_operations = sorted(set(op for ops in operations_by_source_type.values() for op in ops))
return {
"success": True,
"data": {
"key": key,
"operations_by_source_type": operations_by_source_type,
"supported_operations": supported_operations,
},
}
@integration_router.get("/{key}/connection-schema", response_model=dict)
async def get_integration_connection_schema(
key: str = Path(..., min_length=1, max_length=64),
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""获取厂商集成的连接配置 JSON Schema用于前端动态表单渲染。
返回 ``connection_extra_schema`` 字段可能为 None表示该集成无额外
连接配置与详情端点的差异本端点仅返回 Schema适合前端表单渲染
时独立获取
``get_integration_or_raise`` 未找到时抛 ``EntityNotFoundError`` (404)
"""
metadata = IntegrationRegistry.get_integration_or_raise(key)
return {
"success": True,
"data": {
"key": key,
"connection_extra_schema": metadata.connection_extra_schema,
},
}
@integration_router.get("/{key}/example-config", response_model=dict)
async def get_integration_example_config(
key: str = Path(..., min_length=1, max_length=64),
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""获取厂商集成的推荐配置示例,用于前端表单预填与配置引导。
返回 ``example_config`` 字段可能为 None表示该集成无推荐配置
``get_integration_or_raise`` 未找到时抛 ``EntityNotFoundError`` (404)
"""
metadata = IntegrationRegistry.get_integration_or_raise(key)
return {
"success": True,
"data": {
"key": key,
"example_config": metadata.example_config,
},
}