新增简易的8080面板😊

This commit is contained in:
Falck
2026-04-17 23:15:15 +08:00
parent c38d2f66d1
commit 9d19d09821
465 changed files with 9235 additions and 35285 deletions

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,279 @@
"""插件加载 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))
self._load_from_dir(Path("./data/pkg"))
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, [])