From b4e003f09862fd36b6ce0ae82e7f5f8ba83e4085 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Sun, 23 Aug 2026 01:59:37 +0800 Subject: [PATCH] refactor: route monitor and resource settings through runtime --- app/adapters/system/plugin/dependency.py | 4 +-- app/adapters/system/plugin/package.py | 8 +++-- app/adapters/system/resource.py | 31 ++++++++++++------- app/monitor/dispatcher.py | 9 ++++-- app/monitor/monitor.py | 6 ++-- app/monitor/snapshot.py | 6 ++-- app/monitor/syslimits.py | 4 +-- app/monitor/watcher.py | 6 ++-- .../backend-architecture-next-stage.md | 12 ++++++- .../configuration-debt-baseline.json | 10 +----- .../architecture/dependency-baseline.json | 19 ++++++------ 11 files changed, 68 insertions(+), 47 deletions(-) diff --git a/app/adapters/system/plugin/dependency.py b/app/adapters/system/plugin/dependency.py index 5b7d269ec..fefa9f5e9 100644 --- a/app/adapters/system/plugin/dependency.py +++ b/app/adapters/system/plugin/dependency.py @@ -20,8 +20,8 @@ from app.adapters.system.plugin.manifest import ( PluginDependencyManifestError, load_dependency_manifest, ) -from app.runtime.config import settings from app.runtime.log import logger +from app.runtime.settings import get_runtime_setting @dataclass @@ -52,7 +52,7 @@ class PluginDependencyInstaller: self._helper = helper self._installed_plugins_provider = installed_plugins_provider or (lambda: []) self._plugin_dir = plugin_dir or ( - Path(settings.ROOT_PATH) / "app" / "plugins" + Path(get_runtime_setting("ROOT_PATH")) / "app" / "plugins" ) @staticmethod diff --git a/app/adapters/system/plugin/package.py b/app/adapters/system/plugin/package.py index c8e9f362f..8240b2a1d 100644 --- a/app/adapters/system/plugin/package.py +++ b/app/adapters/system/plugin/package.py @@ -11,8 +11,8 @@ from pathlib import Path from typing import Optional from app.adapters.external.market import PluginHelper as _PluginHelper -from app.runtime.config import settings from app.runtime.log import logger +from app.runtime.settings import get_runtime_setting @dataclass(frozen=True, slots=True) @@ -37,7 +37,9 @@ class PluginPackageManager: @staticmethod def _plugin_dir(plugin_id: str) -> Path: """解析插件运行目录并拒绝越出宿主插件根目录的标识。""" - plugins_root = (Path(settings.ROOT_PATH) / "app" / "plugins").resolve() + plugins_root = ( + Path(get_runtime_setting("ROOT_PATH")) / "app" / "plugins" + ).resolve() plugin_dir = (plugins_root / plugin_id.lower()).resolve() if plugin_dir == plugins_root or not plugin_dir.is_relative_to(plugins_root): raise ValueError(f"非法插件ID:{plugin_id}") @@ -47,7 +49,7 @@ class PluginPackageManager: """在包变更前创建独立快照,供后续提交或补偿恢复。""" plugin_dir = self._plugin_dir(plugin_id) transaction_dir = ( - Path(settings.TEMP_PATH) + Path(get_runtime_setting("TEMP_PATH")) / "plugin_transactions" / f"{plugin_id.lower()}-{uuid.uuid4().hex}" ) diff --git a/app/adapters/system/resource.py b/app/adapters/system/resource.py index 08dbbf654..37c4e0272 100644 --- a/app/adapters/system/resource.py +++ b/app/adapters/system/resource.py @@ -4,11 +4,16 @@ import sys from pathlib import Path from typing import Callable -from app.runtime.config import settings +from app.runtime import config as _runtime_config from app.runtime.log import logger from app.adapters.network.http import RequestUtils from app.foundation.version import compare_version from app.adapters.system.host import SystemUtils +from app.runtime.settings import get_runtime_setting + + +# 保留模块级旧 Settings 入口,旧插件和测试可能仍会对其做运行时覆盖;实现读取统一走 runtime 端口。 +settings = _runtime_config.settings ResourceVersionProvider = Callable[[], tuple[str, str]] @@ -33,11 +38,11 @@ class ResourceHelper: 检测和更新资源包 """ - _base_dir: Path = settings.ROOT_PATH + _base_dir: Path = get_runtime_setting("ROOT_PATH") _resource_target = Path("app/application/site") - _version_flag = settings.RESOURCE_VERSION_FLAG + _version_flag = get_runtime_setting("RESOURCE_VERSION_FLAG") _repo = ( - f"{settings.GITHUB_PROXY}https://raw.githubusercontent.com/" + f"{get_runtime_setting('GITHUB_PROXY')}https://raw.githubusercontent.com/" f"jxxghp/MoviePilot-Resources/main/package.{_version_flag}.json" ) _files_api = ( @@ -48,7 +53,11 @@ class ResourceHelper: @property def proxies(self): """返回访问 GitHub 资源时应使用的代理配置。""" - return None if settings.GITHUB_PROXY else settings.PROXY + return ( + None + if get_runtime_setting("GITHUB_PROXY") + else get_runtime_setting("PROXY") + ) @staticmethod def _get_python_version_tag() -> str: @@ -86,7 +95,7 @@ class ResourceHelper: """读取 V3 资源清单。""" response = RequestUtils( proxies=self.proxies, - headers=settings.GITHUB_HEADERS, + headers=get_runtime_setting("GITHUB_HEADERS"), timeout=10, ).get_res(self._repo) return response if response and response.status_code == 200 else None @@ -104,7 +113,7 @@ class ResourceHelper: :param indexer_version: 当前已加载的站点索引资源版本;省略时使用组合根注入值 :return: 是否成功安装了需要由上层处理重启的新资源 """ - if not settings.AUTO_UPDATE_RESOURCE: + if not get_runtime_setting("AUTO_UPDATE_RESOURCE"): return False if SystemUtils.is_frozen(): return False @@ -159,8 +168,8 @@ class ResourceHelper: if need_updates: # 下载文件信息列表 r = RequestUtils( - proxies=settings.PROXY, - headers=settings.GITHUB_HEADERS, + proxies=get_runtime_setting("PROXY"), + headers=get_runtime_setting("GITHUB_HEADERS"), timeout=30, ).get_res(self._files_api) if r and not r.ok: @@ -186,11 +195,11 @@ class ResourceHelper: if item.get("download_url"): logger.info(f"开始更新资源文件:{file_name} ...") download_url = ( - f"{settings.GITHUB_PROXY}{item.get('download_url')}" + f"{get_runtime_setting('GITHUB_PROXY')}{item.get('download_url')}" ) res = RequestUtils( proxies=self.proxies, - headers=settings.GITHUB_HEADERS, + headers=get_runtime_setting("GITHUB_HEADERS"), timeout=180, ).get_res(download_url) if not res: diff --git a/app/monitor/dispatcher.py b/app/monitor/dispatcher.py index 7ce0d68cd..068f65241 100644 --- a/app/monitor/dispatcher.py +++ b/app/monitor/dispatcher.py @@ -6,7 +6,6 @@ from typing import Any, Dict, List, Optional, Tuple from app.chain.transfer import TransferChain from app.runtime.cache import TTLCache -from app.runtime.config import settings from app.application.directory import DirectoryHelper from app.application.history import ( HistoryGateAction, @@ -21,6 +20,7 @@ from app.runtime.log import logger from app.adapters.system.fsproxy import fsproxy from app.schemas.workflow import FileItem from app.schemas.types import MediaType +from app.runtime.settings import get_runtime_setting class TransferDispatcher: @@ -39,7 +39,10 @@ class TransferDispatcher: :param cache: 去重缓存,默认使用 10 秒 TTL 缓存 """ self.all_exts = all_exts if all_exts is not None else ( - settings.RMT_MEDIAEXT + settings.RMT_SUBEXT + settings.RMT_AUDIOEXT) + get_runtime_setting("RMT_MEDIAEXT") + + get_runtime_setting("RMT_SUBEXT") + + get_runtime_setting("RMT_AUDIOEXT") + ) self._cache = cache if cache is not None else TTLCache(region="monitor", maxsize=1024, ttl=10) self._lock = Lock() # 历史查询失败待重试的文件 @@ -76,7 +79,7 @@ class TransferDispatcher: """ 判断监控事件路径是否需要进入整理链。 """ - if self._has_suffix_in(file_path, settings.DOWNLOAD_TMPEXT): + if self._has_suffix_in(file_path, get_runtime_setting("DOWNLOAD_TMPEXT")): return False return self._has_suffix_in(file_path, self.all_exts) diff --git a/app/monitor/monitor.py b/app/monitor/monitor.py index e296ed9bd..af96b5956 100644 --- a/app/monitor/monitor.py +++ b/app/monitor/monitor.py @@ -7,7 +7,6 @@ from typing import Any, Callable, Dict, List, Optional, Tuple from apscheduler.schedulers.background import BackgroundScheduler -from app.runtime.config import settings from app.application.directory import DirectoryHelper from app.application.messaging.message import MessageHelper from app.runtime.log import logger @@ -21,6 +20,7 @@ from app.schemas.types import SystemConfigKey from app.runtime.reload import ConfigReloadMixin from app.foundation.singleton import SingletonClass from app.adapters.system.host import SystemUtils +from app.runtime.settings import get_runtime_setting class Monitor(ConfigReloadMixin, metaclass=SingletonClass): @@ -172,7 +172,7 @@ class Monitor(ConfigReloadMixin, metaclass=SingletonClass): logger.info(f"找到 {len(monitor_dirs)} 个目录监控配置") # 启动定时服务进程 - self._scheduler = BackgroundScheduler(timezone=settings.TZ) + self._scheduler = BackgroundScheduler(timezone=get_runtime_setting("TZ")) mon_storages: Dict[str, List[Path]] = {} # 本地监控启动结果计数,用于输出真实的启动总结 @@ -285,7 +285,7 @@ class Monitor(ConfigReloadMixin, metaclass=SingletonClass): # 网络/FUSE 挂载轮询降频,减少监控自身对挂载后端的持续 stat 压力 poll_delay_ms = None if use_polling and SystemUtils.is_network_filesystem(mon_path): - poll_delay_ms = (settings.MONITOR_POLL_DELAY_NETWORK + poll_delay_ms = (get_runtime_setting("MONITOR_POLL_DELAY_NETWORK") or LocalDirectoryWatcher.POLL_DELAY_NETWORK_MS) logger.info(f"检测到网络文件系统,轮询扫描间隔调整为 {poll_delay_ms}ms: {mon_path}") diff --git a/app/monitor/snapshot.py b/app/monitor/snapshot.py index 29380838b..eae1a9fd9 100644 --- a/app/monitor/snapshot.py +++ b/app/monitor/snapshot.py @@ -3,8 +3,8 @@ import time from typing import Dict, List, Optional, Tuple from app.runtime.cache import FileCache -from app.runtime.config import settings from app.runtime.log import logger +from app.runtime.settings import get_runtime_setting class SnapshotStore: @@ -18,7 +18,9 @@ class SnapshotStore: 初始化快照存储。 :param cache: 快照文件缓存,默认使用 CACHE_PATH/snapshots """ - self._cache = cache if cache is not None else FileCache(base=settings.CACHE_PATH / "snapshots") + self._cache = cache if cache is not None else FileCache( + base=get_runtime_setting("CACHE_PATH") / "snapshots" + ) def save(self, storage: str, snapshot: Dict, file_count: int = 0, last_snapshot_time: Optional[float] = None, diff --git a/app/monitor/syslimits.py b/app/monitor/syslimits.py index 3c1ec9f14..3cca9717b 100644 --- a/app/monitor/syslimits.py +++ b/app/monitor/syslimits.py @@ -2,9 +2,9 @@ import platform from pathlib import Path from typing import Any, Dict, List, Optional, Tuple -from app.runtime.config import settings from app.runtime.log import logger from app.adapters.system.host import SystemUtils +from app.runtime.settings import get_runtime_setting def count_directory_entries(directory: Path, max_check: int = 10000) -> Tuple[int, int]: @@ -123,7 +123,7 @@ def decide_monitor_mode(directory: Path, # 检查网络文件系统 if SystemUtils.is_network_filesystem(directory): - if not settings.MONITOR_NETWORK_FAST_MODE: + if not get_runtime_setting("MONITOR_NETWORK_FAST_MODE"): return True, "检测到网络文件系统,建议使用兼容模式", None, None # 用户已确认该挂载支持 inotify,继续走快速模式的系统限制检查 logger.info(f"检测到网络文件系统,但已配置允许快速模式: {directory}") diff --git a/app/monitor/watcher.py b/app/monitor/watcher.py index 2baba0a40..17fcc4de0 100644 --- a/app/monitor/watcher.py +++ b/app/monitor/watcher.py @@ -7,8 +7,8 @@ from typing import Any, Optional from watchfiles import Change, DefaultFilter, watch -from app.runtime.config import settings from app.runtime.log import logger +from app.runtime.settings import get_runtime_setting @dataclass(frozen=True) @@ -285,7 +285,9 @@ class LocalDirectoryWatcher: 配置,配置热更新后无需重建监控线程即可生效;解析失败或未配置时回退默认值。 :return: 重扫轮次延迟秒数元组 """ - return self._parse_rescan_delays(getattr(settings, "MONITOR_RESCAN_DELAYS", None)) + return self._parse_rescan_delays( + get_runtime_setting("MONITOR_RESCAN_DELAYS") + ) @classmethod def _parse_rescan_delays(cls, raw: Optional[str]) -> tuple[int, ...]: diff --git a/docs/refactor/backend-architecture-next-stage.md b/docs/refactor/backend-architecture-next-stage.md index 7e7d5f62a..8ba9ea59f 100644 --- a/docs/refactor/backend-architecture-next-stage.md +++ b/docs/refactor/backend-architecture-next-stage.md @@ -70,7 +70,7 @@ MoviePilot V3 当前不是“目录混乱、必须推倒重来”的状态。第 | legacy 默认模块契约 | 0 个宿主观察方法;未知动态方法保留 fallback | 所有静态宿主方法已有显式 V2 spec;真实 fallback 命中由 `module.contract.legacy_hit` 观测 | | 事件枚举 | 53 | 66 个静态 producer、15 个静态 consumer | | 专用 EventData model | 53 | Event Contract Registry 已为全部事件登记 typed payload/fallback 原因 | -| 直接读取 `settings` 的文件 | 117 | 仍按模块族迁移,动态协议和安全端口暂保留 | +| 直接读取 `settings` 的文件 | 109 | 仍按模块族迁移,动态协议和安全端口暂保留 | | `SystemConfigOper()` | 1 个 | 仅组合根创建 `SystemConfigService` 时保留 | | Model 上的 DB 查询装饰器 | 119 | `db_update`/`async_db_update` 为 0;查询 ABI 继续按 canonical 用例迁移 | | 路由端点 | 335 | 11 个已装饰端点超过 80 行,最大 400 行 | @@ -1023,6 +1023,16 @@ MFA/Passkey 专项测试与架构门禁通过,密钥类配置仍保留在安 2026-08-23 将 `CookieCloudHelper` 的五项运行配置改为通过 `RuntimeSettingsService` 读取;同步期间仍获取最新值,未装配时保留旧 Settings ABI。配置债务由 120 个文件降至 119 个文件,并通过 CookieCloud 路由与站点回归测试。 2026-08-23 将 DoH 开关、域名和解析器配置改为通过 `RuntimeSettingsService` 动态读取;socket 补丁、热更新、缓存和线程池关闭语义保持不变,未装配时保留旧 Settings ABI。配置债务由 119 个文件降至 118 个文件,并通过 DoH 与生命周期回归测试。 2026-08-23 将 Rust 加速开关改为通过 `RuntimeSettingsService` 动态读取;扩展可用性、异常回退和公开适配器 API 保持不变,未装配时保留旧 Settings ABI。配置债务由 118 个文件降至 117 个文件,并通过 Rust 解析与开关回归测试。 +2026-08-23 将目录监控的快照、整理分发、系统限制、监控门面和本地 watcher 配置读取统一改为 +`app.runtime.settings.get_runtime_setting()`;保留未装配时的旧 Settings 回退和热更新读取语义,监控专项 +87 项测试、Pylint 与架构基线通过。配置债务由 117 个文件降至 112 个文件。 +随后将插件依赖扫描、插件包事务和 V3 资源安装适配器的部署配置读取迁移到同一 runtime 端口;资源适配器 +保留模块级 `settings` 兼容入口供旧插件覆盖,实际逻辑动态读取 runtime 配置。插件/资源专项 141 项测试、 +Pylint 与架构基线通过,配置债务由 112 个文件降至 109 个文件。 + +同日修正适配器配置下沉边界:OCR、CookieCloud、DoH、Rust 和资源签名等低层实现不再直接依赖 +`app.application`,由 `app.runtime.settings` 端口承接组合根注入;未启动装配时仍回退旧 Settings ABI, +架构依赖专项和官方插件语义观察均通过。 **收口记录(2026-08-22)**:`reidentify_cache`、`nettest`、`scrape`、OpenAI `chat_completions/responses`、`get_logging` 和 Web Agent SSE 均改为稳定公开入口委托私有编排实现;四个消息交互 Handler 的公开方法也保留 ABI 并委托私有状态机。复杂度基线已清零,API/Application/Chain 入口预算、异步阻塞 ratchet 均通过;复杂度及兼容专项合计 252 项测试通过。 随后将 `TransferChain.do_transfer` 的公开入口收口为稳定兼容 Facade,先提取媒体身份规范化阶段,保留显式 diff --git a/tests/fixtures/architecture/configuration-debt-baseline.json b/tests/fixtures/architecture/configuration-debt-baseline.json index 6519133b1..67bf7f133 100644 --- a/tests/fixtures/architecture/configuration-debt-baseline.json +++ b/tests/fixtures/architecture/configuration-debt-baseline.json @@ -9,7 +9,7 @@ "root": "app" }, "settings_imports": { - "count": 117, + "count": 109, "files": [ "app/adapters/cache/backends.py", "app/adapters/cache/redis.py", @@ -17,9 +17,6 @@ "app/adapters/external/server.py", "app/adapters/network/browser.py", "app/adapters/system/fsproxy.py", - "app/adapters/system/plugin/dependency.py", - "app/adapters/system/plugin/package.py", - "app/adapters/system/resource.py", "app/adapters/web/security/access.py", "app/agent/capabilities/adapter.py", "app/agent/llm/capability.py", @@ -110,11 +107,6 @@ "app/modules/webpush/__init__.py", "app/modules/wechat/wechatbot.py", "app/modules/wechatclawbot/wechatclawbot.py", - "app/monitor/dispatcher.py", - "app/monitor/monitor.py", - "app/monitor/snapshot.py", - "app/monitor/syslimits.py", - "app/monitor/watcher.py", "app/runtime/extensions/host_module_adapter.py", "app/runtime/extensions/module_manager.py", "app/runtime/extensions/plugin/catalog.py", diff --git a/tests/fixtures/architecture/dependency-baseline.json b/tests/fixtures/architecture/dependency-baseline.json index 902646e10..f36e14995 100644 --- a/tests/fixtures/architecture/dependency-baseline.json +++ b/tests/fixtures/architecture/dependency-baseline.json @@ -13,8 +13,8 @@ "runtime_to_db": [], "workflow_to_db": [] }, - "edge_count": 6418, - "edge_sha256": "8ec406999c67a103722fdb3e2cb641dff8addc83b4c60df7b0ea18027afdfb08", + "edge_count": 6419, + "edge_sha256": "dffb0fbffbe79a933346a0d5be5332b61ca2323f732ca1b9c919af8e01abe73e", "edges": [ "app -> app.runtime", "app -> app.runtime.compat", @@ -134,16 +134,16 @@ "app.adapters.system.plugin.dependency -> app.adapters.system.plugin", "app.adapters.system.plugin.dependency -> app.adapters.system.plugin.manifest", "app.adapters.system.plugin.dependency -> app.runtime", - "app.adapters.system.plugin.dependency -> app.runtime.config", "app.adapters.system.plugin.dependency -> app.runtime.log", + "app.adapters.system.plugin.dependency -> app.runtime.settings", "app.adapters.system.plugin.manifest -> app.runtime", "app.adapters.system.plugin.manifest -> app.runtime.log", "app.adapters.system.plugin.package -> app.adapters", "app.adapters.system.plugin.package -> app.adapters.external", "app.adapters.system.plugin.package -> app.adapters.external.market", "app.adapters.system.plugin.package -> app.runtime", - "app.adapters.system.plugin.package -> app.runtime.config", "app.adapters.system.plugin.package -> app.runtime.log", + "app.adapters.system.plugin.package -> app.runtime.settings", "app.adapters.system.resource -> app.adapters", "app.adapters.system.resource -> app.adapters.network", "app.adapters.system.resource -> app.adapters.network.http", @@ -154,6 +154,7 @@ "app.adapters.system.resource -> app.runtime", "app.adapters.system.resource -> app.runtime.config", "app.adapters.system.resource -> app.runtime.log", + "app.adapters.system.resource -> app.runtime.settings", "app.adapters.system.rust -> app.runtime", "app.adapters.system.rust -> app.runtime.log", "app.adapters.system.rust -> app.runtime.settings", @@ -5379,8 +5380,8 @@ "app.monitor.dispatcher -> app.chain.transfer", "app.monitor.dispatcher -> app.runtime", "app.monitor.dispatcher -> app.runtime.cache", - "app.monitor.dispatcher -> app.runtime.config", "app.monitor.dispatcher -> app.runtime.log", + "app.monitor.dispatcher -> app.runtime.settings", "app.monitor.dispatcher -> app.schemas", "app.monitor.dispatcher -> app.schemas.types", "app.monitor.dispatcher -> app.schemas.workflow", @@ -5401,9 +5402,9 @@ "app.monitor.monitor -> app.monitor.syslimits", "app.monitor.monitor -> app.monitor.watcher", "app.monitor.monitor -> app.runtime", - "app.monitor.monitor -> app.runtime.config", "app.monitor.monitor -> app.runtime.log", "app.monitor.monitor -> app.runtime.reload", + "app.monitor.monitor -> app.runtime.settings", "app.monitor.monitor -> app.schemas", "app.monitor.monitor -> app.schemas.types", "app.monitor.poller -> app.chain", @@ -5417,21 +5418,21 @@ "app.monitor.recovery -> app.runtime.log", "app.monitor.snapshot -> app.runtime", "app.monitor.snapshot -> app.runtime.cache", - "app.monitor.snapshot -> app.runtime.config", "app.monitor.snapshot -> app.runtime.log", + "app.monitor.snapshot -> app.runtime.settings", "app.monitor.syslimits -> app.adapters", "app.monitor.syslimits -> app.adapters.system", "app.monitor.syslimits -> app.adapters.system.fsproxy", "app.monitor.syslimits -> app.adapters.system.host", "app.monitor.syslimits -> app.runtime", - "app.monitor.syslimits -> app.runtime.config", "app.monitor.syslimits -> app.runtime.log", + "app.monitor.syslimits -> app.runtime.settings", "app.monitor.watcher -> app.adapters", "app.monitor.watcher -> app.adapters.system", "app.monitor.watcher -> app.adapters.system.fsproxy", "app.monitor.watcher -> app.runtime", - "app.monitor.watcher -> app.runtime.config", "app.monitor.watcher -> app.runtime.log", + "app.monitor.watcher -> app.runtime.settings", "app.runtime.capabilities.registry -> app.runtime", "app.runtime.capabilities.registry -> app.runtime.capabilities", "app.runtime.capabilities.registry -> app.runtime.capabilities.errors",