ForcePilot/backend/server/routers/channels/doctor_router.py

241 lines
9.5 KiB
Python
Raw Normal View History

"""配置诊断 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 入站端点无匿名访问
``check_id`` 校验策略
- 路径参数声明为 ``str`` Pydantic 注解格式校验
``^[A-Za-z0-9_-]+$``统一在 ``DoctorHandler.match_pattern``
入口经模块级 ``_validateCheckId`` 函数执行确保非法格式也经
控制面管道五阶段AC-32 失败审计避免 Router
``StringConstraints`` 注解返回 422 与管道内 ``ValidationError``
返回 400 的错误码不一致P2 修正
"""
from __future__ import annotations
from typing import Any
from fastapi import APIRouter, Depends, Request
from yuxi.channels.contract.dtos.channel import ChannelType
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-CHECK-02FR-17
对应控制面操作 ``doctor/checks/{check_id}``
``ChannelControlService.getDoctorCheck`` 内部构造 ``ControlCmd``
委托 ``_executeControl`` 执行控制面管道返回检查项元信息
``check_id`` / ``name`` / ``severity`` / ``description`` /
``auto_repairable``检查项不存在返回 404 ``DoctorService.getCheck``
``_getCheckItem`` ``NotFoundError``携带 ``trace_id``
``check_id`` 格式非法返回 400 ``doctor_handler._validateCheckId``
在管道内抛 ``ValidationError``渠道未注册诊断适配器返回 501
本端点走标准模板 A控制面端口路由不复用 ``getDoctorChecks`` 全量
查询后过滤GAP-06 修正单项查询直接走 ``DoctorService.getCheck``
端口方法避免 router 层业务判断与 ``trace_id`` 丢失
"""
operator = build_operator(current_user, request)
result = await use_cases.doctor.getDoctorCheck(
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}/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-04FR-17
对应控制面操作 ``doctor/checks/{check_id}/run``
``ChannelControlService.runDoctorCheck`` 内部构造 ``ControlCmd``
委托 ``_executeControl`` 执行控制面管道``check_id`` 格式由
``doctor_handler._validateCheckId`` 在管道内校验正则
``^[A-Za-z0-9_-]+$``格式非法返回 400经审计五阶段AC-32
检查项不存在返回 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-REP-02FR-17 / AC-40dry-run 只读)。
对应控制面操作 ``doctor/checks/{check_id}/repair/preview``
``ChannelControlService.repairDoctorCheck`` ``confirmed=False``
内部构造 ``ControlCmd`` 并委托 ``_executeControl`` 执行控制面管道
返回含修复方案repair_plan的检查结果不执行修复preview execute
使用独立 operation确保只读预览审计为 ``doctor_repair_previewed`` 而非
写操作 ``doctor_repair``检查项不支持自动修复时返回 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`` ``confirmed=True``
内部构造 ``ControlCmd`` 并委托 ``_executeControl`` 执行控制面管道
执行 ``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)}