mirror of
https://github.com/insistence/RuoYi-Vue3-FastAPI.git
synced 2026-10-06 01:28:02 +00:00
* feat: 初始化插件系统 * refactor: 收口插件系统运行时重构 * perf: 优化插件系统类型提示 * fix&perf: 修复和优化插件系统 * fix: 修复gitignore规则误忽略插件文件的问题 * fix: 修复运行时插件根路径算错的问题 * fix: 加强插件发现和路由注册的防护措施 * revert: 回滚定时任务白名单 * fix: 移除未使用的应用路由注册探测 * revert: 恢复部分代码 * perf: 优化插件系统 * docs: 新增插件开发文档 * perf: 优化插件管理模块 * perf: 提升插件系统核心能力 * refactor: 重构生命周期 step runner * fix: 修复lint错误 * test: 清理测试用例 * test: 调整测试目录名称 * fix: 修复前后端目录硬编码的问题 * fix: 修复插件系统安全性缺口 * refactor: 重新设计插件生命周期 Migration 事务与回滚 * perf: 优化插件系统边界问题 * refactor: 重构当前插件系统的依赖体系设计 * perf: 优化代码 * perf: 优化代码 * fix: 修复代码合并问题 * fix: 修复bug * perf: 优化代码 * perf&fix: 优化代码和修复bug * docs: 优化文档格式 * feat: 适配Vue2版本 * docs: 更新README文档 * fix: 修复ruff lint错误 * chore: 更新后端依赖文件
228 lines
7.0 KiB
Python
228 lines
7.0 KiB
Python
from dataclasses import dataclass
|
|
|
|
from plugins.core.discovery.scanner import DiscoveredPlugin
|
|
from plugins.core.manifest.schema import PluginMenuManifest
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class PluginMenuConflictItem:
|
|
"""
|
|
插件菜单冲突检查项。
|
|
|
|
:param kind: 冲突类型
|
|
:param plugin_id: 当前插件 ID
|
|
:param value: 冲突值
|
|
:param message: 冲突说明
|
|
:param conflict_plugin_id: 冲突插件 ID
|
|
"""
|
|
|
|
kind: str
|
|
plugin_id: str
|
|
value: str
|
|
message: str
|
|
conflict_plugin_id: str | None = None
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class PluginMenuConflictResult:
|
|
"""
|
|
插件菜单冲突检查结果。
|
|
|
|
:param plugin_id: 插件 ID
|
|
:param items: 冲突检查项列表
|
|
"""
|
|
|
|
plugin_id: str
|
|
items: list[PluginMenuConflictItem]
|
|
|
|
@property
|
|
def ok(self) -> bool:
|
|
"""
|
|
判断菜单冲突检查是否通过。
|
|
|
|
:return: 是否通过
|
|
"""
|
|
return not self.items
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class PluginMenuSnapshot:
|
|
"""
|
|
插件菜单快照。
|
|
|
|
:param plugin_id: 插件 ID
|
|
:param menu: 插件菜单声明
|
|
:param menu_key: 插件内菜单自然键
|
|
"""
|
|
|
|
plugin_id: str
|
|
menu: PluginMenuManifest
|
|
menu_key: str
|
|
|
|
|
|
class PluginMenuConflictChecker:
|
|
"""
|
|
插件菜单冲突检查器。
|
|
|
|
使用 Checker 模式在安装前检查 manifest 层面的菜单自然键和权限冲突。
|
|
"""
|
|
|
|
def check(
|
|
self,
|
|
plugin: DiscoveredPlugin,
|
|
all_plugins: list[DiscoveredPlugin] | None = None,
|
|
) -> PluginMenuConflictResult:
|
|
"""
|
|
检查指定插件菜单冲突。
|
|
|
|
:param plugin: 当前待检查插件
|
|
:param all_plugins: 全量已发现插件列表
|
|
:return: 菜单冲突检查结果
|
|
"""
|
|
all_plugin_list = all_plugins or [plugin]
|
|
snapshots_by_plugin = {
|
|
discovered_plugin.manifest.id: self._build_menu_snapshots(discovered_plugin)
|
|
for discovered_plugin in all_plugin_list
|
|
}
|
|
current_snapshots = snapshots_by_plugin.get(plugin.manifest.id, [])
|
|
items = []
|
|
items.extend(self._check_duplicate_menu_keys(plugin.manifest.id, current_snapshots))
|
|
items.extend(self._check_duplicate_permissions(plugin.manifest.id, current_snapshots, snapshots_by_plugin))
|
|
|
|
return PluginMenuConflictResult(plugin_id=plugin.manifest.id, items=items)
|
|
|
|
def _check_duplicate_menu_keys(
|
|
self,
|
|
plugin_id: str,
|
|
snapshots: list[PluginMenuSnapshot],
|
|
) -> list[PluginMenuConflictItem]:
|
|
"""
|
|
检查同一插件内菜单自然键重复。
|
|
|
|
:param plugin_id: 插件 ID
|
|
:param snapshots: 当前插件菜单快照列表
|
|
:return: 冲突检查项列表
|
|
"""
|
|
seen_keys = set()
|
|
conflicts = []
|
|
for snapshot in snapshots:
|
|
if snapshot.menu_key in seen_keys:
|
|
conflicts.append(
|
|
PluginMenuConflictItem(
|
|
kind='duplicate_menu_key',
|
|
plugin_id=plugin_id,
|
|
value=snapshot.menu_key,
|
|
message=f'插件 {plugin_id} 存在重复菜单自然键:{snapshot.menu_key}',
|
|
)
|
|
)
|
|
seen_keys.add(snapshot.menu_key)
|
|
|
|
return conflicts
|
|
|
|
def _check_duplicate_permissions(
|
|
self,
|
|
plugin_id: str,
|
|
current_snapshots: list[PluginMenuSnapshot],
|
|
snapshots_by_plugin: dict[str, list[PluginMenuSnapshot]],
|
|
) -> list[PluginMenuConflictItem]:
|
|
"""
|
|
检查不同插件之间权限标识重复。
|
|
|
|
:param plugin_id: 当前插件 ID
|
|
:param current_snapshots: 当前插件菜单快照列表
|
|
:param snapshots_by_plugin: 全量插件菜单快照
|
|
:return: 冲突检查项列表
|
|
"""
|
|
permission_owner_map = self._build_permission_owner_map(snapshots_by_plugin)
|
|
conflicts = []
|
|
for snapshot in current_snapshots:
|
|
if not snapshot.menu.perms:
|
|
continue
|
|
conflict_plugin_id = permission_owner_map.get(snapshot.menu.perms)
|
|
if conflict_plugin_id and conflict_plugin_id != plugin_id:
|
|
conflicts.append(
|
|
PluginMenuConflictItem(
|
|
kind='duplicate_permission',
|
|
plugin_id=plugin_id,
|
|
conflict_plugin_id=conflict_plugin_id,
|
|
value=snapshot.menu.perms,
|
|
message=(f'插件 {plugin_id} 权限 {snapshot.menu.perms} 与插件 {conflict_plugin_id} 冲突'),
|
|
)
|
|
)
|
|
|
|
return conflicts
|
|
|
|
def _build_permission_owner_map(
|
|
self,
|
|
snapshots_by_plugin: dict[str, list[PluginMenuSnapshot]],
|
|
) -> dict[str, str]:
|
|
"""
|
|
构建权限标识归属映射。
|
|
|
|
:param snapshots_by_plugin: 全量插件菜单快照
|
|
:return: 权限标识与插件 ID 映射
|
|
"""
|
|
permission_owner_map = {}
|
|
for plugin_id, snapshots in snapshots_by_plugin.items():
|
|
for snapshot in snapshots:
|
|
if snapshot.menu.perms and snapshot.menu.perms not in permission_owner_map:
|
|
permission_owner_map[snapshot.menu.perms] = plugin_id
|
|
|
|
return permission_owner_map
|
|
|
|
def _build_menu_snapshots(self, plugin: DiscoveredPlugin) -> list[PluginMenuSnapshot]:
|
|
"""
|
|
构建插件菜单快照列表。
|
|
|
|
:param plugin: 已发现插件
|
|
:return: 菜单快照列表
|
|
"""
|
|
snapshots = []
|
|
for menu in plugin.manifest.frontend.menus:
|
|
snapshots.extend(
|
|
self._build_menu_tree_snapshots(
|
|
plugin_id=plugin.manifest.id,
|
|
menu=menu,
|
|
parent_key=plugin.manifest.id,
|
|
)
|
|
)
|
|
|
|
return snapshots
|
|
|
|
def _build_menu_tree_snapshots(
|
|
self,
|
|
plugin_id: str,
|
|
menu: PluginMenuManifest,
|
|
parent_key: str,
|
|
) -> list[PluginMenuSnapshot]:
|
|
"""
|
|
递归构建菜单树快照。
|
|
|
|
:param plugin_id: 插件 ID
|
|
:param menu: 插件菜单声明
|
|
:param parent_key: 父菜单自然键
|
|
:return: 菜单快照列表
|
|
"""
|
|
menu_key = self._build_menu_key(menu, parent_key)
|
|
snapshots = [PluginMenuSnapshot(plugin_id=plugin_id, menu=menu, menu_key=menu_key)]
|
|
for child_menu in menu.children:
|
|
snapshots.extend(self._build_menu_tree_snapshots(plugin_id, child_menu, menu_key))
|
|
|
|
return snapshots
|
|
|
|
@staticmethod
|
|
def _build_menu_key(menu: PluginMenuManifest, parent_key: str) -> str:
|
|
"""
|
|
构建插件菜单自然键。
|
|
|
|
:param menu: 插件菜单声明
|
|
:param parent_key: 父级菜单自然键
|
|
:return: 插件菜单自然键
|
|
"""
|
|
if menu.type == 'F':
|
|
return f'button:{parent_key}/{menu.name}#{menu.perms}'
|
|
if menu.perms:
|
|
return f'perm:{menu.perms}'
|
|
|
|
return f'route:{parent_key}/{menu.path}#{menu.component}'
|