ForcePilot/backend/server/routers/channels/doctor_router.py
Kris ddafd95ff0 refactor(routers): 整理并新增多组渠道相关路由功能
1.  移除多个导出接口的显式response_model声明
2.  调整access_rule和test_case的创建接口位置,修复静态路径冲突
3.  优化适配器配置校验的异常处理逻辑
4.  重构集成路由的查询逻辑,统一使用get_integration_or_raise
5.  新增channels路由组下的capability、reports、dashboard、webhook、wizard、doctor、directory、session共8个子路由模块
6.  注册channels_router到全局路由列表
2026-07-02 03:29:06 +08:00

242 lines
9.4 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.

"""配置诊断 Router。
路由分一类:
- 诊断端点GET /checks / GET /checks/{check_id} / POST /checks/run-all /
POST /checks/{check_id}/run / POST /checks/{check_id}/repair/preview /
POST /checks/{check_id}/repair控制面操作通过 DoctorPort 走
控制面管道,统一执行认证 / 权限 / 限流 / 分派 / 审计五阶段(模板 A
失败时通过 ``raiseOnControlFailure`` 反向重构异常交由全局 handler 映射。
端口装配:
- ``use_cases.doctor`` 字段绑定到 ``ChannelControlService`` 同一实例
(窄端口依赖,由 ``infrastructure/channel_use_cases.py`` 装配)
鉴权策略:
- 全部端点使用 ``get_admin_user`` 依赖(管理员角色校验)
- 无 Webhook 入站端点,无匿名访问
"""
from __future__ import annotations
from typing import Any
from fastapi import APIRouter, Depends, Request
from yuxi.channels.contract.dtos.channel import ChannelType
from yuxi.channels.contract.errors import InternalError, NotFoundError
from yuxi.storage.postgres.models_business import User
from server.routers.channels import (
build_operator,
get_channel_use_cases,
raiseOnControlFailure,
serialize_control_data,
)
from server.utils.auth_middleware import get_admin_user
doctor_router = APIRouter(tags=["channels-doctor"])
@doctor_router.get(
"/{channel_type}/{account_id}/doctor/checks",
response_model=dict,
)
async def list_doctor_checks(
channel_type: ChannelType,
account_id: str,
request: Request,
use_cases=Depends(get_channel_use_cases),
current_user: User = Depends(get_admin_user),
) -> dict[str, Any]:
"""查询诊断检查项列表DOC-CHECK-01FR-17
对应控制面操作 ``doctor/checks``(由 ``ChannelControlService.getDoctorChecks``
内部构造 ``ControlCmd`` 并委托 ``_executeControl`` 执行控制面管道)。
返回指定渠道账户的诊断检查项列表,包括插件声明的检查项与框架级
默认检查项。渠道未注册诊断适配器时返回 501。
"""
operator = build_operator(current_user, request)
result = await use_cases.doctor.getDoctorChecks(
channel_type=channel_type,
account_id=account_id,
operator=operator,
)
raiseOnControlFailure(result)
return {"success": True, "data": serialize_control_data(result.data)}
@doctor_router.post(
"/{channel_type}/{account_id}/doctor/checks/run-all",
response_model=dict,
)
async def run_all_doctor_checks(
channel_type: ChannelType,
account_id: str,
request: Request,
use_cases=Depends(get_channel_use_cases),
current_user: User = Depends(get_admin_user),
) -> dict[str, Any]:
"""运行全部诊断检查DOC-CHECK-03FR-17AC-19 / AC-64
对应控制面操作 ``doctor/run``(由 ``ChannelControlService.runDoctorChecks``
内部构造 ``ControlCmd`` 并委托 ``_executeControl`` 执行控制面管道)。
执行 6 项框架级默认检查credential_validity / required_permissions /
webhook_registered / webhook_reachable / rate_limit_status /
whitelist_status+ 适配器自定义检查项,聚合为诊断报告(含整体健康度、
检查项结果列表、已应用迁移列表)。单项检查异常不中断整体流程
_safeRunItem 捕获非 DependencyError 降级为"未通过 + ERROR")。
"""
operator = build_operator(current_user, request)
result = await use_cases.doctor.runDoctorChecks(
channel_type=channel_type,
account_id=account_id,
operator=operator,
)
raiseOnControlFailure(result)
return {"success": True, "data": serialize_control_data(result.data)}
@doctor_router.get(
"/{channel_type}/{account_id}/doctor/checks/{check_id}",
response_model=dict,
)
async def get_doctor_check(
channel_type: ChannelType,
account_id: str,
check_id: str,
request: Request,
use_cases=Depends(get_channel_use_cases),
current_user: User = Depends(get_admin_user),
) -> dict[str, Any]:
"""查询单项检查项元信息DOC-03FR-17
复用控制面操作 ``doctor/checks``(由 ``ChannelControlService.getDoctorChecks``
内部构造 ``ControlCmd`` 并委托 ``_executeControl`` 执行控制面管道)。
返回检查项清单后由 router 层按 ``check_id`` 筛选单项元信息check_id /
name / severity / description / auto_repairable。检查项不存在返回 404。
注意:本端点与 DOC-CHECK-01 共享同一端口方法 ``getDoctorChecks``
router 层从检查项列表中筛选指定 ``check_id``。这是协议层的字段筛选
(从已有数据中按 ID 过滤,不涉及业务规则判断),符合 INV-8驱动
适配器无业务规则)。检查项不存在时抛 ``NotFoundError``(契约层异常,
非 HTTPException交由全局 handler 映射为 404。
已知技术债务GAP-06当前复用 ``getDoctorChecks`` 全量查询后筛选,
未来应在端口层新增 ``getDoctorCheck`` 单项查询方法,避免全量查询开销。
当前复用现有端口方法以最小化契约变更。
"""
operator = build_operator(current_user, request)
result = await use_cases.doctor.getDoctorChecks(
channel_type=channel_type,
account_id=account_id,
operator=operator,
)
raiseOnControlFailure(result)
if not isinstance(result.data, dict):
raise InternalError(message="control pipeline returned malformed data for doctor checks")
checks = result.data.get("checks", [])
target = next((c for c in checks if c.get("check_id") == check_id), None)
if target is None:
raise NotFoundError(
resource="doctor_check",
id=check_id,
)
return {"success": True, "data": serialize_control_data(target)}
@doctor_router.post(
"/{channel_type}/{account_id}/doctor/checks/{check_id}/run",
response_model=dict,
)
async def run_doctor_check(
channel_type: ChannelType,
account_id: str,
check_id: str,
request: Request,
use_cases=Depends(get_channel_use_cases),
current_user: User = Depends(get_admin_user),
) -> dict[str, Any]:
"""运行单项诊断检查DOC-CHECK-02FR-17
对应控制面操作 ``doctor/checks/{check_id}/run``(由
``ChannelControlService.runDoctorCheck`` 内部构造 ``ControlCmd`` 并
委托 ``_executeControl`` 执行控制面管道)。``check_id`` 格式由
``ChannelControlService._validateCheckId`` 校验(正则
``^[A-Za-z0-9_-]+$``),格式非法返回 400检查项不存在返回 404
渠道未注册诊断适配器返回 501。
"""
operator = build_operator(current_user, request)
result = await use_cases.doctor.runDoctorCheck(
channel_type=channel_type,
account_id=account_id,
check_id=check_id,
operator=operator,
)
raiseOnControlFailure(result)
return {"success": True, "data": serialize_control_data(result.data)}
@doctor_router.post(
"/{channel_type}/{account_id}/doctor/checks/{check_id}/repair/preview",
response_model=dict,
)
async def preview_doctor_repair(
channel_type: ChannelType,
account_id: str,
check_id: str,
request: Request,
use_cases=Depends(get_channel_use_cases),
current_user: User = Depends(get_admin_user),
) -> dict[str, Any]:
"""修复方案预览DOC-02FR-17 / AC-40dry-run
对应控制面操作 ``doctor/checks/{check_id}/repair``(由
``ChannelControlService.repairDoctorCheck`` 内部构造 ``ControlCmd`` 并
委托 ``_executeControl`` 执行控制面管道),传入 ``confirmed=False``。
返回含修复方案repair_plan的检查结果不执行修复。检查项不支持
自动修复时返回 400检查项不存在返回 404渠道未注册诊断适配器返回 501。
"""
operator = build_operator(current_user, request)
result = await use_cases.doctor.repairDoctorCheck(
channel_type=channel_type,
account_id=account_id,
check_id=check_id,
confirmed=False,
operator=operator,
)
raiseOnControlFailure(result)
return {"success": True, "data": serialize_control_data(result.data)}
@doctor_router.post(
"/{channel_type}/{account_id}/doctor/checks/{check_id}/repair",
response_model=dict,
)
async def execute_doctor_repair(
channel_type: ChannelType,
account_id: str,
check_id: str,
request: Request,
use_cases=Depends(get_channel_use_cases),
current_user: User = Depends(get_admin_user),
) -> dict[str, Any]:
"""执行修复DOC-REP-01FR-17 / AC-40
对应控制面操作 ``doctor/checks/{check_id}/repair``(由
``ChannelControlService.repairDoctorCheck`` 内部构造 ``ControlCmd`` 并
委托 ``_executeControl`` 执行控制面管道),传入 ``confirmed=True``。
执行 ``adapter.autoFix`` 修复后复检返回结果;修复失败时返回原检查结果
"未通过"不修改渠道配置AC-40。检查项不支持自动修复时返回 400
检查项不存在返回 404渠道未注册诊断适配器返回 501。
"""
operator = build_operator(current_user, request)
result = await use_cases.doctor.repairDoctorCheck(
channel_type=channel_type,
account_id=account_id,
check_id=check_id,
confirmed=True,
operator=operator,
)
raiseOnControlFailure(result)
return {"success": True, "data": serialize_control_data(result.data)}