refactor: use explicit system config getter

This commit is contained in:
jxxghp
2026-08-24 04:04:35 +08:00
parent 4dd2d7fed2
commit 8555d0da3e
28 changed files with 111 additions and 76 deletions
+4 -4
View File
@@ -23,7 +23,7 @@ import jwt
from app.runtime.settings import RuntimeSettingsCompat from app.runtime.settings import RuntimeSettingsCompat
settings = RuntimeSettingsCompat() settings = RuntimeSettingsCompat()
from app.application.configuration import get_configured_system_config as SystemConfigOper from app.application.configuration import get_configured_system_config
from app.runtime.log import logger from app.runtime.log import logger
from app.schemas.types import LlmProviderAction, SystemConfigKey from app.schemas.types import LlmProviderAction, SystemConfigKey
from app.foundation.singleton import Singleton from app.foundation.singleton import Singleton
@@ -1510,7 +1510,7 @@ class LLMProviderManager(metaclass=Singleton):
@staticmethod @staticmethod
def _read_agent_config() -> dict[str, Any]: def _read_agent_config() -> dict[str, Any]:
"""读取 AI Agent 配置信息。""" """读取 AI Agent 配置信息。"""
config = SystemConfigOper().get(SystemConfigKey.AIAgentConfig) config = get_configured_system_config().get(SystemConfigKey.AIAgentConfig)
if isinstance(config, dict): if isinstance(config, dict):
return config return config
return {} return {}
@@ -1520,10 +1520,10 @@ class LLMProviderManager(metaclass=Singleton):
""" """
使用异步持久化写回 provider 鉴权配置。 使用异步持久化写回 provider 鉴权配置。
`SystemConfigOper().get()` 读取的是内存缓存,这里保留同步调用; `get_configured_system_config().get()` 读取的是内存缓存,这里保留同步调用;
但写入需要落库,因此统一走 `async_set()`。 但写入需要落库,因此统一走 `async_set()`。
""" """
await SystemConfigOper().async_set( await get_configured_system_config().async_set(
SystemConfigKey.AIAgentConfig, SystemConfigKey.AIAgentConfig,
copy.deepcopy(value) or None, copy.deepcopy(value) or None,
) )
+3 -3
View File
@@ -12,7 +12,7 @@ from dataclasses import dataclass
from typing import Any, Optional from typing import Any, Optional
from urllib.parse import urljoin from urllib.parse import urljoin
from app.application.configuration import get_configured_system_config as SystemConfigOper from app.application.configuration import get_configured_system_config
from app.runtime.log import logger from app.runtime.log import logger
from app.schemas.agent import ( from app.schemas.agent import (
AgentMcpServerConfig, AgentMcpServerConfig,
@@ -456,7 +456,7 @@ class AgentMcpManager:
def get_servers(self) -> list[AgentMcpServerConfig]: def get_servers(self) -> list[AgentMcpServerConfig]:
"""读取已保存的外部 MCP 服务器配置。""" """读取已保存的外部 MCP 服务器配置。"""
raw_servers = SystemConfigOper().get(SystemConfigKey.AIAgentMcpServers) or [] raw_servers = get_configured_system_config().get(SystemConfigKey.AIAgentMcpServers) or []
if not isinstance(raw_servers, list): if not isinstance(raw_servers, list):
return [] return []
servers: list[AgentMcpServerConfig] = [] servers: list[AgentMcpServerConfig] = []
@@ -470,7 +470,7 @@ class AgentMcpManager:
async def save_servers(self, servers: list[AgentMcpServerConfig]) -> bool: async def save_servers(self, servers: list[AgentMcpServerConfig]) -> bool:
"""保存外部 MCP 服务器配置。""" """保存外部 MCP 服务器配置。"""
normalized_servers = [self.normalize_server(server).model_dump() for server in servers] normalized_servers = [self.normalize_server(server).model_dump() for server in servers]
return await SystemConfigOper().async_set( return await get_configured_system_config().async_set(
SystemConfigKey.AIAgentMcpServers, SystemConfigKey.AIAgentMcpServers,
normalized_servers or None, normalized_servers or None,
) )
+7 -7
View File
@@ -6,7 +6,7 @@ from typing import Any, Dict, Iterable, Optional
from app.runtime.events import eventmanager from app.runtime.events import eventmanager
from app.application.agentdata import SubscribePort as SubscribeOper from app.application.agentdata import SubscribePort as SubscribeOper
from app.application.configuration import get_configured_system_config as SystemConfigOper from app.application.configuration import get_configured_system_config
from app.application.rules import RuleHelper from app.application.rules import RuleHelper
from app.application.rules import RuleParser from app.application.rules import RuleParser
from app.application.rules import BUILTIN_RULE_SET from app.application.rules import BUILTIN_RULE_SET
@@ -252,13 +252,13 @@ async def collect_rule_group_usages(
"""收集规则组在全局配置和订阅上的引用情况。""" """收集规则组在全局配置和订阅上的引用情况。"""
target_names = set(group_names or []) target_names = set(group_names or [])
search_groups = set( search_groups = set(
SystemConfigOper().get(SystemConfigKey.SearchFilterRuleGroups) or [] get_configured_system_config().get(SystemConfigKey.SearchFilterRuleGroups) or []
) )
subscribe_groups = set( subscribe_groups = set(
SystemConfigOper().get(SystemConfigKey.SubscribeFilterRuleGroups) or [] get_configured_system_config().get(SystemConfigKey.SubscribeFilterRuleGroups) or []
) )
best_version_groups = set( best_version_groups = set(
SystemConfigOper().get(SystemConfigKey.BestVersionFilterRuleGroups) or [] get_configured_system_config().get(SystemConfigKey.BestVersionFilterRuleGroups) or []
) )
usage_map = { usage_map = {
@@ -428,7 +428,7 @@ async def save_system_config(
] ]
normalized_value = normalized_value or None normalized_value = normalized_value or None
success = await SystemConfigOper().async_set(key, normalized_value) success = await get_configured_system_config().async_set(key, normalized_value)
if success: if success:
await eventmanager.async_send_event( await eventmanager.async_send_event(
etype=EventType.ConfigChanged, etype=EventType.ConfigChanged,
@@ -475,7 +475,7 @@ async def rename_rule_group_references(old_name: str, new_name: str) -> dict:
SystemConfigKey.SubscribeFilterRuleGroups, SystemConfigKey.SubscribeFilterRuleGroups,
SystemConfigKey.BestVersionFilterRuleGroups, SystemConfigKey.BestVersionFilterRuleGroups,
): ):
original = SystemConfigOper().get(config_key) or [] original = get_configured_system_config().get(config_key) or []
updated = replace_group_name_in_list(original, old_name, new_name) updated = replace_group_name_in_list(original, old_name, new_name)
if updated != original: if updated != original:
await save_system_config(config_key, updated) await save_system_config(config_key, updated)
@@ -513,7 +513,7 @@ async def remove_rule_group_references(group_name: str) -> dict:
SystemConfigKey.SubscribeFilterRuleGroups, SystemConfigKey.SubscribeFilterRuleGroups,
SystemConfigKey.BestVersionFilterRuleGroups, SystemConfigKey.BestVersionFilterRuleGroups,
): ):
original = SystemConfigOper().get(config_key) or [] original = get_configured_system_config().get(config_key) or []
updated = [value for value in original if value != group_name] updated = [value for value in original if value != group_name]
if updated != original: if updated != original:
await save_system_config(config_key, updated) await save_system_config(config_key, updated)
+4 -4
View File
@@ -11,7 +11,7 @@ from app.runtime.settings import RuntimeSettingsCompat
settings = RuntimeSettingsCompat() settings = RuntimeSettingsCompat()
from app.application.plugin.runtime import get_plugin_manager from app.application.plugin.runtime import get_plugin_manager
from app.application.plugin.install import PluginInstallCommand from app.application.plugin.install import PluginInstallCommand
from app.application.configuration import get_configured_system_config as SystemConfigOper from app.application.configuration import get_configured_system_config
from app.adapters.external.server import MoviePilotServerHelper from app.adapters.external.server import MoviePilotServerHelper
from app.adapters.external.market import PluginHelper from app.adapters.external.market import PluginHelper
from app.adapters.system.plugin.package import PluginPackageManager from app.adapters.system.plugin.package import PluginPackageManager
@@ -327,7 +327,7 @@ async def install_plugin_runtime(
async def save_installed_plugins(plugin_ids: list[str]) -> object: async def save_installed_plugins(plugin_ids: list[str]) -> object:
"""保存智能体安装用例确认后的插件列表。""" """保存智能体安装用例确认后的插件列表。"""
return await SystemConfigOper().async_set( return await get_configured_system_config().async_set(
SystemConfigKey.UserInstalledPlugins, SystemConfigKey.UserInstalledPlugins,
plugin_ids, plugin_ids,
) )
@@ -377,7 +377,7 @@ async def install_plugin_runtime(
return result return result
result = await PluginInstallCommand( result = await PluginInstallCommand(
installed_plugins_reader=lambda: SystemConfigOper().get( installed_plugins_reader=lambda: get_configured_system_config().get(
SystemConfigKey.UserInstalledPlugins SystemConfigKey.UserInstalledPlugins
) or [], ) or [],
installed_plugins_writer=save_installed_plugins, installed_plugins_writer=save_installed_plugins,
@@ -422,7 +422,7 @@ async def uninstall_plugin_runtime(plugin_id: str) -> dict[str, Any]:
instance_ids = "".join(item.instance_id for item in source_instances) instance_ids = "".join(item.instance_id for item in source_instances)
raise ValueError(f"请先卸载该插件的分身:{instance_ids}") raise ValueError(f"请先卸载该插件的分身:{instance_ids}")
config_oper = SystemConfigOper() config_oper = get_configured_system_config()
install_plugins = config_oper.get(SystemConfigKey.UserInstalledPlugins) or [] install_plugins = config_oper.get(SystemConfigKey.UserInstalledPlugins) or []
if plugin_id in install_plugins: if plugin_id in install_plugins:
install_plugins = [ install_plugins = [
@@ -7,7 +7,7 @@ from pydantic import BaseModel
from app.agent.tools.base import MoviePilotTool from app.agent.tools.base import MoviePilotTool
from app.agent.tools.tags import ToolTag from app.agent.tools.tags import ToolTag
from app.application.configuration import get_configured_system_config as SystemConfigOper from app.application.configuration import get_configured_system_config
from app.runtime.log import logger from app.runtime.log import logger
from app.schemas.types import SystemConfigKey from app.schemas.types import SystemConfigKey
@@ -39,7 +39,7 @@ class QueryCustomIdentifiersTool(MoviePilotTool):
@staticmethod @staticmethod
def _load_custom_identifiers(): def _load_custom_identifiers():
"""从内存配置缓存中读取自定义识别词。""" """从内存配置缓存中读取自定义识别词。"""
return SystemConfigOper().get(SystemConfigKey.CustomIdentifiers) return get_configured_system_config().get(SystemConfigKey.CustomIdentifiers)
async def run(self, **kwargs) -> str: async def run(self, **kwargs) -> str:
logger.info(f"执行工具: {self.name}") logger.info(f"执行工具: {self.name}")
+2 -2
View File
@@ -7,7 +7,7 @@ from pydantic import BaseModel
from app.agent.tools.base import MoviePilotTool from app.agent.tools.base import MoviePilotTool
from app.agent.tools.tags import ToolTag from app.agent.tools.tags import ToolTag
from app.application.configuration import get_configured_system_config as SystemConfigOper from app.application.configuration import get_configured_system_config
from app.runtime.log import logger from app.runtime.log import logger
from app.schemas.types import SystemConfigKey from app.schemas.types import SystemConfigKey
@@ -34,7 +34,7 @@ class QueryDownloadersTool(MoviePilotTool):
@staticmethod @staticmethod
def _load_downloaders_config(): def _load_downloaders_config():
"""从内存配置缓存中读取下载器配置。""" """从内存配置缓存中读取下载器配置。"""
return SystemConfigOper().get(SystemConfigKey.Downloaders) return get_configured_system_config().get(SystemConfigKey.Downloaders)
@staticmethod @staticmethod
def _sanitize_downloaders_config(downloaders_config: list) -> list: def _sanitize_downloaders_config(downloaders_config: list) -> list:
@@ -17,7 +17,7 @@ from app.agent.tools.impl._system_setting_utils import (
) )
from app.application.configuration import ( from app.application.configuration import (
SystemConfigReader, SystemConfigReader,
get_configured_system_config as SystemConfigOper, get_configured_system_config,
get_runtime_settings, get_runtime_settings,
) )
from app.runtime.log import logger from app.runtime.log import logger
@@ -103,7 +103,7 @@ class QuerySystemSettingsTool(MoviePilotTool):
def _get_system_config(self) -> SystemConfigReader: def _get_system_config(self) -> SystemConfigReader:
"""返回显式注入端口,旧构造形态则延迟读取组合根服务。""" """返回显式注入端口,旧构造形态则延迟读取组合根服务。"""
return self._system_config or SystemConfigOper() return self._system_config or get_configured_system_config()
async def _run_confirmed(self, **kwargs) -> str: async def _run_confirmed(self, **kwargs) -> str:
"""仅供宿主在消费有效确认后执行一次未脱敏读取。""" """仅供宿主在消费有效确认后执行一次未脱敏读取。"""
+2 -2
View File
@@ -8,7 +8,7 @@ from pydantic import BaseModel, Field
from app.agent.tools.base import MoviePilotTool from app.agent.tools.base import MoviePilotTool
from app.agent.tools.tags import ToolTag from app.agent.tools.tags import ToolTag
from app.chain.search import SearchChain from app.chain.search import SearchChain
from app.application.configuration import get_configured_system_config as SystemConfigOper from app.application.configuration import get_configured_system_config
from app.application.site.sites import SitesHelper # pylint: disable=import-error,no-name-in-module from app.application.site.sites import SitesHelper # pylint: disable=import-error,no-name-in-module
from app.runtime.log import logger from app.runtime.log import logger
from app.schemas.types import MediaSource, MediaType, SystemConfigKey from app.schemas.types import MediaSource, MediaType, SystemConfigKey
@@ -63,7 +63,7 @@ class SearchTorrentsTool(MoviePilotTool):
@staticmethod @staticmethod
def _load_configured_sites() -> List[int]: def _load_configured_sites() -> List[int]:
"""同步读取默认搜索站点列表。""" """同步读取默认搜索站点列表。"""
return SystemConfigOper().get(SystemConfigKey.IndexerSites) or [] return get_configured_system_config().get(SystemConfigKey.IndexerSites) or []
async def run(self, media_source: MediaSource, media_id: str, async def run(self, media_source: MediaSource, media_id: str,
media_type: Optional[str] = None, area: Optional[str] = None, media_type: Optional[str] = None, area: Optional[str] = None,
@@ -8,7 +8,7 @@ from pydantic import BaseModel, Field
from app.agent.tools.base import MoviePilotTool from app.agent.tools.base import MoviePilotTool
from app.agent.tools.tags import ToolTag from app.agent.tools.tags import ToolTag
from app.domain.metainfo import clear_rust_parse_options_cache from app.domain.metainfo import clear_rust_parse_options_cache
from app.application.configuration import get_configured_system_config as SystemConfigOper from app.application.configuration import get_configured_system_config
from app.runtime.log import logger from app.runtime.log import logger
from app.schemas.types import SystemConfigKey from app.schemas.types import SystemConfigKey
@@ -80,7 +80,7 @@ class UpdateCustomIdentifiersTool(MoviePilotTool):
# 过滤空字符串 # 过滤空字符串
identifiers = [i for i in identifiers if i is not None] identifiers = [i for i in identifiers if i is not None]
system_config_oper = SystemConfigOper() system_config_oper = get_configured_system_config()
# 保存 # 保存
value = identifiers if identifiers else None value = identifiers if identifiers else None
@@ -19,7 +19,7 @@ from app.agent.tools.impl._system_setting_utils import (
from app.runtime.events import eventmanager from app.runtime.events import eventmanager
from app.application.configuration import ( from app.application.configuration import (
SystemConfigService, SystemConfigService,
get_configured_system_config as SystemConfigOper, get_configured_system_config,
get_runtime_settings, get_runtime_settings,
) )
from app.application.plugin.runtime import plugin_system_config_mutation from app.application.plugin.runtime import plugin_system_config_mutation
@@ -109,7 +109,7 @@ class UpdateSystemSettingsTool(MoviePilotTool):
def _get_system_config(self) -> SystemConfigService: def _get_system_config(self) -> SystemConfigService:
"""返回显式注入服务,旧构造形态则延迟读取组合根服务。""" """返回显式注入服务,旧构造形态则延迟读取组合根服务。"""
return self._system_config or SystemConfigOper() return self._system_config or get_configured_system_config()
def get_tool_message(self, **kwargs) -> Optional[str]: def get_tool_message(self, **kwargs) -> Optional[str]:
"""根据更新参数生成友好的提示消息。""" """根据更新参数生成友好的提示消息。"""
+1 -9
View File
@@ -92,12 +92,6 @@ else:
"""返回快照字段,兼容整理链的响应转换。""" """返回快照字段,兼容整理链的响应转换。"""
return dict(self.__dict__) return dict(self.__dict__)
# 旧测试与第三方扩展可能替换该名字;它现在只是应用配置端口的本地别名,
# 不再指向数据库 Oper。
SystemConfigOper = get_configured_system_config
_DEFAULT_SYSTEM_CONFIG_PROVIDER = get_configured_system_config
@dataclass(frozen=True, slots=True) @dataclass(frozen=True, slots=True)
class _SubscribePostCommitContext: class _SubscribePostCommitContext:
"""订阅提交后副作用所需的不可变业务快照。""" """订阅提交后副作用所需的不可变业务快照。"""
@@ -142,9 +136,7 @@ class _SubscribeCreateContext:
def _system_config(): def _system_config():
"""返回配置端口,并兼容旧测试对本地别名的替换""" """返回启动组合根登记的配置端口。"""
if SystemConfigOper is not _DEFAULT_SYSTEM_CONFIG_PROVIDER:
return SystemConfigOper()
return get_configured_system_config() return get_configured_system_config()
+2 -2
View File
@@ -4,7 +4,7 @@ from typing import Tuple, List
from app.runtime.settings import RuntimeSettingsCompat from app.runtime.settings import RuntimeSettingsCompat
settings = RuntimeSettingsCompat() settings = RuntimeSettingsCompat()
from app.application.configuration import get_configured_system_config as SystemConfigOper from app.application.configuration import get_configured_system_config
from app.runtime.log import logger from app.runtime.log import logger
from app.schemas.types import MediaType from app.schemas.types import MediaType
from app.adapters.network.http import RequestUtils, AsyncRequestUtils from app.adapters.network.http import RequestUtils, AsyncRequestUtils
@@ -62,7 +62,7 @@ class HaiDanSpider:
return None return None
def __init__(self, indexer: dict): def __init__(self, indexer: dict):
self.systemconfig = SystemConfigOper() self.systemconfig = get_configured_system_config()
if indexer: if indexer:
self._indexerid = indexer.get('id') self._indexerid = indexer.get('id')
self._url = indexer.get('domain') self._url = indexer.get('domain')
+2 -2
View File
@@ -3,7 +3,7 @@ from typing import Tuple, List, Optional
from app.runtime.settings import RuntimeSettingsCompat from app.runtime.settings import RuntimeSettingsCompat
settings = RuntimeSettingsCompat() settings = RuntimeSettingsCompat()
from app.application.configuration import get_configured_system_config as SystemConfigOper from app.application.configuration import get_configured_system_config
from app.runtime.log import logger from app.runtime.log import logger
from app.schemas.types import MediaType from app.schemas.types import MediaType
from app.adapters.network.http import RequestUtils, AsyncRequestUtils from app.adapters.network.http import RequestUtils, AsyncRequestUtils
@@ -69,7 +69,7 @@ class HddolbySpider:
return cls._size return cls._size
def __init__(self, indexer: dict): def __init__(self, indexer: dict):
self.systemconfig = SystemConfigOper() self.systemconfig = get_configured_system_config()
if indexer: if indexer:
self._indexerid = indexer.get('id') self._indexerid = indexer.get('id')
self._domain = indexer.get('domain') self._domain = indexer.get('domain')
+2 -2
View File
@@ -7,7 +7,7 @@ from urllib.parse import urlparse
from app.runtime.settings import RuntimeSettingsCompat from app.runtime.settings import RuntimeSettingsCompat
settings = RuntimeSettingsCompat() settings = RuntimeSettingsCompat()
from app.application.configuration import get_configured_system_config as SystemConfigOper from app.application.configuration import get_configured_system_config
from app.runtime.log import logger from app.runtime.log import logger
from app.schemas.types import MediaType from app.schemas.types import MediaType
from app.adapters.network.http import RequestUtils, AsyncRequestUtils from app.adapters.network.http import RequestUtils, AsyncRequestUtils
@@ -67,7 +67,7 @@ class MTorrentSpider:
return cls._size return cls._size
def __init__(self, indexer: dict): def __init__(self, indexer: dict):
self.systemconfig = SystemConfigOper() self.systemconfig = get_configured_system_config()
if indexer: if indexer:
self._indexerid = indexer.get('id') self._indexerid = indexer.get('id')
self._url = indexer.get('domain') self._url = indexer.get('domain')
+2 -2
View File
@@ -5,7 +5,7 @@ from typing import List, Optional, Tuple
from app.runtime.settings import RuntimeSettingsCompat from app.runtime.settings import RuntimeSettingsCompat
settings = RuntimeSettingsCompat() settings = RuntimeSettingsCompat()
from app.application.configuration import get_configured_system_config as SystemConfigOper from app.application.configuration import get_configured_system_config
from app.runtime.log import logger from app.runtime.log import logger
from app.schemas.types import MediaType from app.schemas.types import MediaType
from app.adapters.network.http import RequestUtils, AsyncRequestUtils from app.adapters.network.http import RequestUtils, AsyncRequestUtils
@@ -51,7 +51,7 @@ class RousiSpider:
return cls._size return cls._size
def __init__(self, indexer: dict): def __init__(self, indexer: dict):
self.systemconfig = SystemConfigOper() self.systemconfig = get_configured_system_config()
if indexer: if indexer:
self._indexerid = indexer.get('id') self._indexerid = indexer.get('id')
self._url = indexer.get('domain') self._url = indexer.get('domain')
+3 -3
View File
@@ -12,7 +12,7 @@ from app.schemas.mediaserver import MediaServerLibrary as _SchemaMediaServerLibr
from app.schemas.mediaserver import MediaServerPlayItem as _SchemaMediaServerPlayItem from app.schemas.mediaserver import MediaServerPlayItem as _SchemaMediaServerPlayItem
from app.schemas.mediaserver import RefreshMediaItem as _SchemaRefreshMediaItem from app.schemas.mediaserver import RefreshMediaItem as _SchemaRefreshMediaItem
from app.schemas.mediaserver import WebhookEventInfo as _SchemaWebhookEventInfo from app.schemas.mediaserver import WebhookEventInfo as _SchemaWebhookEventInfo
from app.application.configuration import get_configured_system_config as SystemConfigOper from app.application.configuration import get_configured_system_config
from app.application.mediaserver import MediaServerIdentityHelper, MusicMediaServerHelper from app.application.mediaserver import MediaServerIdentityHelper, MusicMediaServerHelper
from app.runtime.log import logger from app.runtime.log import logger
from app.modules.ugreen.api import Api from app.modules.ugreen.api import Api
@@ -116,12 +116,12 @@ class Ugreen:
@staticmethod @staticmethod
def __load_all_session_cache() -> dict: def __load_all_session_cache() -> dict:
sessions = SystemConfigOper().get(SystemConfigKey.UgreenSessionCache) sessions = get_configured_system_config().get(SystemConfigKey.UgreenSessionCache)
return sessions if isinstance(sessions, dict) else {} return sessions if isinstance(sessions, dict) else {}
@staticmethod @staticmethod
def __save_all_session_cache(sessions: dict): def __save_all_session_cache(sessions: dict):
SystemConfigOper().set(SystemConfigKey.UgreenSessionCache, sessions) get_configured_system_config().set(SystemConfigKey.UgreenSessionCache, sessions)
def __remove_persisted_session(self): def __remove_persisted_session(self):
cache_key = self.__session_cache_key() cache_key = self.__session_cache_key()
+2 -2
View File
@@ -2,7 +2,7 @@ from abc import ABC, abstractmethod
from typing import Any, ClassVar, Union from typing import Any, ClassVar, Union
from app.chain import ChainBase from app.chain import ChainBase
from app.application.configuration import get_configured_system_config as SystemConfigOper from app.application.configuration import get_configured_system_config
from app.schemas.workflow import ActionContext from app.schemas.workflow import ActionContext
from app.schemas.workflow import ActionParams from app.schemas.workflow import ActionParams
from app.schemas.workflow import ActionResult from app.schemas.workflow import ActionResult
@@ -37,7 +37,7 @@ class BaseAction(ABC):
self._action_id = action_id self._action_id = action_id
self._done_flag = False self._done_flag = False
self._message = "" self._message = ""
self.systemconfigoper = SystemConfigOper() self.systemconfigoper = get_configured_system_config()
@classmethod @classmethod
def get_contract(cls) -> dict: def get_contract(cls) -> dict:
@@ -6,7 +6,7 @@
> 审计范围:宿主后端;排除 `app/plugins/**` 运行时插件副本 > 审计范围:宿主后端;排除 `app/plugins/**` 运行时插件副本
> 规范优先级:`AGENTS.md``docs/rules/` 高于本文 > 规范优先级:`AGENTS.md``docs/rules/` 高于本文
> 相关文档:`docs/architecture-overview.md``docs/refactor/backend-architecture-governance.md``docs/refactor/backend-module-refactor-compatibility.md` > 相关文档:`docs/architecture-overview.md``docs/refactor/backend-architecture-governance.md``docs/refactor/backend-module-refactor-compatibility.md`
> 实施进度:阶段 0~6 的宿主架构能力已完成收口;API/Application 公共复杂度基线已清零,启动组合根的 SystemConfigOper 构造点已由 14 降至 1;API 进程内后台任务已完成首批统一登记,插件仓适配和 Outbox 外围扩展仍按风险切片推进。Model/Base 查询与写装饰器、legacy 隐式会话外壳均已清零,插件 SDK 也不再导出宿主 Model。2026-08-23 的长期整改阶段 0 已恢复宿主、启动性能、官方插件和 SDK 契约门禁的可信基线;阶段 1a 已补齐 TaskRegistry owner 零债务门禁和诚实的关停超时语义;阶段 1b1 已收口整理 worker、pending 回放、失败通知、进程内 AI 重试、插件监控与事件投递的生命周期所有权;2026-08-24 的阶段 2 已将 212 个已观察宿主模块方法的 legacy aggregation 清零,并补齐可执行 fanout 与下载器文件 DTO 边界;阶段 3 已将消息交互和远程命令的订阅删除统一到 Application/UoW/outbox,宿主不再调用裸线程统计入口;阶段 4 已统一七种消息渠道的宿主回环与后台执行边界;阶段 5 已补齐事件窗口聚合任务的生命周期所有权;阶段 6 已统一插件文件操作的取消完成语义;阶段 7 已统一插件协程补偿的终态等待;阶段 8 已统一宿主同步函数的异步线程池入口;阶段 9 已统一工作流运行时的宿主获取路径;阶段 10 已统一模块、插件与调度运行时的显式 getter 调用。 > 实施进度:阶段 0~6 的宿主架构能力已完成收口;API/Application 公共复杂度基线已清零,启动组合根的 SystemConfigOper 构造点已由 14 降至 1;API 进程内后台任务已完成首批统一登记,插件仓适配和 Outbox 外围扩展仍按风险切片推进。Model/Base 查询与写装饰器、legacy 隐式会话外壳均已清零,插件 SDK 也不再导出宿主 Model。2026-08-23 的长期整改阶段 0 已恢复宿主、启动性能、官方插件和 SDK 契约门禁的可信基线;阶段 1a 已补齐 TaskRegistry owner 零债务门禁和诚实的关停超时语义;阶段 1b1 已收口整理 worker、pending 回放、失败通知、进程内 AI 重试、插件监控与事件投递的生命周期所有权;2026-08-24 的阶段 2 已将 212 个已观察宿主模块方法的 legacy aggregation 清零,并补齐可执行 fanout 与下载器文件 DTO 边界;阶段 3 已将消息交互和远程命令的订阅删除统一到 Application/UoW/outbox,宿主不再调用裸线程统计入口;阶段 4 已统一七种消息渠道的宿主回环与后台执行边界;阶段 5 已补齐事件窗口聚合任务的生命周期所有权;阶段 6 已统一插件文件操作的取消完成语义;阶段 7 已统一插件协程补偿的终态等待;阶段 8 已统一宿主同步函数的异步线程池入口;阶段 9 已统一工作流运行时的宿主获取路径;阶段 10 已统一模块、插件与调度运行时的显式 getter 调用;阶段 11 已清除系统配置 getter 的 Oper 形别名
## 当前复核结论(2026-08-24 ## 当前复核结论(2026-08-24
@@ -129,6 +129,17 @@
- 兼容边界不变:Application 的 `ModuleManager``Scheduler` 类形 Facade 和 concrete 插件管理器类路径 - 兼容边界不变:Application 的 `ModuleManager``Scheduler` 类形 Facade 和 concrete 插件管理器类路径
继续保留,旧插件、V1/V2/V3 索引加载及 SDK/Compat 映射无需迁移;本阶段仅统一宿主生产路径。 继续保留,旧插件、V1/V2/V3 索引加载及 SDK/Compat 映射无需迁移;本阶段仅统一宿主生产路径。
### 长期整改阶段 11:系统配置端口命名统一(2026-08-24)
- Agent、Chain、Module 和 Workflow 的 17 个 canonical 文件原先把
`get_configured_system_config()` 别名或赋值为 `SystemConfigOper`,使 Application 配置端口在调用处
看起来仍像数据库 Oper。宿主生产路径现统一显式调用 getter,测试也改为替换真实组合根接缝。
- 架构门禁禁止为 `get_configured_system_config` 建立别名,也禁止把它赋给本地
`SystemConfigOper`;真正的 `app.db.oper.systemconfig.SystemConfigOper` 只留在 DB 实现、startup 装配和
testing bootstrap,不再形成第二种 canonical 获取方式。
- 兼容边界不变:DB Oper 类、`app.db.oper` 懒导出、SDK/Compat 旧路径和 V1/V2/V3 插件加载均未改动;
已注入 `SystemConfigReader/SystemConfigService` 的 Agent 工具构造合同保持原样。
### 总体判断 ### 总体判断
当前架构总体合理,已经从跨层混合的遗留单体收敛为**边界清晰的模块化单体**: 当前架构总体合理,已经从跨层混合的遗留单体收敛为**边界清晰的模块化单体**:
+7 -6
View File
@@ -107,12 +107,13 @@ API dependencies must narrow that object to a domain runtime (for example,
`AgentChatRuntime`) instead of adding a string key to a global service map. `AgentChatRuntime`) instead of adding a string key to a global service map.
Legacy registries may delegate the same object while domains migrate, but they Legacy registries may delegate the same object while domains migrate, but they
must not construct a second set of service instances. must not construct a second set of service instances.
Canonical host consumers of the process-wide module, plugin and scheduler Canonical host consumers of the process-wide module, plugin, scheduler and
runtimes must call `get_module_manager()`, `get_plugin_manager()` and system-configuration runtimes must call `get_module_manager()`,
`get_scheduler()` explicitly. The class-shaped `ModuleManager` and `Scheduler` `get_plugin_manager()`, `get_scheduler()` and `get_configured_system_config()`
application facades, and concrete plugin manager class paths, remain compatibility explicitly. The class-shaped `ModuleManager` and `Scheduler` application facades,
boundaries for plugins and startup composition; host code must not import those the concrete plugin manager class paths and DB `SystemConfigOper` remain
facades or alias a getter back to a manager class name. compatibility or composition boundaries; host code must not import those facades
or alias a getter back to a manager/Oper class name.
API, Scheduler and Chain deployment values are exposed as frozen snapshots from API, Scheduler and Chain deployment values are exposed as frozen snapshots from
`HostRuntime.configuration`; canonical callers must not add a fresh direct `HostRuntime.configuration`; canonical callers must not add a fresh direct
`settings` import when the required field belongs to an existing snapshot. `settings` import when the required field belongs to an existing snapshot.
+2 -2
View File
@@ -303,7 +303,7 @@ def test_install_plugin_runtime_reloads_in_threadpool() -> None:
with ( with (
patch( patch(
"app.agent.tools.impl._plugin_tool_utils.SystemConfigOper", "app.agent.tools.impl._plugin_tool_utils.get_configured_system_config",
return_value=config_oper, return_value=config_oper,
), ),
patch( patch(
@@ -394,7 +394,7 @@ def test_sealed_agent_uninstall_rejects_before_persistence() -> None:
return_value=plugin_manager, return_value=plugin_manager,
), ),
patch( patch(
"app.agent.tools.impl._plugin_tool_utils.SystemConfigOper", "app.agent.tools.impl._plugin_tool_utils.get_configured_system_config",
return_value=config_oper, return_value=config_oper,
) as config_provider, ) as config_provider,
pytest.raises(PluginMutationRejectedError), pytest.raises(PluginMutationRejectedError),
@@ -1,5 +1,7 @@
"""Agent 资源流程工具权限测试。""" """Agent 资源流程工具权限测试。"""
# pylint: disable=no-name-in-module
import asyncio import asyncio
import json import json
from types import SimpleNamespace from types import SimpleNamespace
@@ -308,7 +310,7 @@ def test_query_downloaders_hides_sensitive_fields_for_non_admin_user():
] ]
with patch( with patch(
"app.agent.tools.impl.query_downloaders.SystemConfigOper" "app.agent.tools.impl.query_downloaders.get_configured_system_config"
) as system_config_oper: ) as system_config_oper:
system_config_oper.return_value.get.return_value = downloaders system_config_oper.return_value.get.return_value = downloaders
result = asyncio.run(tool.run()) result = asyncio.run(tool.run())
@@ -345,7 +347,7 @@ def test_query_downloaders_keeps_full_fields_for_admin_context():
] ]
with patch( with patch(
"app.agent.tools.impl.query_downloaders.SystemConfigOper" "app.agent.tools.impl.query_downloaders.get_configured_system_config"
) as system_config_oper: ) as system_config_oper:
system_config_oper.return_value.get.return_value = downloaders system_config_oper.return_value.get.return_value = downloaders
result = asyncio.run(tool.run()) result = asyncio.run(tool.run())
+7 -7
View File
@@ -35,7 +35,7 @@ class TestAgentSystemSettingsTools(unittest.TestCase):
tool = QuerySystemSettingsTool(session_id="session-1", user_id="10001") tool = QuerySystemSettingsTool(session_id="session-1", user_id="10001")
with patch( with patch(
"app.agent.tools.impl.query_system_settings.SystemConfigOper" "app.agent.tools.impl.query_system_settings.get_configured_system_config"
) as system_config_oper: ) as system_config_oper:
system_config_oper.return_value.get.return_value = [{"name": "qb", "enabled": True}] system_config_oper.return_value.get.return_value = [{"name": "qb", "enabled": True}]
result = asyncio.run(tool.run(setting_key="Downloaders")) result = asyncio.run(tool.run(setting_key="Downloaders"))
@@ -54,7 +54,7 @@ class TestAgentSystemSettingsTools(unittest.TestCase):
tool = QuerySystemSettingsTool(session_id="session-1", user_id="10001") tool = QuerySystemSettingsTool(session_id="session-1", user_id="10001")
with patch( with patch(
"app.agent.tools.impl.query_system_settings.SystemConfigOper" "app.agent.tools.impl.query_system_settings.get_configured_system_config"
) as system_config_oper: ) as system_config_oper:
system_config_oper.return_value.get.return_value = [ system_config_oper.return_value.get.return_value = [
{ {
@@ -84,7 +84,7 @@ class TestAgentSystemSettingsTools(unittest.TestCase):
tool.set_agent_context({"is_admin": True}) tool.set_agent_context({"is_admin": True})
with patch( with patch(
"app.agent.tools.impl.query_system_settings.SystemConfigOper" "app.agent.tools.impl.query_system_settings.get_configured_system_config"
) as system_config_oper: ) as system_config_oper:
system_config_oper.return_value.get.return_value = [ system_config_oper.return_value.get.return_value = [
{"name": "site-a", "apikey": "site-api-key"} {"name": "site-a", "apikey": "site-api-key"}
@@ -156,7 +156,7 @@ class TestAgentSystemSettingsTools(unittest.TestCase):
tool = QuerySystemSettingsTool(session_id="session-1", user_id="10001") tool = QuerySystemSettingsTool(session_id="session-1", user_id="10001")
with patch( with patch(
"app.agent.tools.impl.query_system_settings.SystemConfigOper" "app.agent.tools.impl.query_system_settings.get_configured_system_config"
) as system_config_oper: ) as system_config_oper:
system_config_oper.return_value.get.return_value = [] system_config_oper.return_value.get.return_value = []
result = asyncio.run(tool.run(group="systemconfig")) result = asyncio.run(tool.run(group="systemconfig"))
@@ -231,7 +231,7 @@ class TestAgentSystemSettingsTools(unittest.TestCase):
config_oper.async_set = AsyncMock(return_value=True) config_oper.async_set = AsyncMock(return_value=True)
with patch( with patch(
"app.agent.tools.impl.update_system_settings.SystemConfigOper", "app.agent.tools.impl.update_system_settings.get_configured_system_config",
return_value=config_oper, return_value=config_oper,
), patch( ), patch(
"app.agent.tools.impl.update_system_settings.eventmanager.async_send_event", "app.agent.tools.impl.update_system_settings.eventmanager.async_send_event",
@@ -264,7 +264,7 @@ class TestAgentSystemSettingsTools(unittest.TestCase):
config_oper.async_set = AsyncMock(return_value=True) config_oper.async_set = AsyncMock(return_value=True)
with patch( with patch(
"app.agent.tools.impl.update_system_settings.SystemConfigOper", "app.agent.tools.impl.update_system_settings.get_configured_system_config",
return_value=config_oper, return_value=config_oper,
), patch( ), patch(
"app.agent.tools.impl.update_system_settings.eventmanager.async_send_event", "app.agent.tools.impl.update_system_settings.eventmanager.async_send_event",
@@ -297,7 +297,7 @@ class TestAgentSystemSettingsTools(unittest.TestCase):
config_oper.async_set = AsyncMock(return_value=True) config_oper.async_set = AsyncMock(return_value=True)
with patch( with patch(
"app.agent.tools.impl.update_system_settings.SystemConfigOper", "app.agent.tools.impl.update_system_settings.get_configured_system_config",
return_value=config_oper, return_value=config_oper,
), patch( ), patch(
"app.agent.tools.impl.update_system_settings.eventmanager.async_send_event", "app.agent.tools.impl.update_system_settings.eventmanager.async_send_event",
+22 -1
View File
@@ -368,6 +368,18 @@ def test_host_code_uses_explicit_runtime_facade_getters():
continue continue
tree = ast.parse(path.read_text(encoding="utf-8-sig"), filename=str(path)) tree = ast.parse(path.read_text(encoding="utf-8-sig"), filename=str(path))
for node in ast.walk(tree): for node in ast.walk(tree):
if isinstance(node, ast.Assign) and isinstance(node.value, ast.Name):
target_names = {
target.id for target in node.targets if isinstance(target, ast.Name)
}
if (
"SystemConfigOper" in target_names
and node.value.id == "get_configured_system_config"
):
violations.append(
f"{relative.as_posix()}:{node.lineno}:SystemConfigOper"
)
continue
if not isinstance(node, ast.ImportFrom) or not node.module: if not isinstance(node, ast.ImportFrom) or not node.module:
continue continue
forbidden_names = forbidden_imports.get(node.module, set()) forbidden_names = forbidden_imports.get(node.module, set())
@@ -377,7 +389,16 @@ def test_host_code_uses_explicit_runtime_facade_getters():
and alias.name == "get_plugin_manager" and alias.name == "get_plugin_manager"
and alias.asname is not None and alias.asname is not None
) )
if alias.name in forbidden_names or class_shaped_plugin_getter: class_shaped_config_getter = (
node.module == "app.application.configuration"
and alias.name == "get_configured_system_config"
and alias.asname is not None
)
if (
alias.name in forbidden_names
or class_shaped_plugin_getter
or class_shaped_config_getter
):
imported_name = alias.asname or alias.name imported_name = alias.asname or alias.name
violations.append( violations.append(
f"{relative.as_posix()}:{node.lineno}:{imported_name}" f"{relative.as_posix()}:{node.lineno}:{imported_name}"
+3 -1
View File
@@ -1,4 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# pylint: disable=no-name-in-module
import pytest import pytest
from app.modules.indexer.spider import haidan as haidan_module from app.modules.indexer.spider import haidan as haidan_module
@@ -21,7 +23,7 @@ def _build_indexer() -> dict:
@pytest.fixture() @pytest.fixture()
def haidan_spider(monkeypatch): def haidan_spider(monkeypatch):
"""构造不依赖真实数据库配置的 HaiDanSpider。""" """构造不依赖真实数据库配置的 HaiDanSpider。"""
monkeypatch.setattr(haidan_module, "SystemConfigOper", lambda: None) monkeypatch.setattr(haidan_module, "get_configured_system_config", lambda: None)
return HaiDanSpider(_build_indexer()) return HaiDanSpider(_build_indexer())
+3 -1
View File
@@ -1,4 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# pylint: disable=no-name-in-module
import pytest import pytest
from app.modules.indexer.spider import hddolby as hddolby_module from app.modules.indexer.spider import hddolby as hddolby_module
@@ -21,7 +23,7 @@ def _build_indexer() -> dict:
@pytest.fixture() @pytest.fixture()
def hddolby_spider(monkeypatch): def hddolby_spider(monkeypatch):
"""构造不依赖真实数据库配置的 HddolbySpider。""" """构造不依赖真实数据库配置的 HddolbySpider。"""
monkeypatch.setattr(hddolby_module, "SystemConfigOper", lambda: None) monkeypatch.setattr(hddolby_module, "get_configured_system_config", lambda: None)
return HddolbySpider(_build_indexer()) return HddolbySpider(_build_indexer())
+3 -1
View File
@@ -1,4 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# pylint: disable=no-name-in-module
import pytest import pytest
from app.modules.indexer.spider import mtorrent as mtorrent_module from app.modules.indexer.spider import mtorrent as mtorrent_module
@@ -21,7 +23,7 @@ def _build_indexer() -> dict:
@pytest.fixture() @pytest.fixture()
def mteam_spider(monkeypatch): def mteam_spider(monkeypatch):
"""构造不依赖真实数据库配置的 MTorrentSpider。""" """构造不依赖真实数据库配置的 MTorrentSpider。"""
monkeypatch.setattr(mtorrent_module, "SystemConfigOper", lambda: None) monkeypatch.setattr(mtorrent_module, "get_configured_system_config", lambda: None)
return MTorrentSpider(_build_indexer()) return MTorrentSpider(_build_indexer())
+3 -1
View File
@@ -1,4 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# pylint: disable=no-name-in-module
import pytest import pytest
from app.modules.indexer.spider import rousi as rousi_module from app.modules.indexer.spider import rousi as rousi_module
@@ -21,7 +23,7 @@ def _build_indexer() -> dict:
@pytest.fixture() @pytest.fixture()
def rousi_spider(monkeypatch): def rousi_spider(monkeypatch):
"""构造不依赖真实数据库配置的 RousiSpider。""" """构造不依赖真实数据库配置的 RousiSpider。"""
monkeypatch.setattr(rousi_module, "SystemConfigOper", lambda: None) monkeypatch.setattr(rousi_module, "get_configured_system_config", lambda: None)
return RousiSpider(_build_indexer()) return RousiSpider(_build_indexer())
+1 -1
View File
@@ -1056,7 +1056,7 @@ class SubscribeChainTest(TestCase):
with patch.object(SUBSCRIBE_CHAIN_MODULE, "SubscribeOper", _SubscribeOper), patch.object( with patch.object(SUBSCRIBE_CHAIN_MODULE, "SubscribeOper", _SubscribeOper), patch.object(
SUBSCRIBE_CHAIN_MODULE, SUBSCRIBE_CHAIN_MODULE,
"SystemConfigOper", "get_configured_system_config",
_SystemConfigOper, _SystemConfigOper,
), patch.object( ), patch.object(
SUBSCRIBE_CHAIN_MODULE, SUBSCRIBE_CHAIN_MODULE,