Initial commit: SenSu Alpha 0.2.0
- 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)
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
#!/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)
|
||||
Reference in New Issue
Block a user