ForcePilot/backend/package/yuxi/channels/contract/dtos/health.py
Kris 742299cb07 chore: 批量清理报告相关代码并完成多项功能迭代
本次提交包含多维度代码优化与功能增强:
1.  移除报告模块冗余导入与枚举,清理报表相关代码
2.  新增扫码登录支持方法与飞书适配器适配
3.  完善异常日志与健康检查信息
4.  扩展目录、配对管理、能力查询等接口
5.  优化出站管道与事务提交后钩子逻辑
6.  修复飞书消息解析与响应空值问题
7.  重构配置更新与服务账号创建逻辑
8.  统一传输错误分类契约与错误基类扩展
2026-07-06 20:49:35 +08:00

400 lines
15 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.

"""健康检查 DTO。
定义健康检查端口的命令与结果值对象,包括健康查询、健康快照、渠道健康、
探针结果、诊断导出请求与诊断包。所有 DTO 均为 ``dataclass(frozen=True)``
仅依赖标准库与契约层内部类型,用于健康检查、探针探测与诊断导出等操作。
集合字段使用 tuple 以保证 frozen dataclass 的不可变语义。
"""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime
from typing import Any, Literal
from yuxi.channels.contract.dtos.channel import ChannelType
from yuxi.channels.contract.dtos.common import Operator
from yuxi.channels.contract.errors import ValidationError
#: 诊断导出日志条数上限(与 Router 层 Pydantic schema ``le=500`` 对齐)。
#: DTO 作为契约层单一事实源,内部调用方同样受此上界约束。
_LOG_COUNT_MAX: int = 500
@dataclass(frozen=True)
class WorkerStatus:
"""队列 Worker 状态值对象。
由 ``QueuePort.getWorkerStatus`` 返回,描述 ARQ 队列 Worker 的关键
指标,供 ``HealthAggregator`` 判断是否降级、``DiagnosticsExporter``
填充诊断包的 ``worker_status`` 字段,以及 ``DashboardHandler`` 聚合
实时面板的队列深度与 Worker 利用率。
字段:
queue_name: 队列名称(``arq`` ``default_queue_name``)。
available: Worker 是否可用。
queue_depth: 队列深度(积压任务数,默认 0由 ``ARQQueueAdapter``
通过 ``zcard`` 查询 ``arq:queue:<queue_name>`` ZSET 采集)。
worker_utilization: Worker 利用率0.0-1.0ARQ 客户端当前不暴露
worker 级别利用率统计,设为 ``None`` 表示未采集,由调用方区分
展示)。
summary: 人类可读的中文状态摘要,供前端健康详情弹窗直接展示。
"""
queue_name: str
available: bool
queue_depth: int = 0
worker_utilization: float | None = None
summary: str = ""
@dataclass(frozen=True)
class RedisStreamStatus:
"""Redis 流状态值对象。
由 ``CachePort.getStreamStatus`` 返回,描述 Redis 实例关键指标,
供 ``HealthAggregator`` 与 ``DiagnosticsExporter`` 复用。
字段:
available: Redis 是否可用(``PING`` 成功)。
db_size: Redis 当前的 ``DBSIZE``(键总数)。
summary: 人类可读的中文状态摘要,供前端健康详情弹窗直接展示。
"""
available: bool
db_size: int
summary: str = ""
@dataclass(frozen=True)
class ConnectionPoolStatus:
"""数据库连接池状态值对象。
由 ``PersistenceHealthPort.getConnectionPoolStatus`` 返回,描述
SQLAlchemy 连接池的关键指标,供 ``HealthAggregator`` 判断连接池是否
耗尽、``DiagnosticsExporter`` 填充诊断包的
``db_connection_pool_status`` 字段。
字段:
available: 连接池是否可用(``engine`` 已绑定)。
size: 连接池容量(``pool.size()``)。
checked_in: 已归还连接数(``pool.checkedin()``)。
checked_out: 已检出连接数(``pool.checkedout()``)。
overflow: 溢出连接数(``pool.overflow()``)。
status: SQLAlchemy ``pool.status()`` 文本(用于诊断)。
reason: 不可用原因(``available=False`` 时填写,如
``"engine not bound"``)。
summary: 人类可读的中文状态摘要,供前端健康详情弹窗直接展示。
"""
available: bool
size: int = 0
checked_in: int = 0
checked_out: int = 0
overflow: int = 0
status: str = ""
reason: str | None = None
summary: str = ""
@dataclass(frozen=True)
class AccountHealthSnapshot:
"""传输引擎单账号健康状态值对象。
由 ``TransportHealthPort.getTransportHealth`` 经由
``WorkerHealthSnapshot.accounts`` 返回,描述单个渠道账号在传输引擎
中的运行状态。
字段:
channel_type: 渠道类型(字符串值,与 ``ChannelType.value`` 一致)。
account_id: 渠道账户 ID。
state: 账号运行状态(``running`` / ``backoff`` / ``stopped`` /
``error````error`` 触发 ``HealthAggregator`` 降级。
last_activity_at: 最近活动单调时间戳(``time.monotonic``)。
backoff_attempt: 当前退避尝试次数。
consecutive_successes: 连续成功次数。
"""
channel_type: str
account_id: str
state: Literal["running", "backoff", "stopped", "error"]
last_activity_at: float
backoff_attempt: int
consecutive_successes: int
@dataclass(frozen=True)
class WorkerHealthSnapshot:
"""传输引擎 Worker 健康状态值对象。
描述单个传输 Worker``PullerWorker`` 或 ``StreamWorker``)的运行
状态与账号列表,作为 ``TransportHealthSnapshot.puller`` /
``TransportHealthSnapshot.stream`` 字段类型。
字段:
running: Worker 是否在运行。
transport_mode: 传输模式(``pull`` / ``stream``)。
running_accounts: 运行中账号数。
total_accounts: 总账号数。
accounts: 各账号健康详情(按 ``channel_type`` + ``account_id``
唯一排列,使用 tuple 保证不可变)。
"""
running: bool
transport_mode: Literal["pull", "stream"]
running_accounts: int
total_accounts: int
accounts: tuple[AccountHealthSnapshot, ...] = ()
@dataclass(frozen=True)
class TransportHealthSnapshot:
"""传输引擎健康状态值对象。
由 ``TransportHealthPort.getTransportHealth`` 返回,聚合
``PullerWorker`` 与 ``StreamWorker`` 的 per-account 健康状态,供
``HealthAggregator`` 判断是否有账号处于 ``error`` 状态触发降级、
``DiagnosticsExporter`` 填充诊断包的 ``transport_status`` 字段。
实现约束(``TransportHealthPort``
- 实现 **必须** 幂等、无副作用,可在健康检查中安全调用。
- 实现 **不得** 抛出异常阻塞调用方;内部异常应降级返回空状态
``puller`` / ``stream`` 为 ``None``)并记录日志。
字段:
running: ``TransportManager`` 整体运行状态。
puller: ``PullerWorker`` 健康状态Worker 未启用时为 ``None``)。
stream: ``StreamWorker`` 健康状态Worker 未启用时为 ``None``)。
"""
running: bool
puller: WorkerHealthSnapshot | None = None
stream: WorkerHealthSnapshot | None = None
@dataclass(frozen=True)
class DegradedComponentDetail:
"""降级组件人类可读详情。
由 ``HealthAggregator`` 根据 ``degraded_components`` 生成,为健康详情
弹窗提供结构化的中文说明与建议操作,避免前端维护业务语义映射表。
字段:
component: 组件内部标识(如 ``database``、``redis``、``worker``)。
label: 中文显示名(如 ``数据库``)。
severity: 严重级别(``warning`` 或 ``error``)。
reason: 降级原因说明。
suggestion: 建议操作。
"""
component: str
label: str
severity: Literal["warning", "error"]
reason: str
suggestion: str
@dataclass(frozen=True)
class HealthQuery:
"""健康查询HLT-001
由健康检查端口方法引用,描述一次健康查询请求,可选按渠道过滤并指定
是否执行探针探测。
字段:
channel_filter: 渠道过滤(可选)。
with_probe: 是否执行探针探测(默认 False
"""
channel_filter: ChannelType | None = None
with_probe: bool = False
@dataclass(frozen=True)
class HealthSnapshot:
"""健康快照HLT-001
描述系统整体健康状态,包括状态、版本、降级组件、各渠道健康详情与
诊断包,用于健康检查结果汇报。集合字段使用 tuple 以保证不可变。
字段:
status: 健康状态healthy | degraded | unhealthy
version: 系统版本。
degraded_components: 降级组件标识列表(默认空 tuple
degraded_component_details: 降级组件人类可读详情(默认空 tuple
status_message: 一句话整体状态说明,供前端健康详情弹窗首屏展示。
channels: 各渠道健康详情列表(默认空 tuple
diagnostics_bundle: 诊断包(可选)。
worker_status: Worker 状态(聚合级,供诊断导出复用,可选)。
redis_stream_status: Redis 流状态(聚合级,供诊断导出复用,可选)。
db_connection_pool_status: 数据库连接池状态(聚合级,供诊断导出复用,可选)。
transport_status: 传输引擎状态per-account 粒度,由
TransportHealthPort 聚合,供诊断导出复用,可选)。
error_code: 错误码(用例服务捕获异常时填充,默认空字符串)。
trace_id: 追踪 ID用例服务捕获异常时填充默认空字符串
probed_at: 探针探测时间UTC仅 ``with_probe=True`` 时由用例服务
填充,供前端展示探测时机;非探测路径为 ``None``)。
"""
status: Literal["healthy", "degraded", "unhealthy"]
version: str
degraded_components: tuple[str, ...] = ()
degraded_component_details: tuple[DegradedComponentDetail, ...] = ()
status_message: str = ""
channels: tuple[ChannelHealth, ...] = ()
diagnostics_bundle: dict[str, Any] | None = None
worker_status: WorkerStatus | None = None
redis_stream_status: RedisStreamStatus | None = None
db_connection_pool_status: ConnectionPoolStatus | None = None
transport_status: TransportHealthSnapshot | None = None
error_code: str = ""
trace_id: str = ""
probed_at: datetime | None = None
@dataclass(frozen=True)
class ChannelHealth:
"""渠道健康HLT-002
描述单个渠道账户的健康状态,包括插件状态、适配器状态、最近消息时间、
最近错误、队列深度与连接池状态,用于健康检查结果聚合。
字段:
channel_type: 渠道类型。
account_id: 渠道账户 ID。
plugin_state: 插件状态。
adapter_states: 适配器状态映射。
last_message_at: 最近消息时间(可选)。
last_error: 最近错误信息(可选)。
queue_depth: 队列深度(默认 0
connection_pool_status: 连接池状态(可选)。
circuit_breaker_state: 渠道熔断器状态closed/open/half_open可选
"""
channel_type: ChannelType
account_id: str
plugin_state: str
adapter_states: dict[str, str]
last_message_at: datetime | None = None
last_error: str | None = None
queue_depth: int = 0
connection_pool_status: dict[str, Any] | None = None
circuit_breaker_state: Literal["closed", "open", "half_open"] | None = None # 渠道熔断器状态closed/open/half_open
@dataclass(frozen=True)
class ProbeResult:
"""探针结果HLT-003
描述一次渠道探针探测的结果,包括渠道类型、账户 ID、健康状态、延迟
与消息,用于健康检查的主动探测场景。
字段:
channel_type: 渠道类型。
account_id: 渠道账户 ID。
healthy: 是否健康。
latency_ms: 探测延迟(毫秒)。
message: 消息(可选)。
"""
channel_type: ChannelType
account_id: str
healthy: bool
latency_ms: int
message: str | None = None
@dataclass(frozen=True)
class AdapterProbeOutcome:
"""适配器探测结果值对象。
描述一次适配器 ``probe()`` 调用的原始结果,由 ``ProbeableAdapter``
返回,供应用层 ``ChannelProbe`` 映射为契约层 ``ProbeResult``。
字段:
is_available: 渠道 API 是否可用。
latency_ms: 探测延迟(毫秒)。
reason: 不可用原因或附加说明(可选)。
"""
is_available: bool
latency_ms: int
reason: str | None = None
@dataclass(frozen=True)
class DiagnosticsExportRequest:
"""诊断导出请求。
由诊断导出端口方法引用,描述一次诊断包导出请求,可选按渠道过滤并
指定审计日志与错误日志的包含数量,携带操作人以满足审计要求。
字段:
operator: 操作人(审计用)。
channel_filter: 渠道过滤(可选)。
include_audit_logs: 是否包含审计日志(默认 True
audit_log_count: 审计日志数量(默认 100
include_error_logs: 是否包含错误日志(默认 True
error_log_count: 错误日志数量(默认 100
"""
operator: Operator
channel_filter: ChannelType | None = None
include_audit_logs: bool = True
audit_log_count: int = 100
include_error_logs: bool = True
error_log_count: int = 100
def __post_init__(self) -> None:
"""校验日志条数为正整数且不超过上限HLT-EXP-01
``audit_log_count`` 与 ``error_log_count`` 必须为正整数且不超过
500与 Router 层 Pydantic schema ``le=500`` 对齐,防御深度)。
在构造时即抛出 ``ValidationError``adapter 不再做该校验INV-8
"""
if self.audit_log_count <= 0:
raise ValidationError("audit_log_count", "must be positive")
if self.audit_log_count > _LOG_COUNT_MAX:
raise ValidationError(
"audit_log_count",
f"audit_log_count must not exceed {_LOG_COUNT_MAX}, got {self.audit_log_count}",
)
if self.error_log_count <= 0:
raise ValidationError("error_log_count", "must be positive")
if self.error_log_count > _LOG_COUNT_MAX:
raise ValidationError(
"error_log_count",
f"error_log_count must not exceed {_LOG_COUNT_MAX}, got {self.error_log_count}",
)
@dataclass(frozen=True)
class DiagnosticsBundle:
"""诊断包。
描述一次诊断导出的完整结果,包括清单快照、插件状态、审计日志、错误
日志、队列深度、Worker 状态、Redis 流状态、数据库连接池状态与导出
时间,用于问题排查与可观测性。集合字段使用 tuple 以保证不可变。
字段:
manifest_snapshot: 清单快照。
plugin_states: 插件状态映射。
exported_at: 导出时间。
audit_logs: 审计日志列表(默认空 tuple
error_logs: 错误日志列表(默认空 tuple
queue_depth: 队列深度(默认 0
worker_status: Worker 状态(可选)。
redis_stream_status: Redis 流状态(可选)。
db_connection_pool_status: 数据库连接池状态(可选)。
"""
manifest_snapshot: dict[str, Any]
plugin_states: dict[str, str]
exported_at: datetime
audit_logs: tuple[dict[str, Any], ...] = ()
error_logs: tuple[dict[str, Any], ...] = ()
queue_depth: int = 0
worker_status: WorkerStatus | None = None
redis_stream_status: RedisStreamStatus | None = None
db_connection_pool_status: ConnectionPoolStatus | None = None