ForcePilot/backend/server/routers/channels/doctor_router.py
Kris 08617091dc refactor: 整理项目包结构与导入路径
- 新增多个业务域的__init__.py模块文件,规范包导出结构
- 调整多个DTO文件的导入路径,统一模块组织方式
- 移除测试文件中多余的空行与导入语句
- 优化部分业务模块的包层级划分
2026-07-18 02:04:03 +08:00

240 lines
9.5 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 入站端点,无匿名访问
``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.messaging.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)}