Title: Implement minimal core framework with PL injection and update build config

Key features implemented:
- Updated package metadata and dependencies in PKG-INFO, setup files
- Added main.py entry point for backward compatibility with README launch method
- Enhanced CLI with config options, system info command, and proper signal handling
- Implemented minimal PluginManager loading only plugin-loader core plugin
- Refactored PluginLoader to follow minimal core design, removed sandbox/isolation complexity
- Updated auto-dependency plugin with safer PL injection mechanism and disabled pl_injection
- Removed legacy plugin files (firewall, frp_proxy, ftp_server, multi_lang_deploy, ops_toolbox, security_gateway) as functionality moved to core plugin system
- Improved gitignore with comprehensive ignore patterns

The changes implement a minimal core framework design where only the plugin-loader is directly loaded by the core, with all other plugins managed through the PL injection mechanism, significantly simplifying the architecture.
This commit is contained in:
qwen.ai[bot]
2026-04-25 10:47:26 +00:00
parent a9bc12596e
commit 97ced1b5e6
181 changed files with 667 additions and 1647 deletions

View File

@@ -0,0 +1,8 @@
{
"signature": "j3U1ZFmpc+pOBC8auYyj84O9DMaAmhOx7F0yGIdrpnclTvteuuXDa7qdBduF+cTu7JStUxN9Yx4oA8dZkorvCZgShQ26jWgLxTAUpa74Pqv6b1q1KQVGcgmiIcF5spIu3zNH4R2tfAWidm7Jncmd2BDDrjVMg16d6Bk73fvMN8GajAaNt3PELIr55LFEER3mOMB9ooeuvUmr7EIoDvZap5bLO4iP88kZaKd6xArNhYi5sCgm4HOxKxUFBOLRAnmJFcOKTqGLL0kYwsoqiN1UPLEawndQKNyX47ZQRfKCut8qQZEPpXl4rYpI6j++Lw7NNrj/jX+IEWFpqMaXiumJAG3tDWKWd5I/7/CAOpttERooJEjG2tVyM2ka9HjIyrc4TrWD9DZTamwkRlrbWm0Q7soTn3O6ZkolQ2n/WUxWKu1o84OHkeeoXDg9AS/uiKsOf7ufTpL7doXUm4bj4xTNkPk63D5PlAoF/kLBgcLHo2UkdxYhv9Y/moig2ogqr//nU5ucIZLmGIIX2Bag8RKgwnhRnKZ+KIGJntIuOoAuoH1H3G/EV42/siqU/AsRSOBtCxhAoqBxaHzZMnyios8kguE/6BfIEs7yS4DzN2ANNcA6tXfbvWGq7oeEB2DBAdamPbyVB76rSsdi0/4zGugvXmBJO4yZuxcuu/HeBH7ES+0=",
"signer": "FutureOSS",
"algorithm": "RSA-SHA256",
"timestamp": 1775964226.5213168,
"plugin_hash": "bed620b64c10798828613a45e3227a7849a9a450e471dfd009135354fb650a1e",
"author": "FutureOSS"
}

View File

@@ -0,0 +1,64 @@
"""熔断器实现"""
import time
from typing import Callable, Any
from .state import CircuitState
class CircuitBreaker:
"""熔断器"""
def __init__(self, failure_threshold: int = 3, recovery_timeout: int = 60, half_open_requests: int = 1):
self.failure_threshold = failure_threshold
self.recovery_timeout = recovery_timeout
self.half_open_requests = half_open_requests
self.state = CircuitState.CLOSED
self.failure_count = 0
self.success_count = 0
self.last_failure_time = 0
self.half_open_calls = 0
def call(self, func: Callable, *args, **kwargs) -> Any:
"""执行调用"""
if self.state == CircuitState.OPEN:
if time.time() - self.last_failure_time >= self.recovery_timeout:
self.state = CircuitState.HALF_OPEN
self.half_open_calls = 0
else:
raise Exception("熔断器已打开,调用被拒绝")
try:
result = func(*args, **kwargs)
self._on_success()
return result
except Exception as e:
self._on_failure()
raise
def _on_success(self):
"""成功回调"""
self.failure_count = 0
if self.state == CircuitState.HALF_OPEN:
self.half_open_calls += 1
if self.half_open_calls >= self.half_open_requests:
self.state = CircuitState.CLOSED
self.half_open_calls = 0
def _on_failure(self):
"""失败回调"""
self.failure_count += 1
self.last_failure_time = time.time()
if self.state == CircuitState.HALF_OPEN:
self.state = CircuitState.OPEN
elif self.failure_count >= self.failure_threshold:
self.state = CircuitState.OPEN
def reset(self):
"""重置熔断器"""
self.state = CircuitState.CLOSED
self.failure_count = 0
self.half_open_calls = 0
def get_state(self) -> str:
return self.state

View File

@@ -0,0 +1,8 @@
"""熔断器状态枚举"""
class CircuitState:
"""熔断器状态"""
CLOSED = "closed" # 正常状态
OPEN = "open" # 熔断状态
HALF_OPEN = "half_open" # 半开状态

View File

@@ -0,0 +1,56 @@
"""Pro 配置模型"""
class CircuitBreakerConfig:
"""熔断器配置"""
def __init__(self, config: dict = None):
config = config or {}
self.failure_threshold = config.get("failure_threshold", 3)
self.recovery_timeout = config.get("recovery_timeout", 60)
self.half_open_requests = config.get("half_open_requests", 1)
class RetryConfig:
"""重试配置"""
def __init__(self, config: dict = None):
config = config or {}
self.max_retries = config.get("max_retries", 3)
self.backoff_factor = config.get("backoff_factor", 2)
self.initial_delay = config.get("initial_delay", 1)
class HealthCheckConfig:
"""健康检查配置"""
def __init__(self, config: dict = None):
config = config or {}
self.interval = config.get("interval", 30)
self.timeout = config.get("timeout", 5)
self.max_failures = config.get("max_failures", 5)
class AutoRecoveryConfig:
"""自动恢复配置"""
def __init__(self, config: dict = None):
config = config or {}
self.enabled = config.get("enabled", True)
self.max_attempts = config.get("max_attempts", 3)
self.delay = config.get("delay", 10)
class IsolationConfig:
"""隔离配置"""
def __init__(self, config: dict = None):
config = config or {}
self.enabled = config.get("enabled", True)
self.timeout_per_plugin = config.get("timeout_per_plugin", 30)
class ProConfig:
"""Pro 总配置"""
def __init__(self, config: dict = None):
config = config or {}
self.circuit_breaker = CircuitBreakerConfig(config.get("circuit_breaker"))
self.retry = RetryConfig(config.get("retry"))
self.health_check = HealthCheckConfig(config.get("health_check"))
self.auto_recovery = AutoRecoveryConfig(config.get("auto_recovery"))
self.isolation = IsolationConfig(config.get("isolation"))

View File

@@ -0,0 +1,209 @@
"""插件加载增强器"""
from ..circuit.breaker import CircuitBreaker
from ..recovery.health import HealthChecker
from ..recovery.auto_fix import AutoRecovery
from ..utils.logger import ProLogger
from .config import ProConfig
class PluginLoaderEnhancer:
"""插件加载增强器 - 为现有 plugin-loader 提供高级机制"""
def __init__(self, plugin_manager, config: ProConfig):
self.pm = plugin_manager
self.config = config
self._breakers = {}
self._health_checker = None
self._auto_recovery = AutoRecovery(
config.auto_recovery.max_attempts,
config.auto_recovery.delay
)
self._enhanced = False
def enhance(self):
"""增强 plugin-loader"""
if self._enhanced:
return
ProLogger.info("enhancer", "开始增强 plugin-loader...")
# 1. 为所有插件创建熔断器
self._setup_circuit_breakers()
# 2. 包装启动方法(带重试和容错)
self._wrap_start_methods()
# 3. 启动健康检查
self._start_health_check()
self._enhanced = True
ProLogger.info("enhancer", "增强完成,共增强 {} 个插件".format(
len(self.pm.plugins)
))
def _setup_circuit_breakers(self):
"""为所有插件创建熔断器"""
for name, info in self.pm.plugins.items():
self._breakers[name] = CircuitBreaker(
self.config.circuit_breaker.failure_threshold,
self.config.circuit_breaker.recovery_timeout,
self.config.circuit_breaker.half_open_requests
)
ProLogger.debug("enhancer", f"{name} 创建熔断器")
def _wrap_start_methods(self):
"""包装启动方法"""
original_start_all = getattr(self.pm, 'start_all', None)
if original_start_all:
def wrapped_start_all():
self._safe_start_all()
self.pm.start_all = wrapped_start_all
ProLogger.info("enhancer", "已包装 start_all 方法")
original_init_and_start = getattr(
self.pm, 'init_and_start_all', None
)
if original_init_and_start:
def wrapped_init_and_start():
self._safe_init_and_start_all()
self.pm.init_and_start_all = wrapped_init_and_start
ProLogger.info("enhancer", "已包装 init_and_start_all 方法")
def _safe_init_and_start_all(self):
"""安全的初始化并启动"""
ordered = self._get_ordered_plugins()
# 安全初始化
for name in ordered:
self._safe_call(name, 'init', '初始化')
# 安全启动
for name in ordered:
self._safe_call(name, 'start', '启动')
def _safe_start_all(self):
"""安全启动所有"""
for name in self.pm.plugins:
self._safe_call(name, 'start', '启动')
def _safe_call(self, name: str, method: str, action: str):
"""安全调用插件方法(带熔断和重试)"""
info = self.pm.plugins.get(name)
if not info:
return
instance = info.get("instance")
if not instance or not hasattr(instance, method):
return
breaker = self._breakers.get(name)
if not breaker:
# 没有熔断器,直接调用
try:
getattr(instance, method)()
except Exception as e:
ProLogger.error("safe", f"{name} {action}失败: {e}")
self._on_plugin_error(name, info, str(e))
return
# 有熔断器,包装调用
def do_call():
return getattr(instance, method)()
try:
breaker.call(do_call)
info["info"].error_count = 0
ProLogger.info("safe", f"{name} {action}成功")
except Exception as e:
ProLogger.error("safe", f"{name} {action}失败: {e}")
self._on_plugin_error(name, info, str(e))
def _on_plugin_error(self, name: str, info: dict, error: str):
"""插件错误处理"""
info["info"].error_count += 1
info["info"].last_error = error
# 自动恢复
if self.config.auto_recovery.enabled:
plugin_dir = info.get("dir")
module = info.get("module")
if plugin_dir:
result = self._auto_recovery.attempt_recovery(
name, plugin_dir, module, info.get("instance")
)
if result:
info["instance"] = result
info["info"].error_count = 0
ProLogger.info("recovery", f"{name} 自动恢复成功")
def _start_health_check(self):
"""启动健康检查"""
self._health_checker = HealthChecker(
self.config.health_check.interval,
self.config.health_check.timeout,
self.config.health_check.max_failures
)
for name, info in self.pm.plugins.items():
self._health_checker.add_plugin(name, info["instance"])
self._health_checker.start(
on_failure_callback=self._on_health_check_failure
)
ProLogger.info("enhancer", "健康检查已启动")
def _on_health_check_failure(self, name: str):
"""健康检查失败回调"""
ProLogger.error("health", f"插件 {name} 健康检查失败")
info = self.pm.plugins.get(name)
if not info:
return
plugin_dir = info.get("dir")
module = info.get("module")
if plugin_dir:
result = self._auto_recovery.attempt_recovery(
name, plugin_dir, module, info.get("instance")
)
if result:
info["instance"] = result
self._health_checker.reset_failure_count(name)
ProLogger.info("recovery", f"{name} 健康恢复成功")
def _get_ordered_plugins(self) -> list[str]:
"""获取按依赖排序的插件列表"""
ordered = []
visited = set()
def visit(name):
if name in visited:
return
visited.add(name)
info = self.pm.plugins.get(name)
if not info:
return
for dep in info["info"].dependencies:
clean_dep = dep.rstrip("}")
if clean_dep in self.pm.plugins:
visit(clean_dep)
ordered.append(name)
for name in self.pm.plugins:
visit(name)
return ordered
def disable(self):
"""禁用增强器"""
if self._health_checker:
self._health_checker.stop()
self._enhanced = False
ProLogger.info("enhancer", "增强器已禁用")

View File

@@ -0,0 +1,278 @@
"""插件加载 Pro - 核心管理器"""
import sys
import json
import importlib.util
from pathlib import Path
from typing import Any, Optional
from oss.plugin.types import Plugin
from .config import ProConfig
from .registry import CapabilityRegistry
from .proxy import PluginProxy, PermissionError
from ..models.plugin_info import PluginInfo
from ..circuit.breaker import CircuitBreaker
from ..retry.handler import RetryHandler
from ..fallback.handler import FallbackHandler
from ..recovery.health import HealthChecker
from ..recovery.auto_fix import AutoRecovery
from ..isolation.timeout import TimeoutController, TimeoutError
from ..utils.logger import ProLogger
from oss.plugin.capabilities import scan_capabilities
class ProPluginManager:
"""Pro 插件管理器"""
def __init__(self, config: ProConfig):
self.config = config
self.plugins: dict[str, dict[str, Any]] = {}
self.capability_registry = CapabilityRegistry()
self._breakers: dict[str, CircuitBreaker] = {}
self._health_checker = HealthChecker(
config.health_check.interval,
config.health_check.timeout,
config.health_check.max_failures
)
self._auto_recovery = AutoRecovery(
config.auto_recovery.max_attempts,
config.auto_recovery.delay
)
def load_all(self, store_dir: str = "store"):
"""加载所有插件"""
ProLogger.info("loader", "开始扫描插件...")
self._load_from_dir(Path(store_dir))
ProLogger.info("loader", f"共加载 {len(self.plugins)} 个插件")
def _load_from_dir(self, store_dir: Path):
"""从目录加载插件"""
if not store_dir.exists():
return
for author_dir in store_dir.iterdir():
if not author_dir.is_dir():
continue
for plugin_dir in author_dir.iterdir():
if not plugin_dir.is_dir():
continue
main_file = plugin_dir / "main.py"
if not main_file.exists():
continue
self._load_single_plugin(plugin_dir)
def _load_single_plugin(self, plugin_dir: Path) -> Optional[Any]:
"""加载单个插件"""
main_file = plugin_dir / "main.py"
manifest_file = plugin_dir / "manifest.json"
try:
manifest = {}
if manifest_file.exists():
with open(manifest_file, "r", encoding="utf-8") as f:
manifest = json.load(f)
spec = importlib.util.spec_from_file_location(
f"pro_plugin.{plugin_dir.name}", str(main_file)
)
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
if not hasattr(module, "New"):
return None
instance = module.New()
plugin_name = plugin_dir.name.rstrip("}")
permissions = manifest.get("permissions", [])
if permissions:
instance = PluginProxy(
plugin_name, instance, permissions, self.plugins
)
info = PluginInfo()
meta = manifest.get("metadata", {})
info.name = meta.get("name", plugin_name)
info.version = meta.get("version", "1.0.0")
info.author = meta.get("author", "")
info.description = meta.get("description", "")
info.dependencies = manifest.get("dependencies", [])
info.capabilities = scan_capabilities(plugin_dir)
for cap in info.capabilities:
self.capability_registry.register_provider(
cap, plugin_name, instance
)
self._breakers[plugin_name] = CircuitBreaker(
self.config.circuit_breaker.failure_threshold,
self.config.circuit_breaker.recovery_timeout,
self.config.circuit_breaker.half_open_requests
)
self.plugins[plugin_name] = {
"instance": instance,
"module": module,
"info": info,
"permissions": permissions,
"dir": plugin_dir
}
ProLogger.info("loader", f"已加载: {plugin_name} v{info.version}")
return instance
except Exception as e:
ProLogger.error("loader", f"加载失败 {plugin_dir.name}: {e}")
return None
def init_and_start_all(self):
"""初始化并启动所有插件"""
ProLogger.info("manager", "开始初始化所有插件...")
self._inject_dependencies()
ordered = self._get_ordered_plugins()
for name in ordered:
self._safe_init(name)
ProLogger.info("manager", "开始启动所有插件...")
for name in ordered:
self._safe_start(name)
self._health_checker.start(
on_failure_callback=self._on_plugin_failure
)
def _safe_init(self, name: str):
"""安全初始化插件"""
info = self.plugins[name]
instance = info["instance"]
breaker = self._breakers[name]
try:
breaker.call(instance.init)
info["info"].status = "initialized"
ProLogger.info("manager", f"已初始化: {name}")
except Exception as e:
ProLogger.error("manager", f"初始化失败 {name}: {e}")
info["info"].status = "error"
info["info"].error_count += 1
info["info"].last_error = str(e)
def _safe_start(self, name: str):
"""安全启动插件"""
info = self.plugins[name]
instance = info["instance"]
breaker = self._breakers[name]
try:
breaker.call(instance.start)
info["info"].status = "running"
self._health_checker.add_plugin(name, instance)
ProLogger.info("manager", f"已启动: {name}")
except Exception as e:
ProLogger.error("manager", f"启动失败 {name}: {e}")
info["info"].status = "error"
info["info"].error_count += 1
info["info"].last_error = str(e)
def stop_all(self):
"""停止所有插件"""
self._health_checker.stop()
for name in reversed(list(self.plugins.keys())):
self._safe_stop(name)
def _safe_stop(self, name: str):
"""安全停止插件"""
info = self.plugins[name]
instance = info["instance"]
try:
instance.stop()
info["info"].status = "stopped"
ProLogger.info("manager", f"已停止: {name}")
except Exception as e:
ProLogger.warn("manager", f"停止异常 {name}: {e}")
def _on_plugin_failure(self, name: str):
"""插件失败回调"""
ProLogger.error("recovery", f"插件 {name} 健康检查失败")
if not self.config.auto_recovery.enabled:
return
info = self.plugins.get(name)
if not info:
return
plugin_dir = info.get("dir")
module = info.get("module")
instance = info.get("instance")
if plugin_dir:
result = self._auto_recovery.attempt_recovery(
name, plugin_dir, module, instance
)
if result:
info["instance"] = result
info["info"].status = "running"
self._health_checker.reset_failure_count(name)
def _inject_dependencies(self):
"""注入依赖"""
name_map = {}
for name in self.plugins:
clean = name.rstrip("}")
name_map[clean] = name
name_map[clean + "}"] = name
for name, info in self.plugins.items():
deps = info["info"].dependencies
if not deps:
continue
for dep_name in deps:
actual_dep = name_map.get(dep_name) or name_map.get(dep_name + "}")
if actual_dep and actual_dep in self.plugins:
dep_instance = self.plugins[actual_dep]["instance"]
setter = f"set_{dep_name.replace('-', '_')}"
if hasattr(info["instance"], setter):
try:
getattr(info["instance"], setter)(dep_instance)
ProLogger.info("inject", f"{name} <- {actual_dep}")
except Exception as e:
ProLogger.error("inject", f"注入失败 {name}.{setter}: {e}")
def _get_ordered_plugins(self) -> list[str]:
"""获取插件顺序"""
ordered = []
visited = set()
def visit(name):
if name in visited:
return
visited.add(name)
info = self.plugins.get(name)
if not info:
return
for dep in info["info"].dependencies:
clean_dep = dep.rstrip("}")
if clean_dep in self.plugins:
visit(clean_dep)
ordered.append(name)
for name in self.plugins:
visit(name)
return ordered

View File

@@ -0,0 +1,36 @@
"""插件代理 - 防越级访问"""
class PermissionError(Exception):
"""权限错误"""
pass
class PluginProxy:
"""插件代理"""
def __init__(self, plugin_name: str, plugin_instance: any,
allowed_plugins: list[str], all_plugins: dict[str, dict]):
self._plugin_name = plugin_name
self._plugin_instance = plugin_instance
self._allowed_plugins = set(allowed_plugins)
self._all_plugins = all_plugins
def get_plugin(self, name: str) -> any:
"""获取其他插件实例(带权限检查)"""
if name not in self._allowed_plugins and "*" not in self._allowed_plugins:
raise PermissionError(
f"插件 '{self._plugin_name}' 无权访问插件 '{name}'"
)
if name not in self._all_plugins:
return None
return self._all_plugins[name]["instance"]
def list_plugins(self) -> list[str]:
"""列出有权限访问的插件"""
if "*" in self._allowed_plugins:
return list(self._all_plugins.keys())
return [n for n in self._allowed_plugins if n in self._all_plugins]
def __getattr__(self, name: str):
return getattr(self._plugin_instance, name)

View File

@@ -0,0 +1,51 @@
"""能力注册表"""
from typing import Any, Optional
from .proxy import PermissionError
class CapabilityRegistry:
"""能力注册表"""
def __init__(self, permission_check: bool = True):
self.providers: dict[str, dict[str, Any]] = {}
self.consumers: dict[str, list[str]] = {}
self.permission_check = permission_check
def register_provider(self, capability: str, plugin_name: str, instance: Any):
"""注册能力提供者"""
self.providers[capability] = {
"plugin": plugin_name,
"instance": instance,
}
if capability not in self.consumers:
self.consumers[capability] = []
def register_consumer(self, capability: str, plugin_name: str):
"""注册能力消费者"""
if capability not in self.consumers:
self.consumers[capability] = []
if plugin_name not in self.consumers[capability]:
self.consumers[capability].append(plugin_name)
def get_provider(self, capability: str, requester: str = "",
allowed_plugins: list[str] = None) -> Optional[Any]:
"""获取能力提供者实例(带权限检查)"""
if capability not in self.providers:
return None
if self.permission_check and allowed_plugins is not None:
provider_name = self.providers[capability]["plugin"]
if (provider_name != requester and
provider_name not in allowed_plugins and
"*" not in allowed_plugins):
raise PermissionError(
f"插件 '{requester}' 无权使用能力 '{capability}'"
)
return self.providers[capability]["instance"]
def has_capability(self, capability: str) -> bool:
return capability in self.providers
def get_consumers(self, capability: str) -> list[str]:
return self.consumers.get(capability, [])

View File

@@ -0,0 +1,49 @@
"""降级处理器"""
from typing import Callable, Any, Optional
from ..utils.logger import ProLogger
class FallbackStrategy:
"""降级策略枚举"""
RETURN_DEFAULT = "return_default"
RETURN_CACHE = "return_cache"
RETURN_NULL = "return_null"
CALL_ALTERNATIVE = "call_alternative"
class FallbackHandler:
"""降级处理器"""
def __init__(self, strategy: str = FallbackStrategy.RETURN_NULL,
default_value: Any = None,
alternative_func: Callable = None):
self.strategy = strategy
self.default_value = default_value
self.alternative_func = alternative_func
self._cache = {}
def execute(self, func: Callable, plugin_name: str, *args, **kwargs) -> Any:
"""执行降级逻辑"""
try:
result = func(*args, **kwargs)
self._cache[plugin_name] = result
return result
except Exception as e:
ProLogger.warn("fallback", f"插件 {plugin_name} 执行失败,触发降级: {e}")
return self._apply_fallback(plugin_name)
def _apply_fallback(self, plugin_name: str) -> Any:
"""应用降级策略"""
if self.strategy == FallbackStrategy.RETURN_DEFAULT:
return self.default_value
elif self.strategy == FallbackStrategy.RETURN_CACHE:
return self._cache.get(plugin_name)
elif self.strategy == FallbackStrategy.RETURN_NULL:
return None
elif self.strategy == FallbackStrategy.CALL_ALTERNATIVE:
if self.alternative_func:
try:
return self.alternative_func()
except Exception as e:
ProLogger.error("fallback", f"备选方案也失败了: {e}")
return None

View File

@@ -0,0 +1,29 @@
"""超时控制"""
import signal
class TimeoutError(Exception):
"""超时错误"""
pass
class TimeoutController:
"""超时控制器"""
def __init__(self, timeout: int = 30):
self.timeout = timeout
def execute_with_timeout(self, func, *args, **kwargs) -> any:
"""在超时限制内执行函数"""
def handler(signum, frame):
raise TimeoutError(f"执行超时 (>{self.timeout}s)")
old_handler = signal.signal(signal.SIGALRM, handler)
signal.alarm(self.timeout)
try:
result = func(*args, **kwargs)
signal.alarm(0)
return result
finally:
signal.signal(signal.SIGALRM, old_handler)

View File

@@ -0,0 +1,76 @@
"""插件加载 Pro - 为 plugin-loader 提供高级机制"""
from oss.plugin.types import Plugin, register_plugin_type
from .core.config import ProConfig
from .core.enhancer import PluginLoaderEnhancer
from .utils.logger import ProLogger
class PluginLoaderPro(Plugin):
"""插件加载 Pro - 增强器"""
def __init__(self):
self.plugin_loader = None
self.enhancer = None
self.config = None
self._started = False
def meta(self):
from oss.plugin.types import Metadata, PluginConfig, Manifest
return Manifest(
metadata=Metadata(
name="plugin-loader-pro",
version="1.0.0",
author="FutureOSS",
description="为 plugin-loader 提供熔断、降级、容错、自动修复等高级机制"
),
config=PluginConfig(
enabled=True,
args={}
),
dependencies=["plugin-loader"]
)
def set_plugin_loader(self, plugin_loader):
self.plugin_loader = plugin_loader
ProLogger.info("main", "已注入 plugin-loader")
def init(self, deps: dict = None):
if not self.plugin_loader:
ProLogger.warn("main", "未找到 plugin-loader 依赖")
return
config = {}
if deps:
config = deps.get("config", {})
self.config = ProConfig(config)
self.enhancer = PluginLoaderEnhancer(
self.plugin_loader.manager,
self.config
)
ProLogger.info("main", "增强器已初始化")
def start(self):
if self._started:
return
self._started = True
if not self.enhancer:
ProLogger.warn("main", "增强器未初始化,跳过启动")
return
ProLogger.info("main", "开始增强 plugin-loader...")
self.enhancer.enhance()
def stop(self):
ProLogger.info("main", "停止增强器...")
if self.enhancer:
self.enhancer.disable()
register_plugin_type("PluginLoaderPro", PluginLoaderPro)
def New():
return PluginLoaderPro()

View File

@@ -0,0 +1,40 @@
{
"metadata": {
"name": "plugin-loader-pro",
"version": "1.0.0",
"author": "FutureOSS",
"description": "插件加载 Pro - 为 plugin-loader 提供熔断、降级、容错、自动修复等高级机制",
"type": "enhancer"
},
"config": {
"enabled": true,
"args": {
"circuit_breaker": {
"failure_threshold": 3,
"recovery_timeout": 60,
"half_open_requests": 1
},
"retry": {
"max_retries": 3,
"backoff_factor": 2,
"initial_delay": 1
},
"health_check": {
"interval": 30,
"timeout": 5,
"max_failures": 5
},
"auto_recovery": {
"enabled": true,
"max_attempts": 3,
"delay": 10
},
"isolation": {
"enabled": true,
"timeout_per_plugin": 30
}
}
},
"dependencies": ["plugin-loader"],
"permissions": ["*"]
}

View File

@@ -0,0 +1,30 @@
"""插件信息模型"""
from typing import Any
class PluginInfo:
"""插件信息"""
def __init__(self):
self.name: str = ""
self.version: str = ""
self.author: str = ""
self.description: str = ""
self.readme: str = ""
self.config: dict[str, Any] = {}
self.extensions: dict[str, Any] = {}
self.lifecycle: Any = None
self.capabilities: set[str] = set()
self.dependencies: list[str] = []
self.status: str = "idle" # idle, running, stopped, error
self.error_count: int = 0
self.last_error: str = ""
def to_dict(self) -> dict:
return {
"name": self.name,
"version": self.version,
"author": self.author,
"description": self.description,
"status": self.status,
"error_count": self.error_count
}

View File

@@ -0,0 +1,60 @@
"""自动修复器"""
import time
import importlib
import sys
from pathlib import Path
from ..utils.logger import ProLogger
class AutoRecovery:
"""自动修复器"""
def __init__(self, max_attempts: int = 3, delay: int = 10):
self.max_attempts = max_attempts
self.delay = delay
self._recovery_attempts: dict[str, int] = {}
def attempt_recovery(self, name: str, plugin_dir: Path,
module: any, instance: any) -> bool:
"""尝试恢复插件"""
attempts = self._recovery_attempts.get(name, 0)
if attempts >= self.max_attempts:
ProLogger.error("recovery", f"插件 {name} 已达到最大恢复次数,放弃恢复")
return False
ProLogger.warn("recovery", f"尝试恢复插件 {name} (第 {attempts + 1} 次)")
try:
time.sleep(self.delay)
# 重新加载模块
if module and hasattr(module, '__file__'):
module_path = Path(module.__file__)
if module_path.exists():
spec = importlib.util.spec_from_file_location(
module.__name__, str(module_path)
)
new_module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = new_module
spec.loader.exec_module(new_module)
if hasattr(new_module, 'New'):
new_instance = new_module.New()
ProLogger.info("recovery", f"插件 {name} 恢复成功")
self._recovery_attempts[name] = 0
return new_instance
except Exception as e:
ProLogger.error("recovery", f"恢复插件 {name} 失败: {e}")
self._recovery_attempts[name] = attempts + 1
return False
def reset_attempts(self, name: str):
"""重置恢复尝试次数"""
self._recovery_attempts[name] = 0
def get_attempts(self, name: str) -> int:
"""获取恢复尝试次数"""
return self._recovery_attempts.get(name, 0)

View File

@@ -0,0 +1,76 @@
"""健康检查器"""
import time
import threading
from typing import Any
from ..utils.logger import ProLogger
class HealthChecker:
"""健康检查器"""
def __init__(self, interval: int = 30, timeout: int = 5, max_failures: int = 5):
self.interval = interval
self.timeout = timeout
self.max_failures = max_failures
self._running = False
self._thread = None
self._plugins: dict[str, Any] = {}
self._failure_counts: dict[str, int] = {}
self._on_failure_callback = None
def add_plugin(self, name: str, instance: Any):
"""添加要监控的插件"""
self._plugins[name] = instance
self._failure_counts[name] = 0
def start(self, on_failure_callback=None):
"""启动健康检查"""
self._on_failure_callback = on_failure_callback
self._running = True
self._thread = threading.Thread(target=self._check_loop, daemon=True)
self._thread.start()
ProLogger.info("health", "健康检查已启动")
def stop(self):
"""停止健康检查"""
self._running = False
if self._thread:
self._thread.join(timeout=5)
def _check_loop(self):
"""检查循环"""
while self._running:
for name, instance in self._plugins.items():
self._check_plugin(name, instance)
time.sleep(self.interval)
def _check_plugin(self, name: str, instance: Any):
"""检查单个插件"""
try:
if hasattr(instance, 'health'):
healthy = instance.health()
if not healthy:
self._on_failure(name)
else:
self._failure_counts[name] = 0
except Exception as e:
ProLogger.error("health", f"插件 {name} 健康检查失败: {e}")
self._on_failure(name)
def _on_failure(self, name: str):
"""失败处理"""
self._failure_counts[name] = self._failure_counts.get(name, 0) + 1
if self._failure_counts[name] >= self.max_failures:
ProLogger.warn("health", f"插件 {name} 连续失败 {self._failure_counts[name]}")
if self._on_failure_callback:
self._on_failure_callback(name)
def reset_failure_count(self, name: str):
"""重置失败计数"""
self._failure_counts[name] = 0
def get_failure_count(self, name: str) -> int:
"""获取失败计数"""
return self._failure_counts.get(name, 0)

View File

@@ -0,0 +1,39 @@
"""重试处理器"""
import time
import random
from typing import Callable, Any
from ..core.config import RetryConfig
from ..utils.logger import ProLogger
class RetryHandler:
"""重试处理器"""
def __init__(self, config: RetryConfig = None):
config = config or RetryConfig()
self.max_retries = config.max_retries
self.backoff_factor = config.backoff_factor
self.initial_delay = config.initial_delay
def execute(self, func: Callable, *args, **kwargs) -> Any:
"""执行带重试的调用"""
last_exception = None
for attempt in range(self.max_retries + 1):
try:
return func(*args, **kwargs)
except Exception as e:
last_exception = e
if attempt < self.max_retries:
delay = self._calculate_delay(attempt)
ProLogger.warn("retry", f"{attempt + 1} 次重试,等待 {delay:.1f}s: {e}")
time.sleep(delay)
raise last_exception
def _calculate_delay(self, attempt: int) -> float:
"""计算退避延迟"""
delay = self.initial_delay * (self.backoff_factor ** attempt)
jitter = random.uniform(0, delay * 0.1)
return delay + jitter

View File

@@ -0,0 +1,60 @@
"""插件加载 Pro - 日志工具"""
import sys
class ProLogger:
"""Pro 日志记录器 - 智能颜色识别"""
_COLORS = {
"reset": "\033[0m",
"white": "\033[0;37m",
"yellow": "\033[1;33m",
"blue": "\033[1;34m",
"red": "\033[1;31m",
}
@staticmethod
def _colorize(text: str, color: str) -> str:
"""添加颜色(终端支持时)"""
if not sys.stdout.isatty():
return text
return f"{ProLogger._COLORS.get(color, '')}{text}{ProLogger._COLORS['reset']}"
@staticmethod
def info(component: str, message: str):
"""正常日志 - 白色"""
tag = ProLogger._colorize(f"[pro:{component}]", "white")
msg = ProLogger._colorize(message, "white")
print(f"{tag} {msg}")
@staticmethod
def warn(component: str, message: str):
"""警告日志 - 黄色"""
tag = ProLogger._colorize(f"[pro:{component}]", "yellow")
icon = ProLogger._colorize("", "yellow")
msg = ProLogger._colorize(message, "yellow")
print(f"{tag} {icon} {msg}")
@staticmethod
def error(component: str, message: str):
"""错误日志 - 红色"""
tag = ProLogger._colorize(f"[pro:{component}]", "red")
icon = ProLogger._colorize("", "red")
msg = ProLogger._colorize(message, "red")
print(f"{tag} {icon} {msg}")
@staticmethod
def debug(component: str, message: str):
"""调试日志 - 蓝色"""
tag = ProLogger._colorize(f"[pro:{component}]", "blue")
icon = ProLogger._colorize("", "blue")
msg = ProLogger._colorize(message, "blue")
print(f"{tag} {icon} {msg}")
@staticmethod
def tip(component: str, message: str):
"""提示日志 - 蓝色(用于小提示/额外信息)"""
tag = ProLogger._colorize(f"[pro:{component}]", "blue")
icon = ProLogger._colorize("", "blue")
msg = ProLogger._colorize(message, "blue")
print(f"{tag} {icon} {msg}")