feat(plugin): 实现插件配置集中化管理

将插件配置文件从各自的插件目录迁移至项目根目录下的 `config/plugins/` 文件夹中,方便用户统一管理和修改。

主要变更:
- 新增 `plugins.centralized_config` 总开关,用于控制是否启用此功能。
- 修改插件加载逻辑,现在会从 `config/plugins/<plugin_name>/` 目录读取用户配置。
- 如果用户配置不存在,会自动从插件目录下的模板配置文件复制一份。
- 保留了原有的配置版本检查和自动迁移功能,现在作用于用户配置文件。
This commit is contained in:
minecraft1024a
2025-08-18 13:00:13 +08:00
parent dcc2e4caff
commit 3b3eb080da
6 changed files with 101 additions and 82 deletions

View File

@@ -41,7 +41,8 @@ from src.config.official_configs import (
DependencyManagementConfig, DependencyManagementConfig,
ExaConfig, ExaConfig,
WebSearchConfig, WebSearchConfig,
TavilyConfig TavilyConfig,
PluginsConfig
) )
from .api_ada_configs import ( from .api_ada_configs import (
@@ -362,6 +363,7 @@ class Config(ConfigBase):
exa: ExaConfig = field(default_factory=lambda: ExaConfig()) exa: ExaConfig = field(default_factory=lambda: ExaConfig())
web_search: WebSearchConfig = field(default_factory=lambda: WebSearchConfig()) web_search: WebSearchConfig = field(default_factory=lambda: WebSearchConfig())
tavily: TavilyConfig = field(default_factory=lambda: TavilyConfig()) tavily: TavilyConfig = field(default_factory=lambda: TavilyConfig())
plugins: PluginsConfig = field(default_factory=lambda: PluginsConfig())
@dataclass @dataclass

View File

@@ -1003,3 +1003,12 @@ class WebSearchConfig(ConfigBase):
search_strategy: str = "single" search_strategy: str = "single"
"""搜索策略: 'single'(使用第一个可用引擎), 'parallel'(并行使用所有启用的引擎), 'fallback'(按顺序尝试,失败则尝试下一个)""" """搜索策略: 'single'(使用第一个可用引擎), 'parallel'(并行使用所有启用的引擎), 'fallback'(按顺序尝试,失败则尝试下一个)"""
@dataclass
class PluginsConfig(ConfigBase):
"""插件配置"""
centralized_config: bool = field(
default=True, metadata={"description": "是否启用插件配置集中化管理"}
)

View File

@@ -8,6 +8,7 @@ import shutil
import datetime import datetime
from src.common.logger import get_logger from src.common.logger import get_logger
from src.config.config import CONFIG_DIR
from src.plugin_system.base.component_types import ( from src.plugin_system.base.component_types import (
PluginInfo, PluginInfo,
PythonDependency, PythonDependency,
@@ -71,6 +72,7 @@ class PluginBase(ABC):
self.config: Dict[str, Any] = {} # 插件配置 self.config: Dict[str, Any] = {} # 插件配置
self.plugin_dir = plugin_dir # 插件目录路径 self.plugin_dir = plugin_dir # 插件目录路径
self.log_prefix = f"[Plugin:{self.plugin_name}]" self.log_prefix = f"[Plugin:{self.plugin_name}]"
self._is_enabled = self.enable_plugin # 从插件定义中获取默认启用状态
# 加载manifest文件 # 加载manifest文件
self._load_manifest() self._load_manifest()
@@ -100,7 +102,7 @@ class PluginBase(ABC):
description=self.plugin_description, description=self.plugin_description,
version=self.plugin_version, version=self.plugin_version,
author=self.plugin_author, author=self.plugin_author,
enabled=self.enable_plugin, enabled=self._is_enabled,
is_built_in=False, is_built_in=False,
config_file=self.config_file_name or "", config_file=self.config_file_name or "",
dependencies=self.dependencies.copy(), dependencies=self.dependencies.copy(),
@@ -453,86 +455,91 @@ class PluginBase(ABC):
logger.error(f"{self.log_prefix} 保存配置文件失败: {e}", exc_info=True) logger.error(f"{self.log_prefix} 保存配置文件失败: {e}", exc_info=True)
def _load_plugin_config(self): # sourcery skip: extract-method def _load_plugin_config(self): # sourcery skip: extract-method
"""加载插件配置文件,支持版本检查和自动迁移""" """
加载插件配置文件,实现集中化管理和自动迁移。
处理逻辑:
1. 确定插件模板配置文件路径(位于插件目录内)。
2. 如果模板不存在,则在插件目录内生成一份默认配置。
3. 确定用户配置文件路径(位于 `config/plugins/` 目录下)。
4. 如果用户配置文件不存在,则从插件目录复制模板文件过去。
5. 加载用户配置文件,并进行版本检查和自动迁移(如果需要)。
6. 最终加载的配置是用户配置文件。
"""
if not self.config_file_name: if not self.config_file_name:
logger.debug(f"{self.log_prefix} 未指定配置文件,跳过加载") logger.debug(f"{self.log_prefix} 未指定配置文件,跳过加载")
return return
# 优先使用传入的插件目录路径 # 1. 确定插件模板配置文件路径
if self.plugin_dir: template_config_path = os.path.join(self.plugin_dir, self.config_file_name)
plugin_dir = self.plugin_dir
else: # 2. 如果模板不存在,则在插件目录内生成
# fallback尝试从类的模块信息获取路径 if not os.path.exists(template_config_path):
logger.info(f"{self.log_prefix} 插件目录缺少配置文件 {template_config_path},将生成默认配置。")
self._generate_and_save_default_config(template_config_path)
# 3. 确定用户配置文件路径
plugin_config_dir = os.path.join(CONFIG_DIR, "plugins", self.plugin_name)
user_config_path = os.path.join(plugin_config_dir, self.config_file_name)
# 确保用户插件配置目录存在
os.makedirs(plugin_config_dir, exist_ok=True)
# 4. 如果用户配置文件不存在,从模板复制
if not os.path.exists(user_config_path):
try: try:
plugin_module_path = inspect.getfile(self.__class__) shutil.copy2(template_config_path, user_config_path)
plugin_dir = os.path.dirname(plugin_module_path) logger.info(f"{self.log_prefix} 已从模板创建用户配置文件: {user_config_path}")
except (TypeError, OSError): except IOError as e:
# 最后的fallback从模块的__file__属性获取 logger.error(f"{self.log_prefix} 复制配置文件失败: {e}", exc_info=True)
module = inspect.getmodule(self.__class__) # 如果复制失败,后续将无法加载,直接返回
if module and hasattr(module, "__file__") and module.__file__: return
plugin_dir = os.path.dirname(module.__file__)
else:
logger.warning(f"{self.log_prefix} 无法获取插件目录路径,跳过配置加载")
return
config_file_path = os.path.join(plugin_dir, self.config_file_name) # 检查最终的用户配置文件是否存在
if not os.path.exists(user_config_path):
# 如果配置文件不存在,生成默认配置 logger.warning(f"{self.log_prefix} 用户配置文件 {user_config_path} 不存在且无法创建。")
if not os.path.exists(config_file_path):
logger.info(f"{self.log_prefix} 配置文件 {config_file_path} 不存在,将生成默认配置。")
self._generate_and_save_default_config(config_file_path)
if not os.path.exists(config_file_path):
logger.warning(f"{self.log_prefix} 配置文件 {config_file_path} 不存在且无法生成。")
return return
file_ext = os.path.splitext(self.config_file_name)[1].lower() # 5. 加载、检查和迁移用户配置文件
_, file_ext = os.path.splitext(self.config_file_name)
if file_ext == ".toml": if file_ext.lower() != ".toml":
# 加载现有配置
with open(config_file_path, "r", encoding="utf-8") as f:
existing_config = toml.load(f) or {}
# 检查配置版本
current_version = self._get_current_config_version(existing_config)
# 如果配置文件没有版本信息,跳过版本检查
if current_version == "0.0.0":
logger.debug(f"{self.log_prefix} 配置文件无版本信息,跳过版本检查")
self.config = existing_config
else:
expected_version = self._get_expected_config_version()
if current_version != expected_version:
logger.info(
f"{self.log_prefix} 检测到配置版本需要更新: 当前=v{current_version}, 期望=v{expected_version}"
)
# 生成新的默认配置结构
new_config_structure = self._generate_config_from_schema()
# 迁移旧配置值到新结构
migrated_config = self._migrate_config_values(existing_config, new_config_structure)
# 保存迁移后的配置
self._save_config_to_file(migrated_config, config_file_path)
logger.info(f"{self.log_prefix} 配置文件已从 v{current_version} 更新到 v{expected_version}")
self.config = migrated_config
else:
logger.debug(f"{self.log_prefix} 配置版本匹配 (v{current_version}),直接加载")
self.config = existing_config
logger.debug(f"{self.log_prefix} 配置已从 {config_file_path} 加载")
# 从配置中更新 enable_plugin
if "plugin" in self.config and "enabled" in self.config["plugin"]:
self.enable_plugin = self.config["plugin"]["enabled"] # type: ignore
logger.debug(f"{self.log_prefix} 从配置更新插件启用状态: {self.enable_plugin}")
else:
logger.warning(f"{self.log_prefix} 不支持的配置文件格式: {file_ext},仅支持 .toml") logger.warning(f"{self.log_prefix} 不支持的配置文件格式: {file_ext},仅支持 .toml")
self.config = {} self.config = {}
return
try:
with open(user_config_path, "r", encoding="utf-8") as f:
existing_config = toml.load(f) or {}
except Exception as e:
logger.error(f"{self.log_prefix} 加载用户配置文件 {user_config_path} 失败: {e}", exc_info=True)
self.config = {}
return
current_version = self._get_current_config_version(existing_config)
expected_version = self._get_expected_config_version()
if current_version == "0.0.0":
logger.debug(f"{self.log_prefix} 用户配置文件无版本信息,跳过版本检查")
self.config = existing_config
elif current_version != expected_version:
logger.info(
f"{self.log_prefix} 检测到用户配置版本需要更新: 当前=v{current_version}, 期望=v{expected_version}"
)
new_config_structure = self._generate_config_from_schema()
migrated_config = self._migrate_config_values(existing_config, new_config_structure)
self._save_config_to_file(migrated_config, user_config_path)
logger.info(f"{self.log_prefix} 用户配置文件已从 v{current_version} 更新到 v{expected_version}")
self.config = migrated_config
else:
logger.debug(f"{self.log_prefix} 用户配置版本匹配 (v{current_version}),直接加载")
self.config = existing_config
logger.debug(f"{self.log_prefix} 配置已从 {user_config_path} 加载")
# 从配置中更新 enable_plugin 状态
if "plugin" in self.config and "enabled" in self.config["plugin"]:
self._is_enabled = self.config["plugin"]["enabled"]
logger.debug(f"{self.log_prefix} 从配置更新插件启用状态: {self._is_enabled}")
def _check_dependencies(self) -> bool: def _check_dependencies(self) -> bool:
"""检查插件依赖""" """检查插件依赖"""

View File

@@ -9,8 +9,6 @@ from src.common.logger import get_logger
from src.plugin_system import ( from src.plugin_system import (
BasePlugin, BasePlugin,
ComponentInfo, ComponentInfo,
BaseAction,
BaseCommand,
register_plugin register_plugin
) )
from src.plugin_system.base.config_types import ConfigField from src.plugin_system.base.config_types import ConfigField

View File

@@ -143,10 +143,10 @@ class SchedulerService:
if record: if record:
# 如果存在,则更新状态 # 如果存在,则更新状态
record.is_processed = True record.is_processed = True # type: ignore
record.processed_at = datetime.datetime.now() record.processed_at = datetime.datetime.now()# type: ignore
record.send_success = success record.send_success = success# type: ignore
record.story_content = content record.story_content = content# type: ignore
else: else:
# 如果不存在,则创建新记录 # 如果不存在,则创建新记录
new_record = MaiZoneScheduleStatus( new_record = MaiZoneScheduleStatus(

View File

@@ -1,5 +1,5 @@
[inner] [inner]
version = "6.3.6" version = "6.3.7"
#----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读---- #----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读----
#如果你想要修改配置文件请递增version的值 #如果你想要修改配置文件请递增version的值
@@ -350,3 +350,6 @@ enable_url_tool = true # 是否启用URL解析tool
# 搜索引擎配置 # 搜索引擎配置
enabled_engines = ["ddg"] # 启用的搜索引擎列表,可选: "exa", "tavily", "ddg" enabled_engines = ["ddg"] # 启用的搜索引擎列表,可选: "exa", "tavily", "ddg"
search_strategy = "single" # 搜索策略: "single"(使用第一个可用引擎), "parallel"(并行使用所有启用的引擎), "fallback"(按顺序尝试,失败则尝试下一个) search_strategy = "single" # 搜索策略: "single"(使用第一个可用引擎), "parallel"(并行使用所有启用的引擎), "fallback"(按顺序尝试,失败则尝试下一个)
[plugins] # 插件配置
centralized_config = true # 是否启用插件配置集中化管理