- 新增多个业务域的__init__.py模块文件,规范包导出结构 - 调整多个DTO文件的导入路径,统一模块组织方式 - 移除测试文件中多余的空行与导入语句 - 优化部分业务模块的包层级划分
202 lines
9.7 KiB
Python
202 lines
9.7 KiB
Python
"""外部系统限界上下文 Router 聚合层与共享装配层。
|
||
|
||
本模块承担两个职责:
|
||
|
||
1. **共享装配层**:为各子 Router 提供请求级独立 session 的 UseCases / 轻量
|
||
service 装配依赖(``get_use_cases`` / ``get_dashboard_service`` /
|
||
``get_adapter_stats_service``)。子 Router 通过 ``Depends(get_use_cases)``
|
||
注入已装配的 ``UseCases``,不再各自调用 ``create_use_cases_from_db(db)``。
|
||
|
||
2. **Router 聚合层**:将 23 个子 router 通过 include_router 注册,统一挂载到
|
||
/system/external-systems 前缀下。子 router 只需声明相对前缀(如 /systems、
|
||
/tools),根前缀 /system/external-systems 由本聚合 router 统一追加。
|
||
|
||
独立 session 设计说明(参照 channels 模块):
|
||
|
||
external_systems 模块 **不复用** 鉴权依赖(``get_db``)持有的 ``AsyncSession``,
|
||
而是通过 ``pg_manager.get_async_session_context()`` 开启独立的请求级会话。原因:
|
||
鉴权阶段执行的 ``SELECT`` 会触发 SQLAlchemy 2.0 autobegin 语义开启隐式事务,
|
||
若复用同一 session,控制面 ``TransactionPort.begin()`` 会在已存在的事务上再次
|
||
``begin()``,且异常路径下底层 asyncpg 连接会残留 "manually started transaction",
|
||
连接归还连接池后被下个请求复用时触发
|
||
``cannot use Connection.transaction() in a manually started transaction``。
|
||
|
||
独立 session 使事务边界由 external_systems 应用层独占控制,与鉴权会话隔离,
|
||
避免事务状态互相污染。会话生命周期限制在本次请求内,请求结束自动
|
||
commit(无异常)或 rollback(有异常)后关闭。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from datetime import datetime
|
||
|
||
from fastapi import APIRouter
|
||
from fastapi.exceptions import RequestValidationError
|
||
from yuxi.external_systems.infrastructure.container import (
|
||
UseCases,
|
||
create_adapter_stats_service_from_db,
|
||
create_dashboard_service_from_db,
|
||
create_use_cases_from_db,
|
||
)
|
||
from yuxi.external_systems.use_cases.ports import (
|
||
AdapterStatsServicePort,
|
||
DashboardServicePort,
|
||
)
|
||
from yuxi.storage.postgres.manager import pg_manager
|
||
from yuxi.utils.datetime_utils import coerce_any_to_utc_datetime, to_naive_utc
|
||
|
||
|
||
async def get_use_cases() -> UseCases:
|
||
"""装配 external_systems UseCases 集合(请求级独立 session)。
|
||
|
||
通过 ``pg_manager.get_async_session_context()`` 开启独立请求级 session,
|
||
调用 ``create_use_cases_from_db(db)`` 装配完整 24-service ``UseCases`` 容器,
|
||
yield 完成后自动关闭 session。不复用 ``get_db`` session 的原因见模块
|
||
docstring。
|
||
|
||
Yields:
|
||
已装配的 ``UseCases`` 实例。
|
||
"""
|
||
async with pg_manager.get_async_session_context() as db:
|
||
yield create_use_cases_from_db(db)
|
||
|
||
|
||
async def get_dashboard_service() -> DashboardServicePort:
|
||
"""装配轻量级 DashboardService(请求级独立 session)。
|
||
|
||
Dashboard 是纯聚合查询,无需完整 UseCases 容器。使用
|
||
``create_dashboard_service_from_db(db)`` 轻量工厂仅构造
|
||
``DashboardService``(仅注入 Repositories),降低延迟与资源消耗。
|
||
不复用 ``get_db`` session 的原因见模块 docstring。
|
||
|
||
Yields:
|
||
已装配的 ``DashboardServicePort`` 实例。
|
||
"""
|
||
async with pg_manager.get_async_session_context() as db:
|
||
yield create_dashboard_service_from_db(db)
|
||
|
||
|
||
async def get_adapter_stats_service() -> AdapterStatsServicePort:
|
||
"""装配轻量级 AdapterStatsService(请求级独立 session)。
|
||
|
||
AdapterStatsService 仅依赖 Repositories(纯查询用例,无副作用),无需
|
||
完整 UseCases 容器。使用 ``create_adapter_stats_service_from_db(db)``
|
||
轻量工厂避免每次统计/健康查询都构造完整 24-service 容器。不复用
|
||
``get_db`` session 的原因见模块 docstring。
|
||
|
||
Yields:
|
||
已装配的 ``AdapterStatsServicePort`` 实例。
|
||
"""
|
||
async with pg_manager.get_async_session_context() as db:
|
||
yield create_adapter_stats_service_from_db(db)
|
||
|
||
|
||
# ------------------------------------------------------------------
|
||
# 统一时间参数处理(对齐 channels 模块 parse_datetime 模式)
|
||
#
|
||
# 所有子 Router 的时间查询参数统一使用 str 类型(ISO 8601),
|
||
# 通过 parse_datetime 翻译为 aware UTC datetime,再用 to_naive_utc
|
||
# 剥离为 naive UTC 以适配 TIMESTAMP WITHOUT TIME ZONE 列。
|
||
# ------------------------------------------------------------------
|
||
|
||
|
||
def parse_datetime(field: str, raw: str | None) -> datetime | None:
|
||
"""ISO 8601 string → aware UTC datetime,失败抛 RequestValidationError (422)。
|
||
|
||
对齐 channels 模块的 parse_datetime 模式:naive 字符串视为 Asia/Shanghai,
|
||
aware 字符串统一转 UTC。raw 为 None 时返回 None。
|
||
"""
|
||
if raw is None:
|
||
return None
|
||
try:
|
||
return coerce_any_to_utc_datetime(raw)
|
||
except (ValueError, TypeError) as exc:
|
||
raise RequestValidationError(
|
||
errors=[
|
||
{
|
||
"loc": ("query", field),
|
||
"msg": f"invalid {field} format: {raw} (expected ISO 8601)",
|
||
"type": "value_error",
|
||
"input": raw,
|
||
}
|
||
]
|
||
) from exc
|
||
|
||
|
||
def validate_time_range(start: datetime | None, end: datetime | None) -> None:
|
||
"""校验时间范围语义:start 不晚于 end。失败抛 RequestValidationError (422)。"""
|
||
if start is not None and end is not None and start > end:
|
||
raise RequestValidationError(
|
||
errors=[
|
||
{
|
||
"loc": ("query", "start_time"),
|
||
"msg": "start_time 不能晚于 end_time",
|
||
"type": "value_error",
|
||
"input": {"start_time": start, "end_time": end},
|
||
}
|
||
]
|
||
)
|
||
|
||
|
||
def parse_and_naive(field: str, raw: str | None) -> datetime | None:
|
||
"""一步完成 parse_datetime + to_naive_utc,返回 naive UTC datetime 或 None。"""
|
||
return to_naive_utc(parse_datetime(field, raw))
|
||
|
||
|
||
external_systems_router = APIRouter(
|
||
prefix="/system/external-systems",
|
||
tags=["external-systems"],
|
||
)
|
||
|
||
# 子 router 导入置于共享依赖定义之后,避免循环导入
|
||
# (子 router 通过 ``from server.routers.external_systems import get_use_cases``
|
||
# 反向引用本模块依赖,须保证此处已定义完成)
|
||
from server.routers.external_systems.access_rule_router import access_rule_router # noqa: E402
|
||
from server.routers.external_systems.adapter_router import adapter_router # noqa: E402
|
||
from server.routers.external_systems.alert_router import alert_router # noqa: E402
|
||
from server.routers.external_systems.asset_router import asset_router # noqa: E402
|
||
from server.routers.external_systems.audit_log_router import audit_log_router # noqa: E402
|
||
from server.routers.external_systems.auth_plugin_router import auth_plugin_router # noqa: E402
|
||
from server.routers.external_systems.dashboard_router import dashboard_router # noqa: E402
|
||
from server.routers.external_systems.environment_router import environment_router # noqa: E402
|
||
from server.routers.external_systems.execution_router import execution_router # noqa: E402
|
||
from server.routers.external_systems.health_check_router import health_check_router # noqa: E402
|
||
from server.routers.external_systems.import_router import import_router # noqa: E402
|
||
from server.routers.external_systems.integration_router import integration_router # noqa: E402
|
||
from server.routers.external_systems.integration_tool_router import integration_tool_router # noqa: E402
|
||
from server.routers.external_systems.metric_router import metric_router # noqa: E402
|
||
from server.routers.external_systems.notification_channel_router import notification_channel_router # noqa: E402
|
||
from server.routers.external_systems.quota_router import quota_router # noqa: E402
|
||
from server.routers.external_systems.secret_rotation_router import secret_rotation_router # noqa: E402
|
||
from server.routers.external_systems.system_router import system_router # noqa: E402
|
||
from server.routers.external_systems.test_case_router import test_case_router # noqa: E402
|
||
from server.routers.external_systems.token_router import token_router # noqa: E402
|
||
from server.routers.external_systems.tool_router import tool_router # noqa: E402
|
||
from server.routers.external_systems.trash_router import trash_router # noqa: E402
|
||
from server.routers.external_systems.webhook_router import webhook_router # noqa: E402
|
||
|
||
# 注册 23 个子 router
|
||
external_systems_router.include_router(system_router)
|
||
external_systems_router.include_router(tool_router)
|
||
external_systems_router.include_router(environment_router)
|
||
external_systems_router.include_router(execution_router)
|
||
external_systems_router.include_router(token_router)
|
||
external_systems_router.include_router(audit_log_router)
|
||
external_systems_router.include_router(asset_router)
|
||
external_systems_router.include_router(adapter_router)
|
||
external_systems_router.include_router(import_router)
|
||
external_systems_router.include_router(integration_router)
|
||
external_systems_router.include_router(integration_tool_router)
|
||
external_systems_router.include_router(webhook_router)
|
||
external_systems_router.include_router(health_check_router)
|
||
external_systems_router.include_router(metric_router)
|
||
external_systems_router.include_router(quota_router)
|
||
external_systems_router.include_router(alert_router)
|
||
external_systems_router.include_router(secret_rotation_router)
|
||
external_systems_router.include_router(access_rule_router)
|
||
external_systems_router.include_router(test_case_router)
|
||
external_systems_router.include_router(dashboard_router)
|
||
external_systems_router.include_router(auth_plugin_router)
|
||
external_systems_router.include_router(notification_channel_router)
|
||
external_systems_router.include_router(trash_router)
|