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
committed by Windpicker-owo
parent d30403e182
commit 9b382a9505
6 changed files with 101 additions and 82 deletions

View File

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

View File

@@ -1046,10 +1046,10 @@ class VideoAnalysisConfig(ConfigBase):
"""批量分析时使用的提示词"""
@dataclass
@dataclass
class WebSearchConfig(ConfigBase):
"""联网搜索组件配置类"""
enable_web_search_tool: bool = True
"""是否启用联网搜索工具"""
@@ -1060,4 +1060,13 @@ class WebSearchConfig(ConfigBase):
"""启用的搜索引擎列表,可选: 'exa', 'tavily', 'ddg'"""
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
from src.common.logger import get_logger
from src.config.config import CONFIG_DIR
from src.plugin_system.base.component_types import (
PluginInfo,
PythonDependency,
@@ -71,6 +72,7 @@ class PluginBase(ABC):
self.config: Dict[str, Any] = {} # 插件配置
self.plugin_dir = plugin_dir # 插件目录路径
self.log_prefix = f"[Plugin:{self.plugin_name}]"
self._is_enabled = self.enable_plugin # 从插件定义中获取默认启用状态
# 加载manifest文件
self._load_manifest()
@@ -100,7 +102,7 @@ class PluginBase(ABC):
description=self.plugin_description,
version=self.plugin_version,
author=self.plugin_author,
enabled=self.enable_plugin,
enabled=self._is_enabled,
is_built_in=False,
config_file=self.config_file_name or "",
dependencies=self.dependencies.copy(),
@@ -453,86 +455,91 @@ class PluginBase(ABC):
logger.error(f"{self.log_prefix} 保存配置文件失败: {e}", exc_info=True)
def _load_plugin_config(self): # sourcery skip: extract-method
"""加载插件配置文件,支持版本检查和自动迁移"""
"""
加载插件配置文件,实现集中化管理和自动迁移。
处理逻辑:
1. 确定插件模板配置文件路径(位于插件目录内)。
2. 如果模板不存在,则在插件目录内生成一份默认配置。
3. 确定用户配置文件路径(位于 `config/plugins/` 目录下)。
4. 如果用户配置文件不存在,则从插件目录复制模板文件过去。
5. 加载用户配置文件,并进行版本检查和自动迁移(如果需要)。
6. 最终加载的配置是用户配置文件。
"""
if not self.config_file_name:
logger.debug(f"{self.log_prefix} 未指定配置文件,跳过加载")
return
# 优先使用传入的插件目录路径
if self.plugin_dir:
plugin_dir = self.plugin_dir
else:
# fallback尝试从类的模块信息获取路径
# 1. 确定插件模板配置文件路径
template_config_path = os.path.join(self.plugin_dir, self.config_file_name)
# 2. 如果模板不存在,则在插件目录内生成
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:
plugin_module_path = inspect.getfile(self.__class__)
plugin_dir = os.path.dirname(plugin_module_path)
except (TypeError, OSError):
# 最后的fallback从模块的__file__属性获取
module = inspect.getmodule(self.__class__)
if module and hasattr(module, "__file__") and module.__file__:
plugin_dir = os.path.dirname(module.__file__)
else:
logger.warning(f"{self.log_prefix} 无法获取插件目录路径,跳过配置加载")
return
shutil.copy2(template_config_path, user_config_path)
logger.info(f"{self.log_prefix} 已从模板创建用户配置文件: {user_config_path}")
except IOError as e:
logger.error(f"{self.log_prefix} 复制配置文件失败: {e}", exc_info=True)
# 如果复制失败,后续将无法加载,直接返回
return
config_file_path = os.path.join(plugin_dir, self.config_file_name)
# 如果配置文件不存在,生成默认配置
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} 不存在且无法生成。")
# 检查最终的用户配置文件是否存在
if not os.path.exists(user_config_path):
logger.warning(f"{self.log_prefix} 用户配置文件 {user_config_path} 不存在且无法创建。")
return
file_ext = os.path.splitext(self.config_file_name)[1].lower()
if file_ext == ".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:
# 5. 加载、检查和迁移用户配置文件
_, file_ext = os.path.splitext(self.config_file_name)
if file_ext.lower() != ".toml":
logger.warning(f"{self.log_prefix} 不支持的配置文件格式: {file_ext},仅支持 .toml")
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:
"""检查插件依赖"""

View File

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

View File

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

View File

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