Files
Aether/src/plugins/manager.py
2025-12-10 20:52:44 +08:00

580 lines
19 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 importlib
import inspect
import threading
from pathlib import Path
from typing import Any, Dict, List, Optional, Type
from src.core.logger import logger
from src.plugins.auth.base import AuthPlugin
from src.plugins.cache.base import CachePlugin
# 移除审计插件 - 审计功能现在是核心服务,不再作为插件
from src.plugins.common import BasePlugin, HealthStatus, PluginMetadata
from src.plugins.load_balancer.base import LoadBalancerStrategy
from src.plugins.monitor.base import MonitorPlugin
from src.plugins.notification.base import NotificationPlugin
from src.plugins.rate_limit.base import RateLimitStrategy
from src.plugins.token.base import TokenCounterPlugin
class PluginManager:
"""
统一的插件管理器
负责加载、配置和管理所有类型的插件
"""
# 当前支持的 API 版本
SUPPORTED_API_VERSION = "1.0"
# 插件类型映射
PLUGIN_TYPES = {
"auth": AuthPlugin,
"rate_limit": RateLimitStrategy,
"cache": CachePlugin,
"monitor": MonitorPlugin,
"token": TokenCounterPlugin,
"notification": NotificationPlugin,
"load_balancer": LoadBalancerStrategy,
# 移除 "audit" - 审计功能现在是核心服务
}
def __init__(self, config: Optional[Dict[str, Any]] = None):
"""
初始化插件管理器
Args:
config: 配置字典
"""
self.config = config or {}
self.plugins: Dict[str, Dict[str, Any]] = {
"auth": {},
"rate_limit": {},
"cache": {},
"monitor": {},
"token": {},
"notification": {},
"load_balancer": {},
# 移除 "audit" - 审计功能现在是核心服务
}
self.default_plugins: Dict[str, Optional[str]] = {
"auth": None,
"rate_limit": None,
"cache": None,
"monitor": None,
"token": None,
"notification": None,
"load_balancer": "sticky_priority", # 默认使用粘性优先级策略
# 移除 "audit" - 审计功能现在是核心服务
}
# 跟踪因版本不兼容而跳过的插件
self._incompatible_plugins: List[str] = []
# 自动发现和加载插件
self._auto_discover_plugins()
# 应用配置
self._apply_config()
def _auto_discover_plugins(self):
"""自动发现和加载插件"""
plugins_dir = Path(__file__).parent
for plugin_type in self.PLUGIN_TYPES:
type_dir = plugins_dir / plugin_type
if not type_dir.exists():
continue
# 扫描插件目录
for file_path in type_dir.glob("*.py"):
if file_path.name.startswith("_") or file_path.name == "base.py":
continue
module_name = f"src.plugins.{plugin_type}.{file_path.stem}"
try:
module = importlib.import_module(module_name)
self._load_plugin_from_module(module, plugin_type)
except Exception as e:
logger.error(f"Failed to load plugin module {module_name}: {e}")
def _is_api_version_compatible(self, plugin_api_version: str) -> bool:
"""
检查插件 API 版本是否兼容
采用语义化版本的主版本号兼容策略:
- 主版本号相同则兼容
- 例如: 支持版本 "1.0",插件版本 "1.0", "1.1", "1.2" 都兼容
Args:
plugin_api_version: 插件声明的 API 版本
Returns:
是否兼容
"""
try:
supported_major = self.SUPPORTED_API_VERSION.split(".")[0]
plugin_major = plugin_api_version.split(".")[0]
return supported_major == plugin_major
except (ValueError, IndexError):
# 解析失败,假设兼容
return True
def _load_plugin_from_module(self, module: Any, plugin_type: str):
"""从模块加载插件类"""
base_class = self.PLUGIN_TYPES[plugin_type]
for name, obj in inspect.getmembers(module):
if inspect.isclass(obj) and issubclass(obj, base_class) and obj != base_class:
# 实例化插件
try:
plugin_instance = obj()
# 检查 API 版本兼容性
plugin_api_version = getattr(plugin_instance.metadata, "api_version", "1.0")
if not self._is_api_version_compatible(plugin_api_version):
logger.warning(f"Plugin {plugin_instance.name} has incompatible API version "
f"{plugin_api_version} (supported: {self.SUPPORTED_API_VERSION}), "
f"plugin will be disabled")
plugin_instance.enabled = False
self._incompatible_plugins.append(plugin_instance.name)
self.register_plugin(plugin_type, plugin_instance)
logger.info(f"Loaded {plugin_type} plugin: {plugin_instance.name}")
except Exception as e:
logger.error(f"Failed to instantiate plugin {name}: {e}")
def _apply_config(self):
"""应用配置到插件"""
for plugin_type, plugins in self.plugins.items():
type_config = self.config.get(plugin_type, {})
# 设置默认插件
if "default" in type_config:
self.default_plugins[plugin_type] = type_config["default"]
# 配置各个插件
for plugin_name, plugin in plugins.items():
plugin_config = type_config.get(plugin_name, {})
if plugin_config:
plugin.configure(plugin_config)
def register_plugin(self, plugin_type: str, plugin: Any, set_as_default: bool = False):
"""
注册插件
Args:
plugin_type: 插件类型
plugin: 插件实例
set_as_default: 是否设为默认
"""
if plugin_type not in self.plugins:
raise ValueError(f"Unknown plugin type: {plugin_type}")
# 验证插件类型
base_class = self.PLUGIN_TYPES[plugin_type]
if not isinstance(plugin, base_class):
raise TypeError(
f"Plugin must be instance of {base_class.__name__}, " f"got {type(plugin).__name__}"
)
# 注册插件
self.plugins[plugin_type][plugin.name] = plugin
# 设为默认
if set_as_default or not self.default_plugins[plugin_type]:
self.default_plugins[plugin_type] = plugin.name
logger.debug(f"Registered {plugin_type} plugin: {plugin.name}")
def unregister_plugin(self, plugin_type: str, plugin_name: str):
"""
注销插件
Args:
plugin_type: 插件类型
plugin_name: 插件名称
"""
if plugin_type in self.plugins:
if plugin_name in self.plugins[plugin_type]:
del self.plugins[plugin_type][plugin_name]
# 如果是默认插件,清除默认设置
if self.default_plugins[plugin_type] == plugin_name:
self.default_plugins[plugin_type] = None
logger.debug(f"Unregistered {plugin_type} plugin: {plugin_name}")
def get_plugin(self, plugin_type: str, plugin_name: Optional[str] = None) -> Optional[Any]:
"""
获取插件实例
Args:
plugin_type: 插件类型
plugin_name: 插件名称,不指定则返回默认插件
Returns:
插件实例如果不存在返回None
"""
if plugin_type not in self.plugins:
return None
if plugin_name:
return self.plugins[plugin_type].get(plugin_name)
# 返回默认插件
default_name = self.default_plugins[plugin_type]
if default_name:
return self.plugins[plugin_type].get(default_name)
# 如果没有默认插件,返回第一个可用的
if self.plugins[plugin_type]:
return next(iter(self.plugins[plugin_type].values()))
return None
def get_plugins_by_type(self, plugin_type: str) -> List[Any]:
"""
获取某个类型的所有插件
Args:
plugin_type: 插件类型
Returns:
插件列表
"""
if plugin_type not in self.plugins:
return []
return list(self.plugins[plugin_type].values())
def get_enabled_plugins(self, plugin_type: str) -> List[Any]:
"""
获取某个类型的所有启用的插件
Args:
plugin_type: 插件类型
Returns:
启用的插件列表
"""
plugins = self.get_plugins_by_type(plugin_type)
return [p for p in plugins if getattr(p, "enabled", True)]
async def execute_plugin_chain(
self, plugin_type: str, method_name: str, *args, **kwargs
) -> Any:
"""
执行插件链(按优先级)
Args:
plugin_type: 插件类型
method_name: 要调用的方法名
*args: 位置参数
**kwargs: 关键字参数
Returns:
第一个成功的结果
"""
plugins = self.get_enabled_plugins(plugin_type)
# 按优先级排序如果有priority属性
plugins.sort(key=lambda p: getattr(p, "priority", 0), reverse=True)
for plugin in plugins:
if hasattr(plugin, method_name):
method = getattr(plugin, method_name)
try:
if asyncio.iscoroutinefunction(method):
result = await method(*args, **kwargs)
else:
result = method(*args, **kwargs)
if result is not None:
return result
except Exception as e:
logger.error(f"Plugin {plugin.name} failed in {method_name}: {e}")
return None
def get_stats(self) -> Dict[str, Any]:
"""
获取插件管理器统计信息
Returns:
统计信息字典
"""
stats = {
"supported_api_version": self.SUPPORTED_API_VERSION,
"plugin_counts": {},
"enabled_counts": {},
"default_plugins": self.default_plugins,
"plugin_details": {},
"incompatible_plugins": self._incompatible_plugins,
}
for plugin_type in self.PLUGIN_TYPES:
all_plugins = self.get_plugins_by_type(plugin_type)
enabled_plugins = self.get_enabled_plugins(plugin_type)
stats["plugin_counts"][plugin_type] = len(all_plugins)
stats["enabled_counts"][plugin_type] = len(enabled_plugins)
# 详细信息
stats["plugin_details"][plugin_type] = [
{
"name": p.name,
"enabled": getattr(p, "enabled", True),
"priority": getattr(p, "priority", 0),
"class": type(p).__name__,
"api_version": getattr(p.metadata, "api_version", "unknown"),
"version": getattr(p.metadata, "version", "unknown"),
}
for p in all_plugins
]
return stats
async def initialize_all(self) -> Dict[str, bool]:
"""
初始化所有插件
初始化失败的插件会被自动禁用,防止后续使用未正确初始化的插件。
Returns:
初始化结果字典 {plugin_name: success}
"""
results = {}
# 获取所有插件并按依赖顺序排序
all_plugins = []
for plugin_type in self.PLUGIN_TYPES:
all_plugins.extend(self.get_plugins_by_type(plugin_type))
# 拓扑排序处理依赖
sorted_plugins = self._sort_plugins_by_dependencies(all_plugins)
# 按顺序初始化插件
for plugin in sorted_plugins:
try:
# 检查插件是否有 initialize 方法
if not hasattr(plugin, "initialize"):
# 如果没有 initialize 方法,假设插件已经初始化完成
logger.debug(f"Plugin {plugin.name} has no initialize() method, skipping")
results[f"{plugin.name}"] = True
continue
success = await plugin.initialize()
results[f"{plugin.name}"] = success
if success:
logger.info(f"Successfully initialized plugin: {plugin.name}")
else:
# 初始化失败,禁用插件
plugin.enabled = False
logger.error(f"Failed to initialize plugin: {plugin.name}, plugin has been disabled")
except Exception as e:
results[f"{plugin.name}"] = False
# 初始化异常,禁用插件
plugin.enabled = False
logger.error(f"Error initializing plugin {plugin.name}: {e}, plugin has been disabled")
return results
async def shutdown_all(self):
"""
关闭所有插件
"""
# 获取所有插件并按依赖顺序反向排序(先关闭依赖者)
all_plugins = []
for plugin_type in self.PLUGIN_TYPES:
all_plugins.extend(self.get_plugins_by_type(plugin_type))
sorted_plugins = self._sort_plugins_by_dependencies(all_plugins)
sorted_plugins.reverse() # 反向关闭
# 并发关闭插件
shutdown_tasks = []
for plugin in sorted_plugins:
# 只关闭有 shutdown 方法的插件
if hasattr(plugin, "shutdown"):
shutdown_tasks.append(plugin.shutdown())
if shutdown_tasks:
try:
await asyncio.gather(*shutdown_tasks, return_exceptions=True)
logger.info("All plugins shut down")
except Exception as e:
logger.error(f"Error during plugin shutdown: {e}")
def _sort_plugins_by_dependencies(self, plugins: List[BasePlugin]) -> List[BasePlugin]:
"""
按依赖关系对插件进行拓扑排序
Args:
plugins: 插件列表
Returns:
排序后的插件列表
Note:
存在循环依赖的插件会被自动禁用
"""
# 创建插件名称到插件对象的映射
plugin_map = {plugin.name: plugin for plugin in plugins}
# 计算每个插件的入度(被依赖的次数)
in_degree = {plugin.name: 0 for plugin in plugins}
# 构建依赖图
for plugin in plugins:
for dep in plugin.metadata.dependencies:
if dep in in_degree:
in_degree[plugin.name] += 1
# 拓扑排序
queue = [name for name, degree in in_degree.items() if degree == 0]
result = []
while queue:
current = queue.pop(0)
result.append(plugin_map[current])
# 减少依赖当前插件的其他插件的入度
current_plugin = plugin_map[current]
for plugin in plugins:
if current in plugin.metadata.dependencies:
in_degree[plugin.name] -= 1
if in_degree[plugin.name] == 0:
queue.append(plugin.name)
# 检查是否存在循环依赖
if len(result) != len(plugins):
remaining = [p for p in plugins if p not in result]
circular_names = [p.name for p in remaining]
logger.error(f"Circular dependency detected among plugins: {circular_names}. "
f"These plugins will be disabled.")
# 禁用存在循环依赖的插件,而不是继续加载
for plugin in remaining:
plugin.enabled = False
logger.warning(f"Plugin {plugin.name} has been disabled due to circular dependency")
# 不再将循环依赖的插件添加到结果中
return result
async def health_check_all(self) -> Dict[str, HealthStatus]:
"""
检查所有插件的健康状态
Returns:
健康状态字典 {plugin_name: status}
"""
results = {}
# 获取所有插件
all_plugins = []
for plugin_type in self.PLUGIN_TYPES:
all_plugins.extend(self.get_plugins_by_type(plugin_type))
# 并发检查健康状态
health_tasks = []
for plugin in all_plugins:
health_tasks.append(plugin.health_check())
if health_tasks:
health_results = await asyncio.gather(*health_tasks, return_exceptions=True)
for plugin, result in zip(all_plugins, health_results):
if isinstance(result, Exception):
results[plugin.name] = HealthStatus.UNHEALTHY
else:
results[plugin.name] = result
return results
def validate_plugin_dependencies(self) -> Dict[str, List[str]]:
"""
验证所有插件的依赖关系
Returns:
验证结果字典 {plugin_name: [missing_dependencies]}
"""
results = {}
# 获取所有可用插件名称
available_plugins = {}
for plugin_type in self.PLUGIN_TYPES:
available_plugins[plugin_type] = [p.name for p in self.get_plugins_by_type(plugin_type)]
# 检查每个插件的依赖
for plugin_type in self.PLUGIN_TYPES:
for plugin in self.get_plugins_by_type(plugin_type):
missing_deps = plugin.validate_dependencies(available_plugins)
if missing_deps:
results[plugin.name] = missing_deps
return results
def reload_plugin_config(
self, plugin_type: str, plugin_name: str, new_config: Dict[str, Any]
) -> bool:
"""
重新加载插件配置
Args:
plugin_type: 插件类型
plugin_name: 插件名称
new_config: 新配置
Returns:
是否成功重新加载
"""
plugin = self.get_plugin(plugin_type, plugin_name)
if not plugin:
return False
try:
plugin.configure(new_config)
logger.info(f"Reloaded config for plugin {plugin_name}: {new_config}")
return True
except Exception as e:
logger.error(f"Failed to reload config for plugin {plugin_name}: {e}")
return False
# 全局插件管理器实例
_plugin_manager: Optional[PluginManager] = None
_plugin_manager_lock = threading.Lock()
def get_plugin_manager(config: Optional[Dict[str, Any]] = None) -> PluginManager:
"""
获取全局插件管理器实例(线程安全)
Args:
config: 配置字典
Returns:
插件管理器实例
"""
global _plugin_manager
if _plugin_manager is None:
with _plugin_manager_lock:
# 双重检查锁定模式
if _plugin_manager is None:
_plugin_manager = PluginManager(config)
return _plugin_manager
def reset_plugin_manager():
"""重置插件管理器(用于测试)"""
global _plugin_manager
with _plugin_manager_lock:
_plugin_manager = None