本次提交包含多维度代码优化与功能增强: 1. 移除报告模块冗余导入与枚举,清理报表相关代码 2. 新增扫码登录支持方法与飞书适配器适配 3. 完善异常日志与健康检查信息 4. 扩展目录、配对管理、能力查询等接口 5. 优化出站管道与事务提交后钩子逻辑 6. 修复飞书消息解析与响应空值问题 7. 重构配置更新与服务账号创建逻辑 8. 统一传输错误分类契约与错误基类扩展
400 lines
15 KiB
Python
400 lines
15 KiB
Python
"""健康检查 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.0;ARQ 客户端当前不暴露
|
||
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
|