本次提交对渠道模块进行了全面升级,包含以下核心改进: 1. 新增二维码登录相关协议方法,完善登录流程 2. 优化配置监听逻辑,增加渠道运行状态前置校验 3. 重构动作注册机制,支持动态注册渠道动作并新增批量操作能力 4. 扩展渠道能力模型,新增广播、文件传输等支持 5. 优化适配器加载路径,新增元宝适配器支持 6. 新增凭证过期检查与告警能力,完善运维监控 7. 重构统计收集器,支持多维度渠道统计数据 8. 优化消息路由策略,新增策略缓存与安全处理逻辑 9. 重构基础适配器,新增凭证管理工具方法 10. 完善状态存储功能,支持凭证数据管理与批量清理 11. 重构渠道管理器,新增配置校验、动态渠道管理、限流能力 12. 优化健康检查与状态上报逻辑,完善审计日志与异常处理
100 lines
3.4 KiB
Python
100 lines
3.4 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
from datetime import UTC, datetime, timedelta
|
|
from typing import TYPE_CHECKING
|
|
|
|
from yuxi.utils.logging_config import logger
|
|
|
|
if TYPE_CHECKING:
|
|
from yuxi.channels.manager import ChannelManager
|
|
from yuxi.channels.services.runtime_state import RuntimeState
|
|
|
|
|
|
def _utc_now() -> datetime:
|
|
import time
|
|
|
|
return datetime.fromtimestamp(time.time(), tz=UTC)
|
|
|
|
|
|
class MaintenanceRunner:
|
|
def __init__(self, channel_manager: ChannelManager, state: RuntimeState):
|
|
self._manager = channel_manager
|
|
self._state = state
|
|
self._credential_expiry_alerts: int = 0
|
|
|
|
@property
|
|
def credential_expiry_alerts(self) -> int:
|
|
return self._credential_expiry_alerts
|
|
|
|
async def run(self, interval: float = 600) -> None:
|
|
logger.info("MaintenanceRunner started")
|
|
while True:
|
|
await asyncio.sleep(interval)
|
|
try:
|
|
await self._run_maintenance()
|
|
except asyncio.CancelledError:
|
|
break
|
|
except Exception:
|
|
logger.exception("MaintenanceRunner error")
|
|
|
|
async def _run_maintenance(self) -> None:
|
|
for channel_id, adapter in self._manager._adapters.items():
|
|
try:
|
|
await adapter._refresh_token_if_needed()
|
|
except Exception:
|
|
logger.debug(f"Token refresh skip for {channel_id}")
|
|
|
|
if self._manager._state_store is not None:
|
|
try:
|
|
await self._manager._state_store.cleanup_expired()
|
|
except Exception:
|
|
logger.debug("Plugin state cleanup failed")
|
|
|
|
try:
|
|
await self._check_credential_expiry()
|
|
except Exception:
|
|
logger.exception("Credential expiry check failed")
|
|
|
|
logger.debug("Maintenance cycle complete")
|
|
|
|
async def _check_credential_expiry(self) -> None:
|
|
if self._manager._state_store is None:
|
|
return
|
|
|
|
entries = await self._manager._state_store.scan_credential_entries()
|
|
now = _utc_now()
|
|
alert_count = 0
|
|
|
|
for entry in entries:
|
|
channel_id = entry["channel_id"]
|
|
expires_at_str = entry.get("expires_at")
|
|
if expires_at_str is None:
|
|
continue
|
|
|
|
expires_at = datetime.fromisoformat(expires_at_str)
|
|
remaining = expires_at - now
|
|
|
|
if remaining <= timedelta(hours=1):
|
|
if remaining <= timedelta(0):
|
|
logger.error(
|
|
f"[CREDENTIAL-ALERT] Channel '{channel_id}' credential "
|
|
f"'{entry['entry_key']}' has EXPIRED at {expires_at_str}"
|
|
)
|
|
else:
|
|
logger.warning(
|
|
f"[CREDENTIAL-ALERT] Channel '{channel_id}' credential "
|
|
f"'{entry['entry_key']}' expires in {remaining.total_seconds() / 60:.0f}min "
|
|
f"(at {expires_at_str})"
|
|
)
|
|
alert_count += 1
|
|
elif remaining <= timedelta(hours=24):
|
|
logger.warning(
|
|
f"[CREDENTIAL-WARN] Channel '{channel_id}' credential "
|
|
f"'{entry['entry_key']}' expires in {remaining.total_seconds() / 3600:.1f}h "
|
|
f"(at {expires_at_str})"
|
|
)
|
|
|
|
if alert_count > 0:
|
|
self._credential_expiry_alerts += alert_count
|