feat(plugin): 实现插件配置集中化管理
将插件配置文件从各自的插件目录迁移至项目根目录下的 `config/plugins/` 文件夹中,方便用户统一管理和修改。 主要变更: - 新增 `plugins.centralized_config` 总开关,用于控制是否启用此功能。 - 修改插件加载逻辑,现在会从 `config/plugins/<plugin_name>/` 目录读取用户配置。 - 如果用户配置不存在,会自动从插件目录下的模板配置文件复制一份。 - 保留了原有的配置版本检查和自动迁移功能,现在作用于用户配置文件。
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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": "是否启用插件配置集中化管理"}
|
||||||
|
)
|
||||||
@@ -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__:
|
|
||||||
plugin_dir = os.path.dirname(module.__file__)
|
|
||||||
else:
|
|
||||||
logger.warning(f"{self.log_prefix} 无法获取插件目录路径,跳过配置加载")
|
|
||||||
return
|
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:
|
||||||
"""检查插件依赖"""
|
"""检查插件依赖"""
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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 # 是否启用插件配置集中化管理
|
||||||
|
|||||||
Reference in New Issue
Block a user