本次提交对渠道模块进行了全面升级,包含以下核心改进: 1. 新增二维码登录相关协议方法,完善登录流程 2. 优化配置监听逻辑,增加渠道运行状态前置校验 3. 重构动作注册机制,支持动态注册渠道动作并新增批量操作能力 4. 扩展渠道能力模型,新增广播、文件传输等支持 5. 优化适配器加载路径,新增元宝适配器支持 6. 新增凭证过期检查与告警能力,完善运维监控 7. 重构统计收集器,支持多维度渠道统计数据 8. 优化消息路由策略,新增策略缓存与安全处理逻辑 9. 重构基础适配器,新增凭证管理工具方法 10. 完善状态存储功能,支持凭证数据管理与批量清理 11. 重构渠道管理器,新增配置校验、动态渠道管理、限流能力 12. 优化健康检查与状态上报逻辑,完善审计日志与异常处理
129 lines
4.9 KiB
Python
129 lines
4.9 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import hashlib
|
|
import json
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
from yuxi.utils.logging_config import logger
|
|
|
|
if TYPE_CHECKING:
|
|
from yuxi.channels.manager import ChannelManager
|
|
|
|
|
|
class ConfigWatcher:
|
|
def __init__(self, channel_manager: ChannelManager):
|
|
self._manager = channel_manager
|
|
self._last_config_hash: str | None = None
|
|
self._watched_prefixes: list[str] = ["channels"]
|
|
self._noop_prefixes: list[str] = []
|
|
self._running = False
|
|
self._task: asyncio.Task | None = None
|
|
|
|
def set_watched_prefixes(self, prefixes: list[str]) -> None:
|
|
self._watched_prefixes = list(prefixes)
|
|
|
|
def set_noop_prefixes(self, prefixes: list[str]) -> None:
|
|
self._noop_prefixes = list(prefixes)
|
|
|
|
async def watch(self, interval: float = 30.0) -> None:
|
|
if self._running:
|
|
return
|
|
self._running = True
|
|
self._last_config_hash = self._compute_config_hash()
|
|
self._task = asyncio.create_task(self._watch_loop(interval))
|
|
|
|
async def stop(self) -> None:
|
|
self._running = False
|
|
if self._task:
|
|
self._task.cancel()
|
|
try:
|
|
await self._task
|
|
except asyncio.CancelledError:
|
|
pass
|
|
self._task = None
|
|
|
|
async def reload_now(self, changed_keys: list[str] | None = None) -> dict[str, Any]:
|
|
if changed_keys is None:
|
|
diff = self._compute_config_diff()
|
|
else:
|
|
diff = self._compute_diff_from_keys(changed_keys)
|
|
return await self._apply_diff(diff)
|
|
|
|
async def _watch_loop(self, interval: float) -> None:
|
|
while self._running:
|
|
await asyncio.sleep(interval)
|
|
if not self._running:
|
|
break
|
|
try:
|
|
current = self._compute_config_hash()
|
|
if current != self._last_config_hash:
|
|
logger.info("Config change detected, computing diff")
|
|
diff = self._compute_config_diff()
|
|
await self._apply_diff(diff)
|
|
self._last_config_hash = current
|
|
except asyncio.CancelledError:
|
|
break
|
|
except Exception:
|
|
logger.exception("ConfigWatcher loop error")
|
|
|
|
def _compute_config_hash(self) -> str:
|
|
raw = json.dumps(self._manager._channels_config, sort_keys=True, default=str)
|
|
return hashlib.sha256(raw.encode()).hexdigest()
|
|
|
|
def _compute_config_diff(self) -> dict[str, dict[str, Any]]:
|
|
diff: dict[str, dict[str, Any]] = {}
|
|
current_channels = set(self._manager._channels_config.keys())
|
|
running_channels = set(self._manager._adapters.keys())
|
|
|
|
added = current_channels - running_channels
|
|
removed = running_channels - current_channels
|
|
|
|
for channel_id in added:
|
|
diff[channel_id] = {"action": "start", "config": self._manager._channels_config[channel_id]}
|
|
|
|
for channel_id in removed:
|
|
diff[channel_id] = {"action": "stop"}
|
|
|
|
for channel_id in current_channels & running_channels:
|
|
diff[channel_id] = {"action": "restart"}
|
|
|
|
return diff
|
|
|
|
def _compute_diff_from_keys(self, changed_keys: list[str]) -> dict[str, dict[str, Any]]:
|
|
diff: dict[str, dict[str, Any]] = {}
|
|
for key in changed_keys:
|
|
for prefix in self._watched_prefixes:
|
|
if not key.startswith(prefix):
|
|
continue
|
|
if any(key.startswith(noop) for noop in self._noop_prefixes):
|
|
continue
|
|
parts = key.split(".")
|
|
if len(parts) >= 2:
|
|
channel_id = parts[1]
|
|
diff[channel_id] = {"action": "restart"}
|
|
break
|
|
return diff
|
|
|
|
async def _apply_diff(self, diff: dict) -> dict[str, Any]:
|
|
results: dict[str, Any] = {}
|
|
for channel_id, action in diff.items():
|
|
try:
|
|
if action.get("action") == "start":
|
|
if self._manager._channels_config.get(channel_id, {}).get("enabled", False):
|
|
await self._manager.start_channel(channel_id, action.get("config"))
|
|
results[channel_id] = "started"
|
|
elif action.get("action") == "stop":
|
|
if self._manager.is_running(channel_id):
|
|
await self._manager.stop_channel(channel_id)
|
|
results[channel_id] = "stopped"
|
|
else:
|
|
results[channel_id] = "already_stopped"
|
|
elif action.get("action") == "restart":
|
|
await self._manager.restart_channel(channel_id)
|
|
results[channel_id] = "restarted"
|
|
except Exception:
|
|
logger.exception(f"ConfigWatcher failed to apply diff for {channel_id}")
|
|
results[channel_id] = "failed"
|
|
return results
|