From 5fcedd15318ee14786e1ca5916847cc080e1ed2b Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Mon, 16 Jun 2025 00:04:36 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E6=B7=BB=E5=8A=A0=E6=9B=B4?= =?UTF-8?q?=E6=96=B9=E4=BE=BF=E7=9A=84get=5Fconfig=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/plugins/config-access-guide.md | 249 +++++++++ .../plugins/examples/config-access-example.md | 520 ++++++++++++++++++ src/plugin_system/apis/plugin_api.py | 56 ++ 3 files changed, 825 insertions(+) create mode 100644 docs/plugins/config-access-guide.md create mode 100644 docs/plugins/examples/config-access-example.md diff --git a/docs/plugins/config-access-guide.md b/docs/plugins/config-access-guide.md new file mode 100644 index 000000000..1a619316d --- /dev/null +++ b/docs/plugins/config-access-guide.md @@ -0,0 +1,249 @@ +# 🔧 插件配置访问指南 + +## 问题描述 + +在插件开发中,你可能遇到这样的问题: +- `get_config`方法只在`BasePlugin`类中 +- `BaseAction`和`BaseCommand`无法直接继承这个方法 +- 想要在Action或Command中访问插件配置 + +## ✅ 解决方案 + +**直接使用 `self.api.get_config()` 方法!** + +系统已经自动为你处理了配置传递,你只需要通过`PluginAPI`访问配置即可。 + +## 📖 快速示例 + +### 在Action中访问配置 + +```python +from src.plugin_system import BaseAction + +class MyAction(BaseAction): + async def execute(self): + # 方法1: 获取配置值(带默认值) + api_key = self.api.get_config("api.key", "default_key") + timeout = self.api.get_config("api.timeout", 30) + + # 方法2: 检查配置是否存在 + if self.api.has_config("features.premium"): + premium_enabled = self.api.get_config("features.premium") + # 使用高级功能 + + # 方法3: 支持嵌套键访问 + log_level = self.api.get_config("advanced.logging.level", "INFO") + + # 方法4: 获取所有配置 + all_config = self.api.get_all_config() + + await self.send_text(f"API密钥: {api_key}") + return True, "配置访问成功" +``` + +### 在Command中访问配置 + +```python +from src.plugin_system import BaseCommand + +class MyCommand(BaseCommand): + async def execute(self): + # 使用方式与Action完全相同 + welcome_msg = self.api.get_config("messages.welcome", "欢迎!") + max_results = self.api.get_config("search.max_results", 10) + + # 根据配置执行不同逻辑 + if self.api.get_config("features.debug_mode", False): + await self.send_text(f"调试模式已启用,最大结果数: {max_results}") + + await self.send_text(welcome_msg) + return True, "命令执行完成" +``` + +## 🔧 API方法详解 + +### 1. `get_config(key, default=None)` + +获取配置值,支持嵌套键访问: + +```python +# 简单键 +value = self.api.get_config("timeout", 30) + +# 嵌套键(用点号分隔) +value = self.api.get_config("database.connection.host", "localhost") +value = self.api.get_config("features.ai.model", "gpt-3.5-turbo") +``` + +### 2. `has_config(key)` + +检查配置项是否存在: + +```python +if self.api.has_config("api.secret_key"): + # 配置存在,可以安全使用 + secret = self.api.get_config("api.secret_key") +else: + # 配置不存在,使用默认行为 + pass +``` + +### 3. `get_all_config()` + +获取所有配置的副本: + +```python +all_config = self.api.get_all_config() +for section, config in all_config.items(): + print(f"配置节: {section}, 包含 {len(config)} 项配置") +``` + +## 📁 配置文件示例 + +假设你的插件有这样的配置文件 `config.toml`: + +```toml +[api] +key = "your_api_key" +timeout = 30 +base_url = "https://api.example.com" + +[features] +enable_cache = true +debug_mode = false +max_retries = 3 + +[messages] +welcome = "欢迎使用我的插件!" +error = "出现了错误,请稍后重试" + +[advanced] +[advanced.logging] +level = "INFO" +file_path = "logs/plugin.log" + +[advanced.cache] +ttl_seconds = 3600 +max_size = 100 +``` + +## 🎯 实际使用案例 + +### 案例1:API调用配置 + +```python +class ApiAction(BaseAction): + async def execute(self): + # 获取API配置 + api_key = self.api.get_config("api.key") + if not api_key: + await self.send_text("❌ API密钥未配置") + return False, "缺少API密钥" + + timeout = self.api.get_config("api.timeout", 30) + base_url = self.api.get_config("api.base_url", "https://api.example.com") + + # 使用配置进行API调用 + # ... API调用逻辑 + + return True, "API调用完成" +``` + +### 案例2:功能开关配置 + +```python +class FeatureCommand(BaseCommand): + async def execute(self): + # 检查功能开关 + if not self.api.get_config("features.enable_cache", True): + await self.send_text("缓存功能已禁用") + return True, "功能被禁用" + + # 检查调试模式 + debug_mode = self.api.get_config("features.debug_mode", False) + if debug_mode: + await self.send_text("🐛 调试模式已启用") + + max_retries = self.api.get_config("features.max_retries", 3) + # 使用重试配置 + + return True, "功能执行完成" +``` + +### 案例3:个性化消息配置 + +```python +class WelcomeAction(BaseAction): + async def execute(self): + # 获取个性化消息 + welcome_msg = self.api.get_config("messages.welcome", "欢迎!") + + # 检查是否有自定义问候语列表 + if self.api.has_config("messages.custom_greetings"): + greetings = self.api.get_config("messages.custom_greetings", []) + if greetings: + import random + welcome_msg = random.choice(greetings) + + await self.send_text(welcome_msg) + return True, "发送了个性化问候" +``` + +## 🔄 配置传递机制 + +系统自动处理配置传递,无需手动操作: + +1. **插件初始化** → `BasePlugin`加载`config.toml`到`self.config` +2. **组件注册** → 系统记录插件配置 +3. **组件实例化** → 自动传递`plugin_config`参数给Action/Command +4. **API初始化** → 配置保存到`PluginAPI`实例中 +5. **组件使用** → 通过`self.api.get_config()`访问 + +## ⚠️ 注意事项 + +### 1. 总是提供默认值 + +```python +# ✅ 好的做法 +timeout = self.api.get_config("api.timeout", 30) + +# ❌ 避免这样做 +timeout = self.api.get_config("api.timeout") # 可能返回None +``` + +### 2. 验证配置类型 + +```python +# 获取配置后验证类型 +max_items = self.api.get_config("list.max_items", 10) +if not isinstance(max_items, int) or max_items <= 0: + max_items = 10 # 使用安全的默认值 +``` + +### 3. 缓存复杂配置解析 + +```python +class MyAction(BaseAction): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # 在初始化时解析复杂配置,避免重复解析 + self._api_config = self._parse_api_config() + + def _parse_api_config(self): + return { + 'key': self.api.get_config("api.key", ""), + 'timeout': self.api.get_config("api.timeout", 30), + 'retries': self.api.get_config("api.max_retries", 3) + } +``` + +## 🎉 总结 + +现在你知道了!在Action和Command中访问配置很简单: + +```python +# 这就是你需要的全部代码! +config_value = self.api.get_config("your.config.key", "default_value") +``` + +不需要继承`BasePlugin`,不需要复杂的配置传递,`PluginAPI`已经为你准备好了一切! \ No newline at end of file diff --git a/docs/plugins/examples/config-access-example.md b/docs/plugins/examples/config-access-example.md new file mode 100644 index 000000000..f79adeb38 --- /dev/null +++ b/docs/plugins/examples/config-access-example.md @@ -0,0 +1,520 @@ +# 📖 插件配置访问完整示例 + +> 这个示例展示了如何在Action和Command组件中正确访问插件的配置文件。 + +## 🎯 问题背景 + +在插件开发过程中,你可能遇到这样的问题: +- `get_config`方法只在`BasePlugin`类中 +- `BaseAction`和`BaseCommand`无法直接继承这个方法 +- 想要在Action或Command中访问插件配置 + +## ✅ 解决方案 + +通过`self.api.get_config()`方法访问配置,系统会自动将插件配置传递给组件。 + +## 📁 完整示例 + +### 1. 插件配置文件 + +创建 `config.toml`: + +```toml +[greeting] +default_style = "casual" +enable_emoji = true +custom_messages = [ + "你好呀!", + "嗨!很高兴见到你!", + "哈喽!" +] + +[database] +enabled = true +table_prefix = "hello_" +max_records = 1000 + +[features] +enable_weather = false +enable_jokes = true +api_timeout = 30 + +[advanced.logging] +level = "INFO" +file_path = "logs/hello_plugin.log" + +[advanced.cache] +enabled = true +ttl_seconds = 3600 +max_size = 100 +``` + +### 2. 插件主文件 + +创建 `plugin.py`: + +```python +""" +配置访问示例插件 +展示如何在Action和Command中访问配置 +""" + +from src.plugin_system import ( + BasePlugin, + BaseAction, + BaseCommand, + register_plugin, + ActionInfo, + CommandInfo, + PythonDependency, + ActionActivationType +) +from src.common.logger import get_logger + +logger = get_logger("config_example_plugin") + + +@register_plugin +class ConfigExamplePlugin(BasePlugin): + """配置访问示例插件""" + + plugin_name = "config_example_plugin" + plugin_description = "展示如何在组件中访问配置的示例插件" + plugin_version = "1.0.0" + plugin_author = "MaiBot Team" + config_file_name = "config.toml" + + def get_plugin_components(self): + """返回插件组件""" + return [ + (ActionInfo( + name="config_greeting_action", + description="使用配置的问候Action", + focus_activation_type=ActionActivationType.KEYWORD, + normal_activation_type=ActionActivationType.KEYWORD, + activation_keywords=["配置问候", "config hello"], + ), ConfigGreetingAction), + + (CommandInfo( + name="config_status", + description="显示配置状态", + command_pattern=r"^/config\s*(status|show)?$", + command_help="显示插件配置状态", + command_examples=["/config", "/config status"], + ), ConfigStatusCommand), + + (CommandInfo( + name="config_test", + description="测试配置访问", + command_pattern=r"^/config\s+test\s+(?P\S+)$", + command_help="测试访问指定配置项", + command_examples=["/config test greeting.default_style"], + ), ConfigTestCommand), + ] + + +class ConfigGreetingAction(BaseAction): + """使用配置的问候Action""" + + async def execute(self): + """执行配置化的问候""" + try: + # 方法1: 直接访问配置项 + style = self.api.get_config("greeting.default_style", "casual") + enable_emoji = self.api.get_config("greeting.enable_emoji", True) + + # 方法2: 检查配置是否存在 + if self.api.has_config("greeting.custom_messages"): + messages = self.api.get_config("greeting.custom_messages", []) + if messages: + # 随机选择一个问候语 + import random + message = random.choice(messages) + else: + message = "你好!" + else: + # 使用默认问候语 + if style == "formal": + message = "您好!很高兴为您服务!" + else: + message = "嗨!很开心见到你!" + + # 添加表情符号 + if enable_emoji: + emoji = "😊" if style == "casual" else "🙏" + message += emoji + + # 发送问候消息 + await self.send_text(message) + + # 记录到数据库(如果启用) + await self._save_greeting_record(style, message) + + return True, f"发送了{style}风格的配置化问候" + + except Exception as e: + logger.error(f"配置问候执行失败: {e}") + await self.send_text("抱歉,问候功能遇到了问题") + return False, f"执行失败: {str(e)}" + + async def _save_greeting_record(self, style: str, message: str): + """保存问候记录到数据库""" + try: + # 检查数据库功能是否启用 + if not self.api.get_config("database.enabled", False): + return + + # 获取数据库配置 + table_prefix = self.api.get_config("database.table_prefix", "hello_") + max_records = self.api.get_config("database.max_records", 1000) + + # 构造记录数据 + record_data = { + "style": style, + "message": message, + "timestamp": "now", # 实际应用中使用datetime + "user_id": "demo_user" # 从context获取真实用户ID + } + + # 这里应该调用数据库API保存记录 + logger.info(f"保存问候记录到 {table_prefix}greetings: {record_data}") + + except Exception as e: + logger.error(f"保存问候记录失败: {e}") + + +class ConfigStatusCommand(BaseCommand): + """显示配置状态Command""" + + async def execute(self): + """显示插件配置状态""" + try: + # 获取所有配置 + all_config = self.api.get_all_config() + + if not all_config: + await self.send_text("❌ 没有找到配置文件") + return True, "没有配置文件" + + # 构建状态报告 + status_lines = ["📋 插件配置状态:", ""] + + # 问候配置 + greeting_config = all_config.get("greeting", {}) + if greeting_config: + status_lines.append("🎯 问候配置:") + status_lines.append(f" - 默认风格: {greeting_config.get('default_style', 'N/A')}") + status_lines.append(f" - 启用表情: {'✅' if greeting_config.get('enable_emoji') else '❌'}") + custom_msgs = greeting_config.get('custom_messages', []) + status_lines.append(f" - 自定义消息: {len(custom_msgs)}条") + status_lines.append("") + + # 数据库配置 + db_config = all_config.get("database", {}) + if db_config: + status_lines.append("🗄️ 数据库配置:") + status_lines.append(f" - 状态: {'✅ 启用' if db_config.get('enabled') else '❌ 禁用'}") + status_lines.append(f" - 表前缀: {db_config.get('table_prefix', 'N/A')}") + status_lines.append(f" - 最大记录: {db_config.get('max_records', 'N/A')}") + status_lines.append("") + + # 功能配置 + features_config = all_config.get("features", {}) + if features_config: + status_lines.append("🔧 功能配置:") + for feature, enabled in features_config.items(): + if isinstance(enabled, bool): + status_lines.append(f" - {feature}: {'✅' if enabled else '❌'}") + else: + status_lines.append(f" - {feature}: {enabled}") + status_lines.append("") + + # 高级配置 + advanced_config = all_config.get("advanced", {}) + if advanced_config: + status_lines.append("⚙️ 高级配置:") + for section, config in advanced_config.items(): + status_lines.append(f" - {section}: {len(config) if isinstance(config, dict) else 1}项") + + # 发送状态报告 + status_text = "\n".join(status_lines) + await self.send_text(status_text) + + return True, "显示了配置状态" + + except Exception as e: + logger.error(f"获取配置状态失败: {e}") + await self.send_text(f"❌ 获取配置状态失败: {str(e)}") + return False, f"获取失败: {str(e)}" + + +class ConfigTestCommand(BaseCommand): + """测试配置访问Command""" + + async def execute(self): + """测试访问指定的配置项""" + try: + # 获取要测试的配置键 + config_key = self.matched_groups.get("key", "") + + if not config_key: + await self.send_text("❌ 请指定要测试的配置项\n用法: /config test ") + return True, "缺少配置键参数" + + # 测试配置访问的不同方法 + result_lines = [f"🔍 测试配置项: `{config_key}`", ""] + + # 方法1: 检查是否存在 + exists = self.api.has_config(config_key) + result_lines.append(f"📋 配置存在: {'✅ 是' if exists else '❌ 否'}") + + if exists: + # 方法2: 获取配置值 + config_value = self.api.get_config(config_key) + value_type = type(config_value).__name__ + + result_lines.append(f"📊 数据类型: {value_type}") + + # 根据类型显示值 + if isinstance(config_value, (str, int, float, bool)): + result_lines.append(f"💾 配置值: {config_value}") + elif isinstance(config_value, list): + result_lines.append(f"📝 列表长度: {len(config_value)}") + if config_value: + result_lines.append(f"📋 首项: {config_value[0]}") + elif isinstance(config_value, dict): + result_lines.append(f"🗂️ 字典大小: {len(config_value)}项") + if config_value: + keys = list(config_value.keys())[:3] + result_lines.append(f"🔑 键示例: {', '.join(keys)}") + else: + result_lines.append(f"💾 配置值: {str(config_value)[:100]}...") + + # 方法3: 测试默认值 + test_default = self.api.get_config(config_key, "DEFAULT_VALUE") + if test_default != "DEFAULT_VALUE": + result_lines.append("✅ 默认值机制正常") + else: + result_lines.append("⚠️ 配置值为空或等于测试默认值") + else: + # 测试默认值返回 + default_value = self.api.get_config(config_key, "NOT_FOUND") + result_lines.append(f"🔄 默认值返回: {default_value}") + + # 显示相关配置项 + if "." in config_key: + section = config_key.split(".")[0] + all_config = self.api.get_all_config() + section_config = all_config.get(section, {}) + if section_config and isinstance(section_config, dict): + related_keys = list(section_config.keys())[:5] + result_lines.append("") + result_lines.append(f"🔗 相关配置项 ({section}):") + for key in related_keys: + full_key = f"{section}.{key}" + status = "✅" if self.api.has_config(full_key) else "❌" + result_lines.append(f" {status} {full_key}") + + # 发送测试结果 + result_text = "\n".join(result_lines) + await self.send_text(result_text) + + return True, f"测试了配置项: {config_key}" + + except Exception as e: + logger.error(f"配置测试失败: {e}") + await self.send_text(f"❌ 配置测试失败: {str(e)}") + return False, f"测试失败: {str(e)}" + + +# 演示代码 +async def demo_config_access(): + """演示配置访问功能""" + + print("🔧 插件配置访问演示") + print("=" * 50) + + # 模拟插件配置 + mock_config = { + "greeting": { + "default_style": "casual", + "enable_emoji": True, + "custom_messages": ["你好呀!", "嗨!很高兴见到你!"] + }, + "database": { + "enabled": True, + "table_prefix": "hello_", + "max_records": 1000 + }, + "advanced": { + "logging": { + "level": "INFO", + "file_path": "logs/hello_plugin.log" + } + } + } + + # 创建模拟API + from src.plugin_system.apis.plugin_api import PluginAPI + api = PluginAPI(plugin_config=mock_config) + + print("\n📋 配置访问测试:") + + # 测试1: 基本配置访问 + style = api.get_config("greeting.default_style", "unknown") + print(f" 问候风格: {style}") + + # 测试2: 布尔值配置 + enable_emoji = api.get_config("greeting.enable_emoji", False) + print(f" 启用表情: {enable_emoji}") + + # 测试3: 列表配置 + messages = api.get_config("greeting.custom_messages", []) + print(f" 自定义消息: {len(messages)}条") + + # 测试4: 深层嵌套配置 + log_level = api.get_config("advanced.logging.level", "INFO") + print(f" 日志级别: {log_level}") + + # 测试5: 不存在的配置 + unknown = api.get_config("unknown.config", "default") + print(f" 未知配置: {unknown}") + + # 测试6: 配置存在检查 + exists1 = api.has_config("greeting.default_style") + exists2 = api.has_config("nonexistent.config") + print(f" greeting.default_style 存在: {exists1}") + print(f" nonexistent.config 存在: {exists2}") + + # 测试7: 获取所有配置 + all_config = api.get_all_config() + print(f" 总配置节数: {len(all_config)}") + + print("\n✅ 配置访问测试完成!") + + +if __name__ == "__main__": + import asyncio + asyncio.run(demo_config_access()) +``` + +## 🎯 核心要点 + +### 1. 在Action中访问配置 + +```python +class MyAction(BaseAction): + async def execute(self): + # 基本配置访问 + value = self.api.get_config("section.key", "default") + + # 检查配置是否存在 + if self.api.has_config("section.key"): + # 配置存在,执行相应逻辑 + pass + + # 获取所有配置 + all_config = self.api.get_all_config() +``` + +### 2. 在Command中访问配置 + +```python +class MyCommand(BaseCommand): + async def execute(self): + # 访问配置的方法与Action完全相同 + value = self.api.get_config("section.key", "default") + + # 支持嵌套键访问 + nested_value = self.api.get_config("section.subsection.key") +``` + +### 3. 配置传递机制 + +系统会自动处理配置传递: +1. `BasePlugin`加载配置文件到`self.config` +2. 组件注册时,系统通过`component_registry.get_plugin_config()`获取配置 +3. Action/Command实例化时,配置作为`plugin_config`参数传递 +4. `PluginAPI`初始化时保存配置到`self._plugin_config` +5. 组件通过`self.api.get_config()`访问配置 + +## 🔧 使用这个示例 + +### 1. 创建插件目录 + +```bash +mkdir plugins/config_example_plugin +cd plugins/config_example_plugin +``` + +### 2. 复制文件 + +- 将配置文件保存为 `config.toml` +- 将插件代码保存为 `plugin.py` + +### 3. 测试功能 + +```bash +# 启动MaiBot后测试以下命令: + +# 测试配置状态显示 +/config status + +# 测试特定配置项 +/config test greeting.default_style +/config test database.enabled +/config test advanced.logging.level + +# 触发配置化问候 +配置问候 +``` + +## 💡 最佳实践 + +### 1. 提供合理的默认值 + +```python +# 总是提供默认值 +timeout = self.api.get_config("api.timeout", 30) +enabled = self.api.get_config("feature.enabled", False) +``` + +### 2. 验证配置类型 + +```python +# 验证配置类型 +max_items = self.api.get_config("list.max_items", 10) +if not isinstance(max_items, int) or max_items <= 0: + max_items = 10 # 使用安全的默认值 +``` + +### 3. 缓存复杂配置 + +```python +class MyAction(BaseAction): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # 缓存复杂配置避免重复解析 + self._cached_config = self._parse_complex_config() + + def _parse_complex_config(self): + # 解析复杂配置逻辑 + return processed_config +``` + +### 4. 配置变更检测 + +```python +# 对于支持热更新的配置 +last_config_hash = None + +def check_config_changes(self): + current_config = self.api.get_all_config() + current_hash = hash(str(current_config)) + + if current_hash != self.last_config_hash: + self.last_config_hash = current_hash + self._reload_config() +``` + +通过这种方式,你的Action和Command组件可以灵活地访问插件配置,实现更加强大和可定制的功能! \ No newline at end of file diff --git a/src/plugin_system/apis/plugin_api.py b/src/plugin_system/apis/plugin_api.py index a80165bb8..f0c675666 100644 --- a/src/plugin_system/apis/plugin_api.py +++ b/src/plugin_system/apis/plugin_api.py @@ -110,6 +110,62 @@ class PluginAPI(MessageAPI, LLMAPI, DatabaseAPI, ConfigAPI, UtilsAPI, StreamAPI, """获取action上下文信息""" return self._action_context.get(key, default) + def get_config(self, key: str, default=None): + """获取插件配置值,支持嵌套键访问 + + Args: + key: 配置键名,支持嵌套访问如 "section.subsection.key" + default: 默认值 + + Returns: + Any: 配置值或默认值 + """ + if not self._plugin_config: + return default + + # 支持嵌套键访问 + keys = key.split(".") + current = self._plugin_config + + for k in keys: + if isinstance(current, dict) and k in current: + current = current[k] + else: + return default + + return current + + def has_config(self, key: str) -> bool: + """检查是否存在指定的配置项 + + Args: + key: 配置键名,支持嵌套访问如 "section.subsection.key" + + Returns: + bool: 是否存在该配置项 + """ + if not self._plugin_config: + return False + + keys = key.split(".") + current = self._plugin_config + + for k in keys: + if isinstance(current, dict) and k in current: + current = current[k] + else: + return False + + return True + + def get_all_config(self) -> dict: + """获取所有插件配置 + + Returns: + dict: 插件配置字典的副本 + """ + return self._plugin_config.copy() if self._plugin_config else {} + # 便捷的工厂函数 def create_plugin_api(