e6875f0b4b
- 13-service async plugin framework - Textual TUI with CLI fallback - Plugin hot-reload + permission system - Web management panel (aiohttp) - Bridge-based inter-module communication - 10 regression tests Fixes applied: - PBKDF2-SHA256 auth (was plain SHA256) - Auth bypass removed (was allow-all on fail) - Bare excepts replaced with logged errors - CatFramework/DreamSu -> SenSu naming unified - ServiceManager: health checks + startup_order - Env var credentials (SENSU_ADMIN_PASSWORD etc)
180 lines
6.5 KiB
Python
180 lines
6.5 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
import logging
|
|
import asyncio
|
|
from typing import Dict, List, Callable, Any
|
|
from enum import Enum
|
|
import json
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class MessageType(Enum):
|
|
"""消息类型枚举"""
|
|
EVENT = "event"
|
|
COMMAND = "command"
|
|
DATA = "data"
|
|
STATUS = "status"
|
|
ERROR = "error"
|
|
|
|
class CoreBridge:
|
|
"""核心桥接服务 - 用于框架模块间通信"""
|
|
|
|
def __init__(self):
|
|
self.subscribers: Dict[str, List[Callable]] = {}
|
|
self.message_queue: asyncio.Queue = asyncio.Queue()
|
|
self.is_running = False
|
|
self.processing_task = None
|
|
logger.debug("CoreBridge初始化开始")
|
|
|
|
async def start(self):
|
|
"""启动桥接服务"""
|
|
try:
|
|
logger.info("启动核心桥接服务")
|
|
self.is_running = True
|
|
self.processing_task = asyncio.create_task(self._process_messages())
|
|
logger.debug("核心桥接服务启动完成")
|
|
except Exception as e:
|
|
logger.error(f"启动核心桥接服务时出错: {str(e)}", exc_info=True)
|
|
raise
|
|
|
|
def subscribe(self, topic: str, callback: Callable):
|
|
"""订阅主题"""
|
|
try:
|
|
if topic not in self.subscribers:
|
|
self.subscribers[topic] = []
|
|
|
|
self.subscribers[topic].append(callback)
|
|
logger.debug(f"订阅主题: {topic}, 当前订阅者数: {len(self.subscribers[topic])}")
|
|
except Exception as e:
|
|
logger.error(f"订阅主题 {topic} 时出错: {str(e)}", exc_info=True)
|
|
raise
|
|
|
|
def unsubscribe(self, topic: str, callback: Callable):
|
|
"""取消订阅"""
|
|
try:
|
|
if topic in self.subscribers and callback in self.subscribers[topic]:
|
|
self.subscribers[topic].remove(callback)
|
|
logger.debug(f"取消订阅主题: {topic}, 剩余订阅者数: {len(self.subscribers[topic])}")
|
|
except Exception as e:
|
|
logger.error(f"取消订阅主题 {topic} 时出错: {str(e)}", exc_info=True)
|
|
|
|
async def publish(self, topic: str, message: Dict, msg_type: MessageType = MessageType.DATA):
|
|
"""发布消息"""
|
|
try:
|
|
message_data = {
|
|
"topic": topic,
|
|
"type": msg_type.value,
|
|
"data": message,
|
|
"timestamp": asyncio.get_event_loop().time()
|
|
}
|
|
|
|
await self.message_queue.put(message_data)
|
|
logger.debug(f"发布消息到主题: {topic}, 类型: {msg_type.value}")
|
|
|
|
except Exception as e:
|
|
logger.error(f"发布消息到主题 {topic} 时出错: {str(e)}", exc_info=True)
|
|
raise
|
|
|
|
async def _process_messages(self):
|
|
"""处理消息队列"""
|
|
try:
|
|
logger.debug("开始处理消息队列")
|
|
|
|
while self.is_running:
|
|
try:
|
|
# 等待消息,带超时以便检查运行状态
|
|
message = await asyncio.wait_for(self.message_queue.get(), timeout=1.0)
|
|
|
|
# 分发消息给订阅者
|
|
await self._dispatch_message(message)
|
|
|
|
# 标记任务完成
|
|
self.message_queue.task_done()
|
|
|
|
except asyncio.TimeoutError:
|
|
continue
|
|
except Exception as e:
|
|
logger.error(f"处理消息时出错: {str(e)}", exc_info=True)
|
|
continue
|
|
|
|
logger.debug("消息队列处理结束")
|
|
|
|
except Exception as e:
|
|
logger.error(f"消息队列处理循环出错: {str(e)}", exc_info=True)
|
|
|
|
async def _dispatch_message(self, message: Dict):
|
|
"""分发消息给订阅者"""
|
|
try:
|
|
topic = message["topic"]
|
|
|
|
if topic not in self.subscribers:
|
|
logger.debug(f"主题 {topic} 没有订阅者")
|
|
return
|
|
|
|
subscribers = self.subscribers[topic][:] # 复制列表避免在迭代时修改
|
|
|
|
# 并行调用所有订阅者
|
|
tasks = []
|
|
for callback in subscribers:
|
|
task = asyncio.create_task(self._call_subscriber(callback, message))
|
|
tasks.append(task)
|
|
|
|
if tasks:
|
|
await asyncio.gather(*tasks, return_exceptions=True)
|
|
|
|
logger.debug(f"消息分发完成,主题: {topic}, 订阅者数: {len(subscribers)}")
|
|
|
|
except Exception as e:
|
|
logger.error(f"分发消息时出错: {str(e)}", exc_info=True)
|
|
|
|
async def _call_subscriber(self, callback: Callable, message: Dict):
|
|
"""调用订阅者回调"""
|
|
try:
|
|
if asyncio.iscoroutinefunction(callback):
|
|
await callback(message)
|
|
else:
|
|
callback(message)
|
|
|
|
except Exception as e:
|
|
logger.error(f"调用订阅者回调时出错: {str(e)}", exc_info=True)
|
|
|
|
def get_subscriber_count(self, topic: str = None) -> int:
|
|
"""获取订阅者数量"""
|
|
try:
|
|
if topic:
|
|
count = len(self.subscribers.get(topic, []))
|
|
logger.debug(f"主题 {topic} 的订阅者数量: {count}")
|
|
return count
|
|
else:
|
|
total = sum(len(subs) for subs in self.subscribers.values())
|
|
logger.debug(f"总订阅者数量: {total}")
|
|
return total
|
|
except Exception as e:
|
|
logger.error(f"获取订阅者数量时出错: {str(e)}", exc_info=True)
|
|
return 0
|
|
|
|
async def shutdown(self):
|
|
"""关闭桥接服务"""
|
|
try:
|
|
logger.info("关闭核心桥接服务")
|
|
self.is_running = False
|
|
|
|
# 等待处理任务结束
|
|
if self.processing_task:
|
|
await asyncio.wait_for(self.processing_task, timeout=5.0)
|
|
|
|
# 清空队列和订阅者
|
|
self.subscribers.clear()
|
|
while not self.message_queue.empty():
|
|
try:
|
|
self.message_queue.get_nowait()
|
|
self.message_queue.task_done()
|
|
except asyncio.QueueEmpty:
|
|
break
|
|
|
|
logger.debug("核心桥接服务关闭完成")
|
|
|
|
except Exception as e:
|
|
logger.error(f"关闭核心桥接服务时出错: {str(e)}", exc_info=True)
|