新增了完整的渠道诊断修复工具,包含数据模型定义与核心执行逻辑: 1. 定义了检查状态、严重性、修复模式等枚举类型 2. 实现了诊断选项、各类检查结果、修复步骤与结果等数据类 3. 提供了ChannelDoctorRunner核心类,支持单渠道/多渠道诊断、单步骤/全量修复 4. 实现了配置规范化、过期配置清理、权限/凭证/连通性检查等完整流程 5. 支持诊断结果序列化为字典格式,以及上下文管理器式的运行器管理
265 lines
7.4 KiB
Python
265 lines
7.4 KiB
Python
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import time
|
|
from dataclasses import dataclass, field
|
|
from enum import StrEnum
|
|
|
|
|
|
class CheckStatus(StrEnum):
|
|
PASS = "pass"
|
|
WARN = "warn"
|
|
FAIL = "fail"
|
|
SKIP = "skip"
|
|
|
|
|
|
class Severity(StrEnum):
|
|
INFO = "info"
|
|
WARNING = "warning"
|
|
ERROR = "error"
|
|
|
|
|
|
class DoctorRepairMode(StrEnum):
|
|
NONE = "none"
|
|
YES = "yes"
|
|
REPAIR = "repair"
|
|
FORCE = "force"
|
|
NON_INTERACTIVE = "non_interactive"
|
|
DEEP = "deep"
|
|
|
|
|
|
@dataclass
|
|
class DoctorOptions:
|
|
repair_mode: DoctorRepairMode = DoctorRepairMode.NONE
|
|
deep: bool = False
|
|
generate_gateway_token: bool = False
|
|
workspace_suggestions: bool = True
|
|
|
|
@property
|
|
def should_repair(self) -> bool:
|
|
return self.repair_mode in (DoctorRepairMode.REPAIR, DoctorRepairMode.FORCE, DoctorRepairMode.YES)
|
|
|
|
@property
|
|
def should_force(self) -> bool:
|
|
return self.repair_mode == DoctorRepairMode.FORCE
|
|
|
|
@property
|
|
def is_non_interactive(self) -> bool:
|
|
return self.repair_mode in (
|
|
DoctorRepairMode.NON_INTERACTIVE,
|
|
DoctorRepairMode.REPAIR,
|
|
DoctorRepairMode.YES,
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class CredentialCheckResult:
|
|
status: CheckStatus
|
|
token_obtained: bool
|
|
expires_in: int | None = None
|
|
error: str | None = None
|
|
detail: dict = field(default_factory=dict)
|
|
|
|
|
|
@dataclass
|
|
class PermissionCheckResult:
|
|
status: CheckStatus
|
|
can_send_message: bool = False
|
|
can_read_message: bool = False
|
|
can_manage_group: bool = False
|
|
missing_permissions: list[str] = field(default_factory=list)
|
|
error: str | None = None
|
|
|
|
|
|
@dataclass
|
|
class ConnectivityCheckResult:
|
|
status: CheckStatus
|
|
latency_ms: float | None = None
|
|
endpoint: str = ""
|
|
error: str | None = None
|
|
|
|
|
|
@dataclass
|
|
class DiagnosisWarning:
|
|
severity: Severity
|
|
code: str
|
|
message: str
|
|
suggestion: str
|
|
|
|
|
|
@dataclass
|
|
class LegacyConfigRule:
|
|
old_key: str
|
|
new_key: str
|
|
transform: str | None = None
|
|
|
|
|
|
@dataclass
|
|
class ConfigMutation:
|
|
description: str = ""
|
|
old_value: str = ""
|
|
new_value: str = ""
|
|
key: str = ""
|
|
version: int = 0
|
|
timestamp: float = field(default_factory=time.time)
|
|
operator: str = "system"
|
|
reason: str = ""
|
|
checksum: str = ""
|
|
|
|
def __post_init__(self):
|
|
if not self.checksum:
|
|
self.checksum = self._compute_checksum()
|
|
|
|
def _compute_checksum(self) -> str:
|
|
payload = f"{self.key}|{self.description}|{self.old_value}|{self.new_value}"
|
|
return hashlib.sha256(payload.encode("utf-8")).hexdigest()
|
|
|
|
|
|
@dataclass
|
|
class ChannelStatusWarning:
|
|
channel_type: str
|
|
account_id: str
|
|
status: CheckStatus
|
|
message: str
|
|
detail: dict = field(default_factory=dict)
|
|
|
|
|
|
@dataclass
|
|
class RepairStep:
|
|
id: str
|
|
description: str
|
|
action: str
|
|
reversible: bool = True
|
|
params: dict = field(default_factory=dict)
|
|
requires_interactive: bool = False
|
|
is_aggressive: bool = False
|
|
|
|
|
|
@dataclass
|
|
class RepairResult:
|
|
step_id: str
|
|
success: bool
|
|
message: str
|
|
config_before: dict | None = None
|
|
config_after: dict | None = None
|
|
|
|
|
|
@dataclass
|
|
class DiagnosisResult:
|
|
channel_type: str
|
|
account_id: str
|
|
overall_status: CheckStatus
|
|
timestamp: float
|
|
|
|
credential: CredentialCheckResult | None = None
|
|
permission: PermissionCheckResult | None = None
|
|
connectivity: ConnectivityCheckResult | None = None
|
|
|
|
config_before: dict | None = None
|
|
config_after: dict | None = None
|
|
config_changes: list[ConfigMutation] = field(default_factory=list)
|
|
|
|
warnings: list[DiagnosisWarning] = field(default_factory=list)
|
|
security_warnings: list[str] = field(default_factory=list)
|
|
channel_status_warnings: list[ChannelStatusWarning] = field(default_factory=list)
|
|
|
|
repair_plan: list[RepairStep] = field(default_factory=list)
|
|
repair_results: list[RepairResult] = field(default_factory=list)
|
|
|
|
def to_dict(self) -> dict:
|
|
result: dict = {
|
|
"channel_type": self.channel_type,
|
|
"account_id": self.account_id,
|
|
"overall_status": self.overall_status.value,
|
|
"timestamp": self.timestamp,
|
|
"credential": self._credential_dict(),
|
|
"permission": self._permission_dict(),
|
|
"connectivity": self._connectivity_dict(),
|
|
"config_before": self.config_before,
|
|
"config_after": self.config_after,
|
|
"config_changes": [
|
|
{
|
|
"key": c.key,
|
|
"description": c.description,
|
|
"old_value": c.old_value,
|
|
"new_value": c.new_value,
|
|
"version": c.version,
|
|
"timestamp": c.timestamp,
|
|
"operator": c.operator,
|
|
"reason": c.reason,
|
|
"checksum": c.checksum,
|
|
}
|
|
for c in self.config_changes
|
|
],
|
|
"warnings": [
|
|
{"severity": w.severity.value, "code": w.code, "message": w.message, "suggestion": w.suggestion}
|
|
for w in self.warnings
|
|
],
|
|
"security_warnings": self.security_warnings,
|
|
"channel_status_warnings": [
|
|
{
|
|
"channel_type": c.channel_type,
|
|
"account_id": c.account_id,
|
|
"status": c.status.value,
|
|
"message": c.message,
|
|
"detail": c.detail,
|
|
}
|
|
for c in self.channel_status_warnings
|
|
],
|
|
"repair_plan": [
|
|
{
|
|
"id": s.id,
|
|
"description": s.description,
|
|
"action": s.action,
|
|
"reversible": s.reversible,
|
|
"params": s.params,
|
|
"requires_interactive": s.requires_interactive,
|
|
"is_aggressive": s.is_aggressive,
|
|
}
|
|
for s in self.repair_plan
|
|
],
|
|
"repair_results": [
|
|
{
|
|
"step_id": r.step_id,
|
|
"success": r.success,
|
|
"message": r.message,
|
|
"config_before": r.config_before,
|
|
"config_after": r.config_after,
|
|
}
|
|
for r in self.repair_results
|
|
],
|
|
}
|
|
return result
|
|
|
|
def _credential_dict(self) -> dict | None:
|
|
if not self.credential:
|
|
return None
|
|
return {
|
|
"status": self.credential.status.value,
|
|
"token_obtained": self.credential.token_obtained,
|
|
"expires_in": self.credential.expires_in,
|
|
"error": self.credential.error,
|
|
}
|
|
|
|
def _permission_dict(self) -> dict | None:
|
|
if not self.permission:
|
|
return None
|
|
return {
|
|
"status": self.permission.status.value,
|
|
"can_send_message": self.permission.can_send_message,
|
|
"can_read_message": self.permission.can_read_message,
|
|
"can_manage_group": self.permission.can_manage_group,
|
|
"missing_permissions": self.permission.missing_permissions,
|
|
"error": self.permission.error,
|
|
}
|
|
|
|
def _connectivity_dict(self) -> dict | None:
|
|
if not self.connectivity:
|
|
return None
|
|
return {
|
|
"status": self.connectivity.status.value,
|
|
"latency_ms": self.connectivity.latency_ms,
|
|
"endpoint": self.connectivity.endpoint,
|
|
"error": self.connectivity.error,
|
|
}
|