refactor: 推进后端分层架构治理

This commit is contained in:
jxxghp
2026-08-18 00:29:14 +08:00
parent 5d0bacabd5
commit 5128ae9e1e
363 changed files with 34179 additions and 6019 deletions
+1 -1
View File
@@ -21,7 +21,7 @@ from app.runtime.config import settings
from app.application.messaging.agent import matches_channel_admin
from app.runtime.extensions.service_registry import ServiceConfigHelper
from app.runtime.log import logger
from app.schemas import Message
from app.schemas.message import Message
from app.schemas.types import NotificationChannel, MessageType
if TYPE_CHECKING:
+2 -1
View File
@@ -10,7 +10,8 @@ from app.db.oper.systemconfig import SystemConfigOper
from app.application.rules import RuleHelper
from app.application.rules import RuleParser
from app.application.rules import BUILTIN_RULE_SET
from app.schemas import CustomRule, FilterRuleGroup
from app.schemas.rule import CustomRule
from app.schemas.system import FilterRuleGroup
from app.schemas.event import ConfigChangeEventData
from app.schemas.types import EventType, SystemConfigKey
+2 -7
View File
@@ -1,18 +1,13 @@
"""Agent 音乐工具共享的实体校验与结果精简函数。"""
from typing import Any, Optional
from typing import Any
from app.domain.context import (
MusicAlbumInfo,
MusicArtistInfo,
MusicInfo,
)
from app.schemas.types import (
MUSIC_ENTITY_TYPES,
MUSIC_SUBSCRIBABLE_TYPES,
media_type_to_agent,
)
from app.domain.media import normalize_music_type
from app.schemas.types import media_type_to_agent
MUSIC_TRACK_PREVIEW_LIMIT = 100
+79 -32
View File
@@ -6,9 +6,11 @@ from typing import Any, Optional
from app.runtime.config import settings
from app.runtime.extensions.plugin_manager import PluginManager
from app.application.plugin.install import PluginInstallCommand
from app.db.oper.systemconfig import SystemConfigOper
from app.adapters.external.server import MoviePilotServerHelper
from app.adapters.external.market import PluginHelper
from app.adapters.system.plugin.package import PluginPackageManager
from app.schemas.types import SystemConfigKey
# 默认只向智能体返回一个可读预览,避免超大插件数据挤爆上下文窗口。
@@ -65,22 +67,24 @@ def build_preview_payload(value: Any, max_chars: Optional[int]) -> tuple[bool, i
return True, len(serialized), len(preview), preview
def reload_plugin_runtime(plugin_id: str) -> None:
"""
重载插件并重新注册其命令、定时任务和 API。
"""
def refresh_plugin_registrations(plugin_id: str) -> None:
"""重新注册插件的定时任务、命令和动态 API 路由。"""
# 这些依赖只在真正执行重载时才导入,避免普通查询工具引入不必要的初始化开销。
from app.application.plugins import register_plugin_api
from app.application.commands import init_commands
from app.application.scheduling import update_plugin_job
plugin_manager = PluginManager()
plugin_manager.reload_plugin(plugin_id)
update_plugin_job(plugin_id)
init_commands(plugin_id)
register_plugin_api(plugin_id)
def reload_plugin_runtime(plugin_id: str) -> None:
"""重载插件实例并重新注册其命令、定时任务和 API。"""
PluginManager().reload_plugin(plugin_id)
refresh_plugin_registrations(plugin_id)
def summarize_plugin(plugin: Any) -> dict[str, Any]:
"""
提取插件对象中对 Agent 有价值的摘要字段。
@@ -296,37 +300,80 @@ async def install_plugin_runtime(
"""
按现有插件接口的行为安装插件,并刷新运行态注册信息。
"""
install_plugins = SystemConfigOper().get(SystemConfigKey.UserInstalledPlugins) or []
plugin_manager = PluginManager()
plugin_helper = PluginHelper()
refreshed_only = False
if not force and plugin_id in plugin_manager.get_plugin_ids():
refreshed_only = True
await MoviePilotServerHelper.async_install_plugin_reg(plugin_id=plugin_id, repo_url=repo_url)
message = "插件已存在,已刷新加载"
else:
if not repo_url:
return False, "没有传入仓库地址,无法正确安装插件,请检查配置", False
state, message = await plugin_helper.async_install(
pid=plugin_id,
repo_url=repo_url,
force_install=force,
)
if not state:
return False, message, False
await MoviePilotServerHelper.async_install_plugin_reg(plugin_id=plugin_id, repo_url=repo_url)
if plugin_id not in install_plugins:
install_plugins.append(plugin_id)
await SystemConfigOper().async_set(
SystemConfigKey.UserInstalledPlugins, install_plugins
)
package_manager = PluginPackageManager(plugin_helper)
from app.agent.tools.base import run_agent_blocking
await run_agent_blocking("plugin", reload_plugin_runtime, plugin_id)
return True, message or "插件安装成功", refreshed_only
async def save_installed_plugins(plugin_ids: list[str]) -> object:
"""保存智能体安装用例确认后的插件列表。"""
return await SystemConfigOper().async_set(
SystemConfigKey.UserInstalledPlugins,
plugin_ids,
)
async def install_package(
target_id: str,
target_repo: str,
_release_version: Optional[str],
force_install: bool,
) -> tuple[bool, str]:
"""调用插件包适配器执行异步安装。"""
return await package_manager.async_install(
plugin_id=target_id,
repo_url=target_repo,
force_install=force_install,
)
async def skip_compatibility_check(
_target_id: str,
_target_repo: str,
) -> None:
"""保持 Agent 旧安装入口不额外执行系统版本预检查。"""
return None
async def reload_runtime(target_id: str) -> object:
"""通过 Agent 阻塞任务适配器重建插件实例。"""
return await run_agent_blocking(
"plugin",
plugin_manager.reload_plugin,
target_id,
)
async def refresh_registrations(target_id: str) -> object:
"""通过 Agent 阻塞任务适配器刷新服务、命令和动态路由。"""
return await run_agent_blocking(
"plugin",
refresh_plugin_registrations,
target_id,
)
result = await PluginInstallCommand(
installed_plugins_reader=lambda: SystemConfigOper().get(
SystemConfigKey.UserInstalledPlugins
) or [],
installed_plugins_writer=save_installed_plugins,
plugin_ids_provider=plugin_manager.get_plugin_ids,
compatibility_checker=skip_compatibility_check,
package_installer=install_package,
package_checkpointer=package_manager.async_checkpoint,
package_committer=package_manager.async_commit,
package_rollback=package_manager.async_rollback,
install_reporter=lambda target_id, target_repo: (
MoviePilotServerHelper.async_install_plugin_reg(
plugin_id=target_id,
repo_url=target_repo,
)
),
plugin_reloader=reload_runtime,
registration_refresher=refresh_registrations,
).execute(
plugin_id=plugin_id,
repo_url=repo_url,
force=force,
)
return result.success, result.message, result.refreshed_only
async def uninstall_plugin_runtime(plugin_id: str) -> dict[str, Any]:
+1 -1
View File
@@ -18,7 +18,7 @@ from app.domain.metainfo import MetaInfo
from app.db.oper.site import SiteOper
from app.application.directory import DirectoryHelper, validate_download_save_path
from app.runtime.log import logger
from app.schemas import FileURI
from app.schemas.file import FileURI
from app.foundation.crypto import HashUtils
+1 -1
View File
@@ -10,7 +10,7 @@ from app.chain.subscribe import SubscribeChain
from app.db.oper.user import UserOper
from app.runtime.log import logger
from app.schemas.types import MUSIC_ENTITY_ALBUM, MediaSource, MediaType, NotificationChannel
from ._music_utils import normalize_music_type
from app.domain.media import normalize_music_type
class AddSubscribeInput(BaseModel):
+2 -1
View File
@@ -12,7 +12,8 @@ from app.application.messaging.agent import (
build_agent_choice_callback,
)
from app.runtime.log import logger
from app.schemas import Message, MessageType
from app.schemas.message import Message
from app.schemas.message import MessageType
from app.schemas.notification import ChannelCapabilityManager
from app.schemas.types import NotificationChannel
@@ -9,7 +9,7 @@ from app.agent.tools.tags import ToolTag
from app.chain.storage import StorageChain
from app.db.oper.transferhistory import TransferHistoryOper
from app.runtime.log import logger
from app.schemas import FileItem
from app.schemas.workflow import FileItem
class DeleteTransferHistoryInput(BaseModel):
+2 -1
View File
@@ -20,7 +20,8 @@ from app.schemas.types import (
MediaType,
media_type_to_agent,
)
from ._music_utils import normalize_music_type, simplify_music_info
from app.domain.media import normalize_music_type
from ._music_utils import simplify_music_info
class GetRecommendationsInput(BaseModel):
+1 -1
View File
@@ -3,7 +3,7 @@
import json
from typing import Optional, Type
from pydantic import BaseModel, Field
from pydantic import BaseModel
from app.agent.tools.base import MoviePilotTool
from app.agent.tools.tags import ToolTag
@@ -3,7 +3,7 @@
import json
from typing import Optional, Type
from pydantic import BaseModel, Field
from pydantic import BaseModel
from app.agent.tools.base import MoviePilotTool
from app.agent.tools.tags import ToolTag
+1 -1
View File
@@ -10,7 +10,7 @@ from app.agent.tools.tags import ToolTag
from app.chain.download import DownloadChain
from app.db.oper.downloadhistory import DownloadHistoryOper
from app.runtime.log import logger
from app.schemas import DownloaderTorrent
from app.schemas.transfer import DownloaderTorrent
from app.schemas.types import MUSIC_ENTITY_RECORDING, TorrentQueryStatus, media_type_to_agent
+1 -1
View File
@@ -3,7 +3,7 @@
import json
from typing import Optional, Type
from pydantic import BaseModel, Field
from pydantic import BaseModel
from app.agent.tools.base import MoviePilotTool
from app.agent.tools.tags import ToolTag
+1 -1
View File
@@ -18,7 +18,7 @@ from app.schemas.types import (
MediaType,
media_type_to_agent,
)
from ._music_utils import normalize_music_type
from app.domain.media import normalize_music_type
def _sort_seasons(seasons: Optional[dict]) -> dict:
+1 -1
View File
@@ -17,8 +17,8 @@ from app.schemas.types import (
MediaSource,
MediaType,
)
from app.domain.media import normalize_music_type
from ._music_utils import (
normalize_music_type,
simplify_music_album,
simplify_music_artist,
simplify_music_info,
@@ -12,7 +12,7 @@ from app.domain.context import MediaInfo
from app.adapters.external.server import MoviePilotServerHelper
from app.runtime.log import logger
from app.schemas.types import MUSIC_ENTITY_RECORDING, MediaType, media_type_to_agent
from ._music_utils import normalize_music_type
from app.domain.media import normalize_music_type
MAX_PAGE_SIZE = 50
@@ -10,7 +10,7 @@ from app.agent.tools.tags import ToolTag
from app.db.oper.subscribehistory import SubscribeHistoryOper
from app.runtime.log import logger
from app.schemas.types import MUSIC_ENTITY_RECORDING, MediaType, media_type_to_agent
from ._music_utils import normalize_music_type
from app.domain.media import normalize_music_type
PAGE_SIZE = 20
@@ -10,7 +10,7 @@ from app.agent.tools.tags import ToolTag
from app.adapters.external.server import MoviePilotServerHelper
from app.runtime.log import logger
from app.schemas.types import MUSIC_ENTITY_RECORDING, media_type_to_agent
from ._music_utils import normalize_music_type
from app.domain.media import normalize_music_type
MAX_PAGE_SIZE = 50
+1 -1
View File
@@ -16,7 +16,7 @@ from app.schemas.types import (
MediaType,
media_type_to_agent,
)
from ._music_utils import normalize_music_type
from app.domain.media import normalize_music_type
PAGE_SIZE = 100
+3 -2
View File
@@ -12,7 +12,7 @@ from app.chain.media import MediaChain
from app.chain.scraping import ScrapingChain
from app.runtime.config import settings
from app.runtime.log import logger
from app.schemas import FileItem
from app.schemas.workflow import FileItem
from app.schemas.types import (
MUSIC_ENTITY_ARTIST,
MediaSource,
@@ -20,7 +20,8 @@ from app.schemas.types import (
media_type_to_agent,
)
from app.schemas.media import normalize_media_source
from ._music_utils import normalize_music_type, simplify_music_info
from app.domain.media import normalize_music_type
from ._music_utils import simplify_music_info
class ScrapeMetadataInput(BaseModel):
+2 -1
View File
@@ -11,7 +11,8 @@ from app.chain.media import MediaChain
from app.runtime.log import logger
from app.schemas.types import MediaType, media_type_to_agent
from app.schemas.media import resolve_media_identity
from ._music_utils import normalize_music_type, simplify_music_info
from app.domain.media import normalize_music_type
from ._music_utils import simplify_music_info
class SearchMediaInput(BaseModel):
+1 -1
View File
@@ -12,7 +12,7 @@ from app.db.oper.systemconfig import SystemConfigOper
from app.application.site.sites import SitesHelper # pylint: disable=no-name-in-module
from app.runtime.log import logger
from app.schemas.types import MediaSource, MediaType, SystemConfigKey
from ._music_utils import normalize_music_type
from app.domain.media import normalize_music_type
from ._torrent_search_utils import (
SEARCH_RESULT_CACHE_FILE,
build_filter_options,
+2 -1
View File
@@ -8,7 +8,8 @@ from pydantic import BaseModel, Field, model_validator
from app.agent.tools.base import MoviePilotTool
from app.agent.tools.tags import ToolTag
from app.runtime.log import logger
from app.schemas import Message, MessageType
from app.schemas.message import Message
from app.schemas.message import MessageType
from app.schemas.notification import ChannelCapabilityManager, ChannelCapability
from app.schemas.types import NotificationChannel
+1 -1
View File
@@ -7,7 +7,7 @@ from pydantic import BaseModel, Field, model_validator
from app.agent.tools.base import MoviePilotTool
from app.agent.tools.tags import ToolTag
from app.runtime.log import logger
from app.schemas import Message
from app.schemas.message import Message
from app.schemas.types import MessageType
+2 -1
View File
@@ -8,7 +8,8 @@ from app.agent.tools.base import MoviePilotTool
from app.agent.tools.tags import ToolTag
from app.runtime.config import settings
from app.runtime.log import logger
from app.schemas import Message, MessageType
from app.schemas.message import Message
from app.schemas.message import MessageType
class SendVoiceMessageInput(BaseModel):
+1 -1
View File
@@ -1,7 +1,7 @@
"""切换当前激活人格工具。"""
import json
from typing import Type, Optional
from typing import Type
from pydantic import BaseModel, Field
+3 -2
View File
@@ -8,9 +8,10 @@ from pydantic import BaseModel, Field
from app.agent.tools.base import MoviePilotTool
from app.agent.tools.tags import ToolTag
from app.runtime.log import logger
from app.schemas import FileItem, MediaType
from app.schemas.workflow import FileItem
from app.schemas.types import MediaType
from app.schemas.types import MUSIC_ENTITY_ALBUM, MUSIC_ENTITY_RECORDING, MediaSource
from ._music_utils import normalize_music_type
from app.domain.media import normalize_music_type
class TransferFileInput(BaseModel):