ForcePilot/backend/server/routers/external_systems/health_check_router.py
Kris 6dd16ad3f8 refactor(external-systems-routers): 统一分页参数格式并完善各路由文档与校验
本次提交对多个外部系统路由进行了多维度优化:
1.  统一分页参数:将所有路由的`page = offset//limit +1`、`page_size=limit`替换为标准的`limit`+`offset`分页格式
2.  完善接口文档:补充多个端点的功能说明、参数含义与返回字段解释
3.  增强参数校验:新增字段长度限制、正则校验、枚举类型约束与业务逻辑校验
4.  优化代码复用:提取重复逻辑为辅助函数,减少样板代码
5.  修复接口问题:修正工具健康检查端点路径参数类型,优化导出接口响应格式
6.  补充异常处理:为批量操作添加异常捕获与日志记录,避免流程中断
2026-07-11 06:57:24 +08:00

174 lines
7.2 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.

"""HealthCheck 子域 Router。
外部系统限界上下文的健康检查查询 API覆盖全局最新状态一览 /
状态聚合统计 / 系统跨环境状态 / 系统最新状态 / 故障系统列表 /
未探测系统列表 / 过期记录清理。
所有端点通过 ``create_use_cases_from_db`` 装配 use_cases
经 ``health_check_service`` 端口调用用例。
路径顺序约束静态路径latest/stats/failing/unchecked/old-records
必须在动态路径 /by-system/{system_id} 之前声明。
认证策略:
- 查询类端点使用 get_required_user支持 JWT + API Key 双模认证)
- 清理端点使用 get_admin_user
"""
from __future__ import annotations
from datetime import datetime
from typing import Any, Literal
from fastapi import APIRouter, Depends, Path, Query
from sqlalchemy.ext.asyncio import AsyncSession
from yuxi.external_systems.infrastructure.container import create_use_cases_from_db
from yuxi.external_systems.use_cases.dto.health_check import (
CountByStatusInput,
DeleteOldRecordsInput,
GetLatestBySystemInput,
ListBySystemInput,
ListFailingInput,
ListLatestHealthInput,
ListUncheckedInput,
)
from yuxi.storage.postgres.models_business import User
from server.utils.auth_middleware import get_admin_user, get_db, get_required_user
health_check_router = APIRouter(prefix="/health-checks", tags=["external-systems-health-check"])
# ---------------- Endpoints ----------------
# ---- 静态路径端点(必须在动态路径 /by-system/{system_id} 之前声明) ----
@health_check_router.get("/latest", response_model=dict)
async def list_latest_health(
system_ids: list[int] | None = Query(None, description="按系统 ID 过滤(逗号分隔)"),
health_status: Literal["healthy", "auth_failure", "connectivity_issue", "not_checked"] | None = Query(
None, description="健康状态healthy/auth_failure/connectivity_issue/not_checked"
),
limit: int = Query(20, ge=1, le=100),
offset: int = Query(0, ge=0),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""全局最新健康状态一览。每个系统仅取最近一条记录。"""
use_cases = create_use_cases_from_db(db)
input_dto = ListLatestHealthInput(
system_ids=system_ids,
health_status=health_status,
limit=limit,
offset=offset,
)
output = await use_cases.health_check_service.list_latest(input_dto)
return {"success": True, "data": output.model_dump()}
@health_check_router.get("/stats", response_model=dict)
async def count_by_status(
system_ids: list[int] | None = Query(None, description="按系统 ID 过滤(逗号分隔)"),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""按状态聚合统计。返回 4 状态分组计数 + total。
统计口径:每个系统取其最近一条健康检查记录的状态进行分组。
``not_checked`` 表示最近记录状态为 not_checked 的系统数,
**非**从未探测的系统数(后者使用 ``/unchecked`` 端点)。
``total`` 为有健康检查记录的系统总数,非全部外部系统数。
"""
use_cases = create_use_cases_from_db(db)
input_dto = CountByStatusInput(system_ids=system_ids)
output = await use_cases.health_check_service.count_by_status(input_dto)
return {"success": True, "data": output.model_dump()}
@health_check_router.get("/failing", response_model=dict)
async def list_failing(
health_status: Literal["auth_failure", "connectivity_issue"] = Query(
"auth_failure",
description="故障状态过滤auth_failure / connectivity_issue",
),
system_ids: list[int] | None = Query(None, description="按系统 ID 过滤(逗号分隔)"),
limit: int = Query(100, ge=1, le=500),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""故障系统列表。通过 list_latest(health_status=...) 参数化查询。"""
use_cases = create_use_cases_from_db(db)
input_dto = ListFailingInput(
health_status=health_status,
system_ids=system_ids,
limit=limit,
)
output = await use_cases.health_check_service.list_failing(input_dto)
return {"success": True, "data": output.model_dump()}
@health_check_router.get("/unchecked", response_model=dict)
async def list_unchecked(
system_ids: list[int] = Query(..., description="待检查的系统 ID 列表(逗号分隔)"),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""未探测系统列表。返回传入 system_ids 中无记录的子集。"""
use_cases = create_use_cases_from_db(db)
input_dto = ListUncheckedInput(system_ids=system_ids)
output = await use_cases.health_check_service.list_unchecked(input_dto)
return {"success": True, "data": output.model_dump()}
@health_check_router.delete("/old-records", response_model=dict)
async def delete_old_records(
before: datetime = Query(..., description="清理此时间之前的记录ISO 8601 格式)"),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_admin_user),
) -> dict[str, Any]:
"""清理过期记录(软删除)。按 checked_at 过滤。"""
use_cases = create_use_cases_from_db(db)
input_dto = DeleteOldRecordsInput(before=before)
output = await use_cases.health_check_service.delete_old_records(input_dto)
return {"success": True, "data": output.model_dump()}
# ---- 动态路径端点(最后声明) ----
@health_check_router.get("/by-system/{system_id}", response_model=dict)
async def list_by_system(
system_id: int = Path(ge=1),
env_key: str | None = Query(None, max_length=32, description="按环境过滤"),
limit: int = Query(20, ge=1, le=100),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""系统跨环境健康状态。每个 env_key 仅返回最近一条记录。
指定 ``env_key`` 时仅返回该环境的最新记录;不指定时返回全部环境的最新记录。
"""
use_cases = create_use_cases_from_db(db)
input_dto = ListBySystemInput(
system_id=system_id,
env_key=env_key,
limit=limit,
)
output = await use_cases.health_check_service.list_by_system(input_dto)
return {"success": True, "data": output.model_dump()}
@health_check_router.get("/by-system/{system_id}/latest", response_model=dict)
async def get_latest_by_system(
system_id: int = Path(ge=1),
env_key: str | None = Query(None, max_length=32, description="环境标识(不传则跨 env 取最近一条)"),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""系统最新健康状态。指定 env_key 时按 env 查询,否则跨 env 取最近一条。"""
use_cases = create_use_cases_from_db(db)
input_dto = GetLatestBySystemInput(system_id=system_id, env_key=env_key)
data = await use_cases.health_check_service.get_latest_by_system(input_dto)
return {"success": True, "data": data}