feat(plugin-system): 引入插件权限节点声明式注册机制,解决了issue#24

重构了插件权限节点的注册方式,从原先在 `on_plugin_loaded` 钩子中调用 API 的命令式注册,改为通过在插件类中声明 `permission_nodes` 列表的声明式注册。

这一改进有以下优点:
- **简化插件开发**:插件开发者不再需要在代码中手动调用注册函数,只需在类属性中定义权限节点即可,更加直观和简洁。
- **提升核心健壮性**:权限节点的注册逻辑统一由插件管理器在加载时处理,减少了因插件实现不当导致注册失败或遗漏的风险。
- **增强可读性**:所有权限节点集中定义在插件类的顶部,方便快速了解插件所需的权限。

此变更涉及:
- 新增 `PermissionNodeField` 类型用于标准化权限节点定义。
- 在 `PluginBase` 中添加 `permission_nodes` 属性。
- 在 `PluginManager` 中实现插件加载时自动注册权限节点的逻辑。
- 更新 `maizone_refactored` 和 `permission_management` 插件以适应新的声明式注册方式。
This commit is contained in:
minecraft1024a
2025-10-06 13:26:24 +08:00
parent a2b0dd768a
commit b7a255c4ae
6 changed files with 55 additions and 21 deletions

View File

@@ -106,6 +106,13 @@ class PythonDependency:
return self.install_name
@dataclass
class PermissionNodeField:
"""权限节点声明字段"""
node_name: str # 节点名称 (例如 "manage" 或 "view")
description: str # 权限描述
@dataclass
class ComponentInfo:
"""组件信息"""

View File

@@ -10,6 +10,7 @@ import toml
from src.common.logger import get_logger
from src.config.config import CONFIG_DIR
from src.plugin_system.base.component_types import (
PermissionNodeField,
PluginInfo,
PythonDependency,
)
@@ -34,6 +35,8 @@ class PluginBase(ABC):
config_schema: dict[str, dict[str, ConfigField] | str] = {}
permission_nodes: list["PermissionNodeField"] = []
config_section_descriptions: dict[str, str] = {}
def __init__(self, plugin_dir: str, metadata: PluginMetadata):

View File

@@ -6,6 +6,7 @@ from pathlib import Path
from typing import Any, Optional
from src.common.logger import get_logger
from src.plugin_system.apis.permission_api import permission_api
from src.plugin_system.base.component_types import ComponentType
from src.plugin_system.base.plugin_base import PluginBase
from src.plugin_system.base.plugin_metadata import PluginMetadata
@@ -125,6 +126,18 @@ class PluginManager:
self.loaded_plugins[plugin_name] = plugin_instance
self._show_plugin_components(plugin_name)
# 注册权限节点
if hasattr(plugin_instance, "permission_nodes") and plugin_instance.permission_nodes:
for node in plugin_instance.permission_nodes:
asyncio.create_task( # noqa: RUF006
permission_api.register_permission_node(
node_name=node.node_name,
description=node.description,
plugin_name=plugin_name,
)
)
logger.info(f"为插件 '{plugin_name}' 注册了 {len(plugin_instance.permission_nodes)} 个权限节点")
# 检查并调用 on_plugin_loaded 钩子(如果存在)
if hasattr(plugin_instance, "on_plugin_loaded") and callable(plugin_instance.on_plugin_loaded):
logger.debug(f"为插件 '{plugin_name}' 调用 on_plugin_loaded 钩子")
@@ -405,6 +418,14 @@ class PluginManager:
event_handler_names = [c.name for c in event_handler_components]
logger.info(f" 📢 EventHandler组件: {', '.join(event_handler_names)}")
# 权限节点信息
if plugin_instance := self.loaded_plugins.get(plugin_name):
if hasattr(plugin_instance, "permission_nodes") and plugin_instance.permission_nodes:
node_names = [node.node_name for node in plugin_instance.permission_nodes]
logger.info(
f" 🔑 权限节点 ({len(node_names)}个): {', '.join(node_names)}"
)
# 依赖信息
if plugin_info.dependencies:
logger.info(f" 🔗 依赖: {', '.join(plugin_info.dependencies)}")

View File

@@ -7,7 +7,7 @@ from pathlib import Path
from src.common.logger import get_logger
from src.plugin_system import BasePlugin, ComponentInfo, register_plugin
from src.plugin_system.apis.permission_api import permission_api
from src.plugin_system.base.component_types import PermissionNodeField
from src.plugin_system.base.config_types import ConfigField
from .actions.read_feed_action import ReadFeedAction
@@ -83,19 +83,16 @@ class MaiZoneRefactoredPlugin(BasePlugin):
},
}
permission_nodes: list[PermissionNodeField] = [
PermissionNodeField(node_name="send_feed", description="是否可以使用机器人发送QQ空间说说"),
PermissionNodeField(node_name="read_feed", description="是否可以使用机器人读取QQ空间说说"),
]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
async def on_plugin_loaded(self):
"""插件加载完成后的回调,初始化服务并启动后台任务"""
# --- 注册权限节点 ---
await permission_api.register_permission_node(
"plugin.maizone.send_feed", "是否可以使用机器人发送QQ空间说说", "maiZone", False
)
await permission_api.register_permission_node(
"plugin.maizone.read_feed", "是否可以使用机器人读取QQ空间说说", "maiZone", True
)
# --- 创建并注册所有服务实例 ---
content_service = ContentService(self.get_config)
image_service = ImageService(self.get_config)

View File

@@ -137,7 +137,7 @@ class ReplyTrackerService:
try:
if temp_file.exists():
temp_file.unlink()
except:
except Exception:
pass
def _cleanup_old_records(self):

View File

@@ -12,7 +12,11 @@ from src.plugin_system.apis.permission_api import permission_api
from src.plugin_system.apis.plugin_register_api import register_plugin
from src.plugin_system.base.base_plugin import BasePlugin
from src.plugin_system.base.command_args import CommandArgs
from src.plugin_system.base.component_types import ChatType, PlusCommandInfo
from src.plugin_system.base.component_types import (
ChatType,
PermissionNodeField,
PlusCommandInfo,
)
from src.plugin_system.base.config_types import ConfigField
from src.plugin_system.base.plus_command import PlusCommand
from src.plugin_system.utils.permission_decorators import require_permission
@@ -33,14 +37,16 @@ class PermissionCommand(PlusCommand):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
async def on_plugin_loaded(self):
# 注册权限节点(使用显式前缀,避免再次自动补全)
await permission_api.register_permission_node(
"plugin.permission.manage", "权限管理:可以授权和撤销其他用户的权限", "permission_manager", False
)
await permission_api.register_permission_node(
"plugin.permission.view", "权限查看:可以查看权限节点和用户权限信息", "permission_manager", True
)
permission_nodes: list[PermissionNodeField] = [
PermissionNodeField(
node_name="manage",
description="权限管理:可以授权和撤销其他用户的权限",
),
PermissionNodeField(
node_name="view",
description="权限查看:可以查看权限节点和用户权限信息",
),
]
async def execute(self, args: CommandArgs) -> tuple[bool, str | None, bool]:
"""执行权限管理命令"""
@@ -225,7 +231,7 @@ class PermissionCommand(PlusCommand):
target_user_id = chat_stream.user_info.user_id
# 检查是否为Master用户
is_master = await permission_api.is_master(chat_stream.platform, target_user_id)
is_master = permission_api.is_master(chat_stream.platform, target_user_id)
# 获取用户权限
permissions = await permission_api.get_user_permissions(chat_stream.platform, target_user_id)
@@ -258,7 +264,7 @@ class PermissionCommand(PlusCommand):
# 检查权限
has_permission = await permission_api.check_permission(chat_stream.platform, user_id, permission_node)
is_master = await permission_api.is_master(chat_stream.platform, user_id)
is_master = permission_api.is_master(chat_stream.platform, user_id)
if has_permission:
if is_master: