mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-29 03:56:43 +08:00
refactor(api): 渠道与存储管理端点通用化,端点层零目标特色
- schemas 新增公共 ManageRequest(target+action+params) 与 StorageAction 词汇表
- endpoint 层收敛为 POST /notification/manage、/storage/manage 两个通用接口,
不再定义任何渠道/存储特定的名称、参数与响应字段,前端上送参数原样透传
- chain 层 manage_channel/manage_storage 接受字符串标识纯透明转发,
StorageChain 移除全部特色管理方法
- FileManagerModule 新增 storage_manage 统一入口,模块返回归一化为
{success, message, data};wechatclawbot channel_manage 同步归一化,
路由标识兼容枚举名/值/对象
- dashboard 与 agentopsassistant 的用量查询改走 manage_storage(usage)
- 新增 10 项存储契约守护测试,通知侧补充 3 项链透传与字符串路由测试,
API 守护测试对 ManageRequest 开放映射按设计豁免
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
from pathlib import Path
|
||||
from typing import Optional, List, Tuple, Union, Dict, Callable
|
||||
from typing import Any, Optional, List, Tuple, Union, Dict, Callable
|
||||
|
||||
from app.runtime.config import settings
|
||||
from app.domain.context import MediaInfo, MusicInfo
|
||||
@@ -13,8 +13,8 @@ from app.runtime.log import logger
|
||||
from app.modules import _ModuleBase
|
||||
from app.modules.filemanager.storages import StorageBase
|
||||
from app.modules.filemanager.transhandler import TransHandler
|
||||
from app.schemas import TransferInfo, ExistMediaInfo, TmdbEpisode, TransferDirectoryConf, FileItem, StorageUsage
|
||||
from app.schemas.types import MUSIC_ENTITY_ALBUM, MediaType, ModuleType, OtherModulesType
|
||||
from app.schemas import TransferInfo, ExistMediaInfo, TmdbEpisode, TransferDirectoryConf, FileItem
|
||||
from app.schemas.types import MUSIC_ENTITY_ALBUM, MediaType, ModuleType, OtherModulesType, StorageAction
|
||||
from app.adapters.system.host import SystemUtils
|
||||
from app.foundation import text as text_tools
|
||||
|
||||
@@ -122,17 +122,55 @@ class FileManagerModule(_ModuleBase):
|
||||
def init_setting(self) -> Tuple[str, Union[str, bool]]:
|
||||
pass
|
||||
|
||||
def support_transtype(self, storage: str) -> Optional[dict]:
|
||||
def storage_manage(self, storage: str, action: StorageAction, **params) -> Dict[str, Any]:
|
||||
"""
|
||||
支持的整理方式
|
||||
网盘存储统一管理入口,按存储标识路由
|
||||
|
||||
动作语义与参数解释交给具体存储实现,
|
||||
统一返回 {"success": bool, "message": ..., "data": ...}
|
||||
"""
|
||||
try:
|
||||
action = StorageAction(action)
|
||||
except ValueError:
|
||||
return {"success": False, "message": f"不支持的存储管理动作:{action}"}
|
||||
if storage not in self._support_storages:
|
||||
return None
|
||||
storage_oper = self.__get_storage_oper(storage)
|
||||
return {"success": False, "message": f"不支持的存储类型:{storage}"}
|
||||
|
||||
if action == StorageAction.SAVE_CONFIG:
|
||||
storage_oper = self.__get_storage_oper(storage)
|
||||
if not storage_oper:
|
||||
return {"success": False, "message": f"不支持 {storage} 的配置保存"}
|
||||
storage_oper.set_config(params.get("conf") or {})
|
||||
return {"success": True}
|
||||
if action == StorageAction.RESET_CONFIG:
|
||||
storage_oper = self.__get_storage_oper(storage)
|
||||
if not storage_oper:
|
||||
return {"success": False, "message": f"不支持 {storage} 的重置存储配置"}
|
||||
storage_oper.reset_config()
|
||||
return {"success": True}
|
||||
if action == StorageAction.SUPPORT_TRANSTYPE:
|
||||
storage_oper = self.__get_storage_oper(storage)
|
||||
if not storage_oper:
|
||||
return {"success": False, "message": f"不支持 {storage} 的整理方式获取"}
|
||||
transtype = storage_oper.support_transtype()
|
||||
return {"success": bool(transtype), "data": transtype}
|
||||
if action == StorageAction.USAGE:
|
||||
storage_oper = self.__get_storage_oper(storage)
|
||||
if not storage_oper:
|
||||
return {"success": False, "message": f"不支持 {storage} 的存储使用情况"}
|
||||
usage = storage_oper.usage()
|
||||
return {"success": bool(usage), "data": usage}
|
||||
|
||||
# 登录类动作:存储实现不支持时返回失败信息
|
||||
oper_method = action.value
|
||||
storage_oper = self.__get_storage_oper(storage, oper_method)
|
||||
if not storage_oper:
|
||||
logger.error(f"不支持 {storage} 的整理方式获取")
|
||||
return None
|
||||
return storage_oper.support_transtype()
|
||||
return {"success": False, "message": f"{storage} 不支持 {oper_method}"}
|
||||
result = getattr(storage_oper, oper_method)(**params)
|
||||
if result is None:
|
||||
return {"success": False, "message": f"{storage} 的 {oper_method} 执行失败"}
|
||||
data, errmsg = result
|
||||
return {"success": bool(data), "message": errmsg, "data": data}
|
||||
|
||||
@staticmethod
|
||||
def recommend_name(meta: MetaBase, mediainfo: MediaInfo,
|
||||
@@ -157,56 +195,6 @@ class FileManagerModule(_ModuleBase):
|
||||
)
|
||||
return path.as_posix() if path else ""
|
||||
|
||||
def save_config(self, storage: str, conf: Dict) -> None:
|
||||
"""
|
||||
保存存储配置
|
||||
"""
|
||||
storage_oper = self.__get_storage_oper(storage)
|
||||
if not storage_oper:
|
||||
logger.error(f"不支持 {storage} 的配置保存")
|
||||
return
|
||||
storage_oper.set_config(conf)
|
||||
|
||||
def reset_config(self, storage: str) -> None:
|
||||
"""
|
||||
重置存储配置
|
||||
"""
|
||||
storage_oper = self.__get_storage_oper(storage)
|
||||
if not storage_oper:
|
||||
logger.error(f"不支持 {storage} 的重置存储配置")
|
||||
return
|
||||
storage_oper.reset_config()
|
||||
|
||||
def generate_qrcode(self, storage: str) -> Optional[Tuple[dict, str]]:
|
||||
"""
|
||||
生成二维码
|
||||
"""
|
||||
storage_oper = self.__get_storage_oper(storage, "generate_qrcode")
|
||||
if not storage_oper:
|
||||
logger.error(f"不支持 {storage} 的二维码生成")
|
||||
return None
|
||||
return storage_oper.generate_qrcode()
|
||||
|
||||
def generate_auth_url(self, storage: str) -> Optional[Tuple[dict, str]]:
|
||||
"""
|
||||
生成 OAuth2 授权 URL
|
||||
"""
|
||||
storage_oper = self.__get_storage_oper(storage, "generate_auth_url")
|
||||
if not storage_oper:
|
||||
logger.error(f"不支持 {storage} 的 OAuth2 授权")
|
||||
return {}, f"不支持 {storage} 的 OAuth2 授权"
|
||||
return storage_oper.generate_auth_url()
|
||||
|
||||
def check_login(self, storage: str, **kwargs) -> Optional[Dict[str, str]]:
|
||||
"""
|
||||
登录确认
|
||||
"""
|
||||
storage_oper = self.__get_storage_oper(storage, "check_login")
|
||||
if not storage_oper:
|
||||
logger.error(f"不支持 {storage} 的登录确认")
|
||||
return None
|
||||
return storage_oper.check_login(**kwargs)
|
||||
|
||||
def list_files(self, fileitem: FileItem, recursion: Optional[bool] = False) -> Optional[List[FileItem]]:
|
||||
"""
|
||||
浏览文件
|
||||
@@ -397,18 +385,6 @@ class FileManagerModule(_ModuleBase):
|
||||
previous_snapshot=previous_snapshot
|
||||
)
|
||||
|
||||
def storage_usage(self, storage: str) -> Optional[StorageUsage]:
|
||||
"""
|
||||
存储使用情况
|
||||
"""
|
||||
if storage not in self._support_storages:
|
||||
return None
|
||||
storage_oper = self.__get_storage_oper(storage)
|
||||
if not storage_oper:
|
||||
logger.error(f"不支持 {storage} 的存储使用情况")
|
||||
return None
|
||||
return storage_oper.usage()
|
||||
|
||||
def transfer(self, fileitem: FileItem, meta: MetaBase, mediainfo: MediaInfo,
|
||||
target_directory: TransferDirectoryConf = None,
|
||||
target_storage: Optional[str] = None, target_path: Path = None,
|
||||
|
||||
@@ -93,9 +93,12 @@ class WechatClawBotModule(_ModuleBase, _MessageBase[WechatClawBot]):
|
||||
|
||||
动作语义与表单参数全部由模块自行解释:优先使用已保存配置实例,
|
||||
无匹配配置时可基于表单参数构造临时实例(未保存配置的扫码预览)。
|
||||
统一返回 {"success": bool, "message": ..., ...} 结构。
|
||||
统一返回 {"success": bool, "message": ..., "data": ...} 结构。
|
||||
"""
|
||||
if channel != self.get_subtype():
|
||||
# 路由标识归一化:兼容枚举名、枚举值与原始枚举对象
|
||||
if isinstance(channel, str) and channel not in (self.get_subtype().name, self.get_subtype().value):
|
||||
return None
|
||||
if not isinstance(channel, str) and channel != self.get_subtype():
|
||||
return None
|
||||
try:
|
||||
action = NotificationAction(action)
|
||||
@@ -116,14 +119,17 @@ class WechatClawBotModule(_ModuleBase, _MessageBase[WechatClawBot]):
|
||||
return {"success": False, "message": errmsg}
|
||||
|
||||
if action == NotificationAction.STATUS:
|
||||
return client.get_status(
|
||||
data = client.get_status(
|
||||
refresh_remote=bool(params.get("refresh_remote", True)),
|
||||
auto_generate_qrcode=bool(params.get("auto_generate_qrcode", True)),
|
||||
)
|
||||
return {"success": bool(data.get("success")), "message": data.get("message"), "data": data}
|
||||
if action == NotificationAction.REFRESH_QRCODE:
|
||||
return client.refresh_qrcode()
|
||||
data = client.refresh_qrcode()
|
||||
return {"success": bool(data.get("success")), "message": data.get("message"), "data": data}
|
||||
if action == NotificationAction.LOGOUT:
|
||||
return client.logout()
|
||||
data = client.logout()
|
||||
return {"success": bool(data.get("success")), "message": data.get("message"), "data": data}
|
||||
if action == NotificationAction.TEST_CONNECTION:
|
||||
state, message = client.test_connection()
|
||||
return {"success": state, "message": message}
|
||||
|
||||
Reference in New Issue
Block a user