refactor: 推进后端分层架构治理

This commit is contained in:
jxxghp
2026-08-18 00:29:14 +08:00
parent 5d0bacabd5
commit 5128ae9e1e
363 changed files with 34179 additions and 6019 deletions
@@ -0,0 +1 @@
"""插件运行时内部组件。"""
@@ -0,0 +1,41 @@
"""插件运行时钩子契约。"""
from dataclasses import dataclass
from typing import Any
from app.foundation.reflection import ObjectUtils
@dataclass(frozen=True)
class PluginHookContract:
"""描述宿主识别一个插件钩子时必须保持的运行语义。"""
name: str
requires_enabled: bool = False
isolates_errors: bool = True
PLUGIN_HOOK_CONTRACTS = {
contract.name: contract
for contract in (
PluginHookContract("get_command", requires_enabled=True),
PluginHookContract("get_api"),
PluginHookContract("get_service", requires_enabled=True),
PluginHookContract("get_module", requires_enabled=True),
PluginHookContract("get_actions", requires_enabled=True),
PluginHookContract("get_agent_tools", requires_enabled=True),
PluginHookContract("get_auth_providers", requires_enabled=True),
PluginHookContract("get_sidebar_nav", requires_enabled=True),
PluginHookContract("get_dashboard", requires_enabled=True),
PluginHookContract("get_dashboard_meta", requires_enabled=True),
PluginHookContract("get_form"),
PluginHookContract("get_page"),
PluginHookContract("get_render_mode", isolates_errors=False),
)
}
def supports_plugin_hook(plugin: Any, name: str) -> bool:
"""按旧插件的方法判定规则检查实例是否实现指定钩子。"""
method = getattr(plugin, name, None)
return bool(method and ObjectUtils.check_method(method))
+262
View File
@@ -0,0 +1,262 @@
"""插件公开能力投影。"""
from typing import Any, Callable, Dict, List, Mapping, Optional
from app.runtime.extensions.plugin.contracts import supports_plugin_hook
from app.runtime.log import logger as default_logger
class PluginProjection:
"""把运行态插件投影为宿主命令、API、服务、模块和动作清单。"""
def __init__(
self,
running_plugins: Mapping[str, Any],
log: Any = default_logger,
remote_entry_factory: Optional[Callable[[str, str], str]] = None,
) -> None:
"""保存运行态插件映射和错误日志端口。"""
self._running_plugins = running_plugins
self._logger = log
self._remote_entry_factory = remote_entry_factory
def _items(self, pid: Optional[str]) -> list[tuple[str, Any]]:
"""返回指定插件或运行态插件的稳定快照。"""
snapshot = dict(self._running_plugins)
if pid:
plugin = snapshot.get(pid)
return [(pid, plugin)] if plugin is not None else []
return list(snapshot.items())
def commands(self, pid: Optional[str] = None) -> List[Dict[str, Any]]:
"""聚合插件命令并补充插件 ID。"""
commands: list[dict] = []
for plugin_id, plugin in self._items(pid):
if not supports_plugin_hook(plugin, "get_command"):
continue
try:
if not plugin.get_state():
continue
for command in plugin.get_command() or []:
command["pid"] = plugin_id
commands.append(command)
except Exception as error:
self._logger.error(f"获取插件命令出错:{str(error)}")
return commands
def apis(self, pid: Optional[str] = None) -> List[Dict[str, Any]]:
"""聚合插件 API 并补充宿主路径和默认认证方式。"""
apis: list[dict] = []
for plugin_id, plugin in self._items(pid):
if not supports_plugin_hook(plugin, "get_api"):
continue
try:
for api in plugin.get_api() or []:
api["path"] = f"/{plugin_id}{api['path']}"
if not api.get("auth"):
api["auth"] = "apikey"
apis.append(api)
except Exception as error:
self._logger.error(f"获取插件 {plugin_id} API出错:{str(error)}")
return apis
def services(self, pid: Optional[str] = None) -> List[Dict[str, Any]]:
"""聚合启用插件的定时服务。"""
services: list[dict] = []
for plugin_id, plugin in self._items(pid):
if not supports_plugin_hook(plugin, "get_service"):
continue
try:
if plugin.get_state():
services.extend(plugin.get_service() or [])
except Exception as error:
self._logger.error(f"获取插件 {plugin_id} 服务出错:{str(error)}")
return services
def modules(self, pid: Optional[str] = None) -> Dict[tuple, Dict[str, Any]]:
"""聚合启用插件的模块方法清单。"""
modules: dict[tuple, dict] = {}
for plugin_id, plugin in self._items(pid):
if not supports_plugin_hook(plugin, "get_module"):
continue
try:
if plugin.get_state():
modules[(plugin_id, plugin.get_name())] = plugin.get_module() or []
except Exception as error:
self._logger.error(f"获取插件 {plugin_id} 模块出错:{str(error)}")
return modules
def actions(self, pid: Optional[str] = None) -> List[Dict[str, Any]]:
"""聚合启用插件的工作流动作。"""
actions: list[dict] = []
for plugin_id, plugin in self._items(pid):
if not supports_plugin_hook(plugin, "get_actions"):
continue
try:
if not plugin.get_state():
continue
plugin_actions = plugin.get_actions()
if plugin_actions:
actions.append({
"plugin_id": plugin_id,
"plugin_name": plugin.plugin_name,
"actions": plugin_actions,
})
except Exception as error:
self._logger.error(f"获取插件 {plugin_id} 动作出错:{str(error)}")
return actions
def remotes(self, pid: Optional[str] = None) -> List[Dict[str, Any]]:
"""投影插件联邦远程入口,并保持旧渲染模式筛选语义。"""
remotes = []
for plugin_id, plugin in self._items(pid):
if not supports_plugin_hook(plugin, "get_render_mode"):
continue
render_mode, dist_path = plugin.get_render_mode()
if render_mode != "vue":
continue
if not self._remote_entry_factory:
raise RuntimeError("插件联邦入口生成器尚未配置")
remotes.append({
"id": plugin_id,
"url": self._remote_entry_factory(plugin_id, dist_path),
"name": plugin.plugin_name,
})
return remotes
def auth_providers(self) -> List[Dict[str, Any]]:
"""投影启用插件声明的登录认证提供方。"""
providers = []
for plugin_id, plugin in self._items(None):
if not plugin.get_state() or not supports_plugin_hook(
plugin, "get_auth_providers"
):
continue
try:
plugin_providers = plugin.get_auth_providers() or []
except Exception as error:
self._logger.error(
f"获取插件 {plugin_id} 登录认证提供方出错:{str(error)}"
)
continue
render_mode = None
dist_path = None
if supports_plugin_hook(plugin, "get_render_mode"):
render_mode, dist_path = plugin.get_render_mode()
for raw_provider in plugin_providers:
if not raw_provider or not isinstance(raw_provider, dict):
continue
provider = raw_provider.copy()
provider["type"] = "plugin"
provider["plugin_id"] = plugin_id
provider.setdefault("id", f"plugin:{plugin_id}")
provider.setdefault("name", plugin.plugin_name)
provider.setdefault("enabled", True)
if render_mode == "vue" and dist_path:
if not self._remote_entry_factory:
raise RuntimeError("插件联邦入口生成器尚未配置")
provider.setdefault("component", "AuthPage")
provider["remote"] = {
"id": plugin_id,
"url": self._remote_entry_factory(plugin_id, dist_path),
"name": plugin.plugin_name,
}
providers.append(provider)
return providers
def sidebar(self) -> List[Dict[str, Any]]:
"""投影启用 Vue 插件的侧栏导航,并规整权限、分区和顺序。"""
valid_sections = {"start", "discovery", "subscribe", "organize", "system"}
valid_permissions = {"subscribe", "discovery", "search", "manage", "admin"}
items = []
for plugin_id, plugin in self._items(None):
if not plugin.get_state() or not supports_plugin_hook(
plugin, "get_sidebar_nav"
):
continue
if not supports_plugin_hook(plugin, "get_render_mode"):
continue
render_mode, _ = plugin.get_render_mode()
if render_mode != "vue":
continue
try:
nav_list = plugin.get_sidebar_nav()
if not nav_list:
continue
for raw in nav_list:
if not raw or not isinstance(raw, dict):
continue
nav_key = str(
raw.get("nav_key") or raw.get("key") or "main"
).strip()
if not nav_key or any(
character in nav_key for character in ["/", "?", "#", " "]
):
self._logger.warning(
f"插件[{plugin_id}]侧栏项 nav_key 无效,已跳过: "
f"{nav_key!r}"
)
continue
section = str(raw.get("section") or "system").lower()
if section not in valid_sections:
section = "system"
permission = raw.get("permission")
if permission is not None and str(permission) not in valid_permissions:
permission = None
elif permission is not None:
permission = str(permission)
try:
order = int(raw.get("order", 0))
except (TypeError, ValueError):
order = 0
items.append({
"plugin_id": plugin_id,
"nav_key": nav_key,
"title": raw.get("title") or plugin.plugin_name,
"icon": raw.get("icon") or "mdi-puzzle",
"section": section,
"permission": permission,
"order": order,
})
except Exception as error:
self._logger.error(
f"获取插件[{plugin_id}]侧栏导航出错:{str(error)}"
)
items.sort(
key=lambda item: (
item["section"],
item["order"],
item["plugin_id"],
item["nav_key"],
)
)
return items
def dashboard_metadata(self) -> List[Dict[str, str]]:
"""投影启用插件的单仪表板或多仪表板元信息。"""
metadata = []
for plugin_id, plugin in self._items(None):
if not supports_plugin_hook(plugin, "get_dashboard"):
continue
try:
if not plugin.get_state():
continue
if supports_plugin_hook(plugin, "get_dashboard_meta"):
plugin_metadata = plugin.get_dashboard_meta()
if plugin_metadata:
metadata.extend({
"id": plugin_id,
"name": item.get("name"),
"key": item.get("key"),
} for item in plugin_metadata if item)
else:
metadata.append({
"id": plugin_id,
"name": plugin.plugin_name,
"key": "",
})
except Exception as error:
self._logger.error(
f"获取插件[{plugin_id}]仪表盘元数据出错:{str(error)}"
)
return metadata
+56
View File
@@ -0,0 +1,56 @@
"""插件类与运行实例注册表。"""
from typing import Any, Dict, Optional
class PluginRegistry:
"""集中持有插件类和运行实例,并为读取方提供稳定快照。"""
def __init__(self) -> None:
"""创建彼此独立但生命周期一致的类表和实例表。"""
self._classes: Dict[str, Any] = {}
self._running: Dict[str, Any] = {}
@property
def classes(self) -> Dict[str, Any]:
"""返回兼容旧调用方可变访问语义的插件类表。"""
return self._classes
@property
def running(self) -> Dict[str, Any]:
"""返回兼容旧调用方可变访问语义的运行实例表。"""
return self._running
def has_class(self, plugin_id: str) -> bool:
"""判断插件类是否已经登记。"""
return plugin_id in self._classes
def plugin_class(self, plugin_id: str) -> Optional[Any]:
"""读取指定插件类,未登记时返回空。"""
return self._classes.get(plugin_id)
def instance(self, plugin_id: str) -> Optional[Any]:
"""读取指定运行实例,未运行时返回空。"""
return self._running.get(plugin_id)
def plugin_ids(self) -> list[str]:
"""返回保持登记顺序的插件类 ID 快照。"""
return list(self._classes)
def running_ids(self) -> list[str]:
"""返回保持登记顺序的运行实例 ID 快照。"""
return list(self._running)
def running_snapshot(self) -> Dict[str, Any]:
"""复制运行实例表,避免插件重载期间迭代失效。"""
return dict(self._running)
def remove(self, plugin_id: str) -> None:
"""同时移除指定插件类和运行实例。"""
self._classes.pop(plugin_id, None)
self._running.pop(plugin_id, None)
def clear(self) -> None:
"""原地清空注册表,保持外部持有的兼容字典引用有效。"""
self._classes.clear()
self._running.clear()
+89
View File
@@ -0,0 +1,89 @@
"""插件运行时持久化端口。"""
from __future__ import annotations
from collections.abc import Awaitable, Callable
from typing import Any
ConfigReader = Callable[[Any], Any]
ConfigWriter = Callable[[Any, Any], Any]
AsyncConfigWriter = Callable[[Any, Any], Awaitable[Any]]
ConfigDeleter = Callable[[Any], bool]
PluginDataDeleter = Callable[[str], Any]
def _empty_read(_key: Any) -> Any:
"""组合根尚未装配时返回空配置。"""
return None
def _ignore_write(_key: Any, _value: Any) -> None:
"""组合根尚未装配时忽略同步配置写入。"""
async def _ignore_async_write(_key: Any, _value: Any) -> None:
"""组合根尚未装配时忽略异步配置写入。"""
def _ignore_delete(_key: Any) -> bool:
"""组合根尚未装配时报告配置未删除。"""
return False
def _ignore_plugin_data_delete(_plugin_id: str) -> None:
"""组合根尚未装配时忽略插件数据删除。"""
class PluginStorage:
"""封装插件运行时所需的最小持久化能力。"""
def __init__(
self,
*,
read: ConfigReader = _empty_read,
write: ConfigWriter = _ignore_write,
async_write: AsyncConfigWriter = _ignore_async_write,
delete: ConfigDeleter = _ignore_delete,
delete_data: PluginDataDeleter = _ignore_plugin_data_delete,
) -> None:
"""保存由启动组合根提供的读写函数。"""
self._read = read
self._write = write
self._async_write = async_write
self._delete = delete
self._delete_data = delete_data
def read(self, key: Any) -> Any:
"""读取插件运行时配置。"""
return self._read(key)
def write(self, key: Any, value: Any) -> Any:
"""同步保存插件运行时配置。"""
return self._write(key, value)
async def async_write(self, key: Any, value: Any) -> Any:
"""异步保存插件运行时配置。"""
return await self._async_write(key, value)
def delete(self, key: Any) -> bool:
"""删除插件运行时配置。"""
return self._delete(key)
def delete_data(self, plugin_id: str) -> Any:
"""删除指定插件的业务数据。"""
return self._delete_data(plugin_id)
_plugin_storage = PluginStorage()
def configure_plugin_storage(storage: PluginStorage) -> None:
"""由启动组合根替换插件运行时持久化实现。"""
global _plugin_storage
_plugin_storage = storage
def get_plugin_storage() -> PluginStorage:
"""返回当前插件运行时持久化端口。"""
return _plugin_storage
+86
View File
@@ -0,0 +1,86 @@
"""插件市场、包和依赖系统能力的运行时注入端口。"""
from __future__ import annotations
from collections.abc import Callable
from pathlib import Path
from typing import Any, Optional
class PluginSystemServices:
"""保存由启动组合根注入的插件外部系统适配器。"""
def __init__(
self,
*,
market: Any,
package: Any,
dependency: Any,
compatible_flags: Callable[[Optional[str]], list[str]],
frozen: Callable[[], bool],
) -> None:
"""记录市场、包、依赖和代际兼容计算端口。"""
self.market = market
self.package = package
self.dependency = dependency
self.compatible_flags = compatible_flags
self.frozen = frozen
def local_repo_paths(self) -> list[Path]:
"""返回可监测的本地插件仓库路径。"""
return self.market.get_local_repo_paths()
def local_candidate(self, plugin_id: str, **kwargs: Any) -> Optional[dict]:
"""读取指定本地插件候选。"""
return self.market.get_local_candidate(plugin_id, **kwargs)
def local_candidates(self) -> dict[str, dict]:
"""读取全部本地插件候选。"""
return self.market.get_local_candidates()
def local_repo_url(
self,
plugin_id: str,
repo_path: Optional[object] = None,
package_version: Optional[str] = None,
) -> str:
"""构造本地插件来源标识。"""
return self.market.make_local_repo_url(
plugin_id,
repo_path,
package_version,
)
def annotate_system_version(self, plugin_info: dict) -> dict:
"""补充插件条目的主程序版本兼容信息。"""
return self.market.annotate_system_version(plugin_info)
def is_package_compatible(self, plugin_info: dict, package_version: str) -> bool:
"""判断插件条目是否兼容指定代际。"""
return self.market.is_package_compatible(plugin_info, package_version)
def is_frozen(self) -> bool:
"""判断当前宿主是否为不可写的冻结运行模式。"""
return self.frozen()
_services: Optional[PluginSystemServices] = None
def configure_plugin_system(services: PluginSystemServices) -> None:
"""由启动组合根装配插件外部系统能力。"""
global _services
_services = services
def reset_plugin_system() -> None:
"""清除已装配服务,仅供隔离测试恢复进程状态。"""
global _services
_services = None
def get_plugin_system() -> PluginSystemServices:
"""返回已装配的插件外部系统端口。"""
if _services is None:
raise RuntimeError("插件外部系统服务尚未由启动组合根装配")
return _services