Files
WechatHookBot/utils/event_manager.py

316 lines
9.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
事件管理器模块
提供事件的注册、分发和管理:
- 优先级事件处理
- 处理器缓存优化
- 事件统计
- 异常隔离
"""
import asyncio
import time
import traceback
from collections import defaultdict
from dataclasses import dataclass, field
from typing import Any, Callable, Dict, List, Optional, Set, Tuple
from loguru import logger
@dataclass
class HandlerInfo:
"""事件处理器信息"""
handler: Callable
instance: object
priority: int
handler_name: str = field(default="")
def __post_init__(self):
if not self.handler_name:
self.handler_name = f"{self.instance.__class__.__name__}.{self.handler.__name__}"
@dataclass
class EventStats:
"""事件统计信息"""
emit_count: int = 0
handler_calls: int = 0
total_time_ms: float = 0
error_count: int = 0
stopped_count: int = 0 # 被 return False 中断的次数
class EventManager:
"""
事件管理器
特性:
- 优先级排序(高优先级先执行)
- 处理器可返回 False 中断后续处理
- 异常隔离(单个处理器异常不影响其他)
- 性能统计
"""
# 类级别存储
_handlers: Dict[str, List[HandlerInfo]] = {}
_stats: Dict[str, EventStats] = defaultdict(EventStats)
_handler_cache: Dict[str, List[HandlerInfo]] = {} # 排序后的缓存
_cache_valid: Set[str] = set()
@classmethod
def bind_instance(cls, instance: object):
"""
绑定实例的事件处理方法
扫描实例的所有方法,将带有 _event_type 属性的方法注册为事件处理器。
Args:
instance: 插件实例
"""
registered_count = 0
for method_name in dir(instance):
if method_name.startswith('_'):
continue
try:
method = getattr(instance, method_name)
except Exception:
continue
if not callable(method) or not hasattr(method, '_event_type'):
continue
event_type = getattr(method, '_event_type')
priority = getattr(method, '_priority', 50)
handler_info = HandlerInfo(
handler=method,
instance=instance,
priority=priority,
)
if event_type not in cls._handlers:
cls._handlers[event_type] = []
cls._handlers[event_type].append(handler_info)
# 使缓存失效
cls._cache_valid.discard(event_type)
registered_count += 1
logger.debug(
f"[EventManager] 注册: {handler_info.handler_name} -> "
f"{event_type} (优先级={priority})"
)
if registered_count > 0:
logger.success(
f"[EventManager] {instance.__class__.__name__} "
f"注册了 {registered_count} 个事件处理器"
)
@classmethod
def unbind_instance(cls, instance: object):
"""
解绑实例的所有事件处理器
Args:
instance: 插件实例
"""
unbound_count = 0
for event_type in list(cls._handlers.keys()):
original_count = len(cls._handlers[event_type])
cls._handlers[event_type] = [
h for h in cls._handlers[event_type]
if h.instance is not instance
]
removed = original_count - len(cls._handlers[event_type])
if removed > 0:
unbound_count += removed
cls._cache_valid.discard(event_type)
# 清理空列表
if not cls._handlers[event_type]:
del cls._handlers[event_type]
if unbound_count > 0:
logger.debug(
f"[EventManager] {instance.__class__.__name__} "
f"解绑了 {unbound_count} 个事件处理器"
)
@classmethod
def _get_sorted_handlers(cls, event_type: str) -> List[HandlerInfo]:
"""获取排序后的处理器列表(带缓存)"""
if event_type not in cls._cache_valid:
handlers = cls._handlers.get(event_type, [])
# 按优先级降序排序
cls._handler_cache[event_type] = sorted(
handlers,
key=lambda h: h.priority,
reverse=True
)
cls._cache_valid.add(event_type)
return cls._handler_cache.get(event_type, [])
@classmethod
async def emit(cls, event_type: str, *args, **kwargs) -> bool:
"""
触发事件
Args:
event_type: 事件类型
*args: 传递给处理器的位置参数(通常是 api_client, message
**kwargs: 传递给处理器的关键字参数
Returns:
True 表示所有处理器都执行了False 表示被中断
"""
handlers = cls._get_sorted_handlers(event_type)
if not handlers:
logger.debug(f"[EventManager] 事件 {event_type} 没有处理器")
return True
# 更新统计
stats = cls._stats[event_type]
stats.emit_count += 1
start_time = time.time()
all_completed = True
logger.debug(
f"[EventManager] 触发: {event_type}, "
f"处理器数量: {len(handlers)}"
)
for handler_info in handlers:
stats.handler_calls += 1
try:
logger.debug(f"[EventManager] 调用: {handler_info.handler_name}")
result = await handler_info.handler(*args, **kwargs)
# 检查是否中断
if result is False:
stats.stopped_count += 1
all_completed = False
logger.debug(
f"[EventManager] {handler_info.handler_name} "
f"返回 False中断事件处理"
)
break
except Exception as e:
stats.error_count += 1
logger.error(
f"[EventManager] {handler_info.handler_name} 执行失败: {e}"
)
logger.debug(f"详细错误:\n{traceback.format_exc()}")
# 继续执行其他处理器
elapsed_ms = (time.time() - start_time) * 1000
stats.total_time_ms += elapsed_ms
return all_completed
@classmethod
async def emit_parallel(
cls,
event_type: str,
*args,
max_concurrency: int = 5,
**kwargs
) -> List[Any]:
"""
并行触发事件(忽略优先级和中断)
适用于不需要顺序执行的场景。
Args:
event_type: 事件类型
max_concurrency: 最大并发数
*args, **kwargs: 传递给处理器的参数
Returns:
所有处理器的返回值列表
"""
handlers = cls._get_sorted_handlers(event_type)
if not handlers:
return []
semaphore = asyncio.Semaphore(max_concurrency)
async def run_handler(handler_info: HandlerInfo):
async with semaphore:
try:
return await handler_info.handler(*args, **kwargs)
except Exception as e:
logger.error(f"[EventManager] {handler_info.handler_name} 失败: {e}")
return None
tasks = [run_handler(h) for h in handlers]
return await asyncio.gather(*tasks, return_exceptions=True)
@classmethod
def get_handlers(cls, event_type: str) -> List[str]:
"""获取事件的所有处理器名称"""
handlers = cls._get_sorted_handlers(event_type)
return [h.handler_name for h in handlers]
@classmethod
def get_all_events(cls) -> List[str]:
"""获取所有已注册的事件类型"""
return list(cls._handlers.keys())
@classmethod
def get_stats(cls, event_type: str = None) -> Dict[str, Any]:
"""
获取事件统计信息
Args:
event_type: 指定事件类型None 返回所有
Returns:
统计信息字典
"""
if event_type:
stats = cls._stats.get(event_type, EventStats())
return {
"emit_count": stats.emit_count,
"handler_calls": stats.handler_calls,
"total_time_ms": stats.total_time_ms,
"avg_time_ms": stats.total_time_ms / max(stats.emit_count, 1),
"error_count": stats.error_count,
"stopped_count": stats.stopped_count,
}
return {
event: cls.get_stats(event)
for event in cls._stats.keys()
}
@classmethod
def reset_stats(cls):
"""重置所有统计"""
cls._stats.clear()
@classmethod
def clear(cls):
"""清除所有处理器和统计(用于测试)"""
cls._handlers.clear()
cls._handler_cache.clear()
cls._cache_valid.clear()
cls._stats.clear()
# ==================== 导出 ====================
__all__ = ['EventManager', 'HandlerInfo', 'EventStats']