ForcePilot/backend/server/routers/channels/doctor_router.py
Kris e5e9f45411 refactor(channel-routers): 批量优化各渠道路由代码与契约对齐
1. config_router: 为expected_version添加ge=1校验
2. directory_router: 补充scope校验逻辑与注释
3. login_router: 拆分强制下线权限,添加参数校验与注释更新
4. reports_router: 统一时间参数处理,修复分页限制使用契约常量
5. dashboard_router: 更新文档与响应格式,修正参数传递逻辑
6. health_router: 缩减健康检查响应字段,修复响应结构与参数校验
7. plugin_router: 新增插件目录端点,补充枚举校验与注释
8. pairing_router: 新增时间过滤参数,补充参数校验
9. __init__.py: 修复异常映射,更新trace_id获取逻辑与工具类
10. doctor_router: 重构单项检查端点,修正注释与校验逻辑
11. account_router: 新增恢复降级账户端点,补充批量操作校验
12. webhook_router: 优化webhook处理逻辑,修复流式读取与响应逻辑
13. content_review_router: 补充批量审核端点,完善参数校验与注释
14. analytics_router: 修正管道阶段描述,统一参数传递
15. wizard_router: 新增OAuth相关端点,重构路由路径与校验逻辑
2026-07-04 00:16:00 +08:00

241 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.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)}