File size: 11,320 Bytes
b6db694 d0c18f0 b6db694 27beae4 b6db694 d0c18f0 b6db694 27beae4 d0c18f0 b6db694 d0c18f0 27beae4 b6db694 d0c18f0 27beae4 b6db694 d0c18f0 27beae4 d0c18f0 27beae4 d0c18f0 27beae4 d0c18f0 27beae4 10b8d56 d0c18f0 10b8d56 27beae4 d0c18f0 27beae4 d0c18f0 10b8d56 d0c18f0 10b8d56 d0c18f0 10b8d56 d0c18f0 10b8d56 d0c18f0 27beae4 b6db694 d0c18f0 b6db694 d0c18f0 b6db694 27beae4 d0c18f0 27beae4 b6db694 27beae4 b6db694 d0c18f0 10b8d56 d0c18f0 10b8d56 d0c18f0 10b8d56 d0c18f0 10b8d56 d0c18f0 10b8d56 d0c18f0 10b8d56 d0c18f0 10b8d56 d0c18f0 b6db694 27beae4 b6db694 d0c18f0 b6db694 d0c18f0 b6db694 27beae4 b6db694 d0c18f0 b6db694 d0c18f0 27beae4 d0c18f0 b6db694 d0c18f0 b6db694 e5e756a d0c18f0 b6db694 e5e756a 27beae4 b6db694 d0c18f0 b6db694 27beae4 d0c18f0 b6db694 27beae4 10b8d56 b6db694 27beae4 b6db694 27beae4 b6db694 27beae4 d0c18f0 b6db694 27beae4 b6db694 27beae4 d0c18f0 b6db694 27beae4 b6db694 d0c18f0 27beae4 b6db694 d0c18f0 b6db694 27beae4 10b8d56 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 | """
插件加载器 - 按需加载,惰性实例化
"""
import importlib.util
import sys
import logging
from pathlib import Path
from typing import Dict, Any, Optional, List, Callable
from fastapi import APIRouter, FastAPI
from app.plugins.models import PluginInfo, PluginStatus
from app.config.settings import settings
logger = logging.getLogger(__name__)
class DefaultPlugin:
"""默认插件实现 - 无生命周期钩子"""
def on_enable(self):
pass
def on_disable(self):
pass
class PluginLoader:
"""插件加载器 - 仅加载启用插件,惰性实例化"""
def __init__(self):
self._loaded_plugins: Dict[str, Any] = {}
self._plugin_routers: Dict[str, APIRouter] = {}
self._plugin_factories: Dict[str, Dict] = {} # 存储工厂函数,延迟实例化
def load_plugin(self, plugin_info: PluginInfo) -> bool:
"""加载插件模块(仅导入,不实例化)"""
plugin_name = plugin_info.metadata.name
if plugin_name in self._plugin_factories:
return True
plugin_path = Path(plugin_info.install_path)
# 1. 动态导入插件主模块
main_module = self._import_plugin_module(plugin_path, "main")
if not main_module:
logger.error(f"无法加载插件主模块: {plugin_name}")
return False
# 2. 存储工厂函数而非实例
factory = self._get_plugin_factory(main_module)
self._plugin_factories[plugin_name] = {
"factory": factory,
"module": main_module,
"info": plugin_info,
}
# 3. 仅对已启用插件执行实例化并调用 on_enable
logger.info(f"插件状态检查: {plugin_name} - status={plugin_info.status}, is_enabled={plugin_info.status == PluginStatus.ENABLED}")
if plugin_info.status == PluginStatus.ENABLED:
instance = self._instantiate_plugin(plugin_name)
logger.info(f"实例化结果: {plugin_name} - instance={instance is not None}, has_on_enable={hasattr(instance, 'on_enable') if instance else False}")
if instance and hasattr(instance, "on_enable"):
instance.on_enable()
logger.info(f"已启用插件: {plugin_name}")
logger.info(f"插件模块已加载: {plugin_name}")
return True
def load_plugin_from_dir(self, plugin_dir: Path) -> bool:
"""从目录加载插件模块(兼容新的 manager 接口)
Args:
plugin_dir: 插件目录路径
Returns:
是否加载成功
"""
plugin_name = plugin_dir.name
# 如果已加载,返回 True
if plugin_name in self._plugin_factories:
return True
# 动态导入插件主模块
main_module = self._import_plugin_module(plugin_dir, "main")
if not main_module:
logger.error(f"无法加载插件主模块: {plugin_name}")
return False
# 存储工厂函数
factory = self._get_plugin_factory(main_module)
self._plugin_factories[plugin_name] = {
"factory": factory,
"module": main_module,
"info": None, # 不再依赖 PluginInfo
}
# 实例化并调用 on_enable
instance = self._instantiate_plugin(plugin_name)
if instance and hasattr(instance, "on_enable"):
instance.on_enable()
logger.info(f"已启用插件: {plugin_name}")
logger.info(f"插件模块已加载: {plugin_name}")
return True
def _instantiate_plugin(self, plugin_name: str) -> Optional[Any]:
"""实例化插件"""
factory_data = self._plugin_factories.get(plugin_name)
logger.info(f"_instantiate_plugin: {plugin_name} - factory_data={factory_data is not None}")
if not factory_data:
return None
if plugin_name in self._loaded_plugins:
logger.info(f"_instantiate_plugin: {plugin_name} - 已存在实例")
return self._loaded_plugins[plugin_name]["instance"]
logger.info(f"_instantiate_plugin: {plugin_name} - 创建新实例, factory={factory_data['factory']}")
instance = factory_data["factory"]()
logger.info(f"_instantiate_plugin: {plugin_name} - instance={instance is not None}, type={type(instance).__name__ if instance else 'None'}")
self._loaded_plugins[plugin_name] = {
"instance": instance,
"module": factory_data["module"],
"info": factory_data["info"],
}
return instance
def _get_plugin_factory(self, module) -> Callable:
"""获取插件工厂函数"""
plugin_class = None
if hasattr(module, "Plugin"):
plugin_class = getattr(module, "Plugin")
else:
for attr_name in dir(module):
if attr_name.endswith("Plugin") and attr_name != "Plugin":
plugin_class = getattr(module, attr_name)
break
if plugin_class and callable(plugin_class):
return plugin_class
else:
return DefaultPlugin
def unload_plugin(self, plugin_name: str) -> bool:
"""卸载插件"""
if plugin_name in self._loaded_plugins:
try:
plugin_data = self._loaded_plugins[plugin_name]
if hasattr(plugin_data["instance"], "on_disable"):
plugin_data["instance"].on_disable()
del self._loaded_plugins[plugin_name]
if plugin_name in self._plugin_routers:
del self._plugin_routers[plugin_name]
logger.info(f"成功卸载插件: {plugin_name}")
return True
except Exception as e:
logger.error(f"卸载插件失败 {plugin_name}: {e}")
return False
return False
def enable_plugin(self, plugin_name: str) -> bool:
"""启用插件 - 惰性实例化并调用 on_enable"""
logger.info(f"enable_plugin: {plugin_name} - 已在loaded_plugins={plugin_name in self._loaded_plugins}")
if plugin_name in self._loaded_plugins:
instance = self._loaded_plugins[plugin_name]["instance"]
logger.info(f"enable_plugin: {plugin_name} - 调用 on_enable (已存在实例)")
if hasattr(instance, "on_enable"):
instance.on_enable()
logger.info(f"enable_plugin: {plugin_name} - on_enable 已调用")
return True
logger.info(f"enable_plugin: {plugin_name} - 调用 _instantiate_plugin")
instance = self._instantiate_plugin(plugin_name)
logger.info(f"enable_plugin: {plugin_name} - instance={instance is not None}")
if instance:
if hasattr(instance, "on_enable"):
logger.info(f"enable_plugin: {plugin_name} - 调用 on_enable (新实例)")
instance.on_enable()
logger.info(f"插件 {plugin_name} 已启用")
return True
return False
def disable_plugin(self, plugin_name: str) -> bool:
"""禁用插件 - 调用 on_disable"""
if plugin_name in self._loaded_plugins:
try:
plugin_data = self._loaded_plugins[plugin_name]
if hasattr(plugin_data["instance"], "on_disable"):
plugin_data["instance"].on_disable()
return True
except Exception as e:
logger.error(f"禁用插件失败 {plugin_name}: {e}")
return False
return True
def register_plugin_routes(self, app: FastAPI, plugin_info: PluginInfo) -> bool:
"""注册插件路由"""
try:
plugin_path = Path(plugin_info.install_path)
plugin_name = plugin_info.metadata.name
api_router = self.create_api_router(plugin_path, plugin_info)
if api_router:
base_path = f"/plugins/{plugin_name}/api"
app.include_router(api_router, prefix=base_path)
api_base_path = f"/api/plugins/{plugin_name}"
app.include_router(api_router, prefix=api_base_path)
self._plugin_routers[plugin_name] = api_router
logger.info(f"注册API路由: {base_path}")
logger.info(f"注册API兼容路由: {api_base_path}")
return True
except Exception as e:
logger.error(f"注册插件路由失败 {plugin_name}: {e}")
return False
def _import_plugin_module(self, plugin_path: Path, module_name: str) -> Optional[Any]:
"""动态导入插件模块"""
try:
module_file = plugin_path / f"{module_name}.py"
if not module_file.exists():
return None
full_module_name = f"plugins.{plugin_path.name}.{module_name}"
# 如果模块已存在,返回已存在的模块(避免重复导入导致不同实例)
if full_module_name in sys.modules:
return sys.modules[full_module_name]
spec = importlib.util.spec_from_file_location(full_module_name, module_file)
if spec is None or spec.loader is None:
return None
module = importlib.util.module_from_spec(spec)
sys.modules[full_module_name] = module
spec.loader.exec_module(module)
return module
except Exception as e:
logger.error(f"导入模块失败 {module_name}: {e}")
return None
def create_api_router(self, plugin_path: Path, plugin_info: PluginInfo) -> Optional[APIRouter]:
"""创建 API 路由器"""
try:
api_module = self._import_plugin_module(plugin_path, "api")
if api_module and hasattr(api_module, "router"):
router = getattr(api_module, "router")
if isinstance(router, APIRouter):
if hasattr(api_module, "set_plugin_instance"):
plugin_instance = self.get_plugin_instance(plugin_info.metadata.name)
if plugin_instance:
api_module.set_plugin_instance(plugin_instance)
return router
return None
except Exception as e:
logger.error(f"创建 API 路由器失败 {plugin_info.metadata.name}: {e}")
return None
def get_plugin_instance(self, plugin_name: str) -> Optional[Any]:
"""获取插件实例"""
return self._loaded_plugins.get(plugin_name, {}).get("instance")
def get_loaded_plugins(self) -> List[str]:
"""获取已加载的插件列表"""
return list(self._loaded_plugins.keys())
def is_plugin_loaded(self, plugin_name: str) -> bool:
"""检查插件是否已实例化"""
return plugin_name in self._loaded_plugins
@property
def plugin_routers(self) -> Dict[str, APIRouter]:
"""公共访问器:获取已注册的路由器"""
return self._plugin_routers
@property
def loaded_plugins(self) -> Dict[str, Any]:
"""公共访问器:获取已加载的插件"""
return self._loaded_plugins
plugin_loader = PluginLoader()
|