Implement proper architecture: module->chain->API with single CategoryHelper

Co-authored-by: jxxghp <51039935+jxxghp@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-01-26 04:07:56 +00:00
co-authored by jxxghp
parent 71cde6661d
commit 27756a53db
5 changed files with 80 additions and 63 deletions
+3 -3
View File
@@ -1,8 +1,8 @@
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends
from app import schemas from app import schemas
from app.chain.tmdb import TmdbChain
from app.db.models import User from app.db.models import User
from app.db.user_oper import get_current_active_superuser, get_current_active_user from app.db.user_oper import get_current_active_superuser, get_current_active_user
from app.helper.category import CategoryHelper
from app.schemas.category import CategoryConfig from app.schemas.category import CategoryConfig
router = APIRouter() router = APIRouter()
@@ -13,7 +13,7 @@ def get_category_config(_: User = Depends(get_current_active_user)):
""" """
获取分类策略配置 获取分类策略配置
""" """
config = CategoryHelper().load() config = TmdbChain().load_category_config()
return schemas.Response(success=True, data=config.model_dump()) return schemas.Response(success=True, data=config.model_dump())
@@ -22,7 +22,7 @@ def save_category_config(config: CategoryConfig, _: User = Depends(get_current_a
""" """
保存分类策略配置 保存分类策略配置
""" """
if CategoryHelper().save(config): if TmdbChain().save_category_config(config):
return schemas.Response(success=True, message="保存成功") return schemas.Response(success=True, message="保存成功")
else: else:
return schemas.Response(success=False, message="保存失败") return schemas.Response(success=False, message="保存失败")
+13
View File
@@ -5,6 +5,7 @@ from app import schemas
from app.chain import ChainBase from app.chain import ChainBase
from app.core.context import MediaInfo from app.core.context import MediaInfo
from app.schemas import MediaType from app.schemas import MediaType
from app.schemas.category import CategoryConfig
class TmdbChain(ChainBase): class TmdbChain(ChainBase):
@@ -320,3 +321,15 @@ class TmdbChain(ChainBase):
if infos: if infos:
return [info.backdrop_path for info in infos if info and info.backdrop_path][:num] return [info.backdrop_path for info in infos if info and info.backdrop_path][:num]
return [] return []
def load_category_config(self) -> CategoryConfig:
"""
加载分类策略配置
"""
return self.run_module("load_category_config")
def save_category_config(self, config: CategoryConfig) -> bool:
"""
保存分类策略配置
"""
return self.run_module("save_category_config", config=config)
-58
View File
@@ -1,58 +0,0 @@
import yaml
from app.core.config import settings
from app.core.module import ModuleManager
from app.log import logger
from app.schemas.category import CategoryConfig
HEADER_COMMENTS = """####### 配置说明 #######
# 1. 该配置文件用于配置电影和电视剧的分类策略,配置后程序会按照配置的分类策略名称进行分类,配置文件采用yaml格式,需要严格符合语法规则
# 2. 配置文件中的一级分类名称:`movie`、`tv` 为固定名称不可修改,二级名称同时也是目录名称,会按先后顺序匹配,匹配后程序会按这个名称建立二级目录
# 3. 支持的分类条件:
# `original_language` 语种,具体含义参考下方字典
# `production_countries` 国家或地区(电影)、`origin_country` 国家或地区(电视剧),具体含义参考下方字典
# `genre_ids` 内容类型,具体含义参考下方字典
# `release_year` 发行年份,格式:YYYY,电影实际对应`release_date`字段,电视剧实际对应`first_air_date`字段,支持范围设定,如:`YYYY-YYYY`
# themoviedb 详情API返回的其它一级字段
# 4. 配置多项条件时需要同时满足,一个条件需要匹配多个值是使用`,`分隔
# 5. !条件值表示排除该值
"""
class CategoryHelper:
def __init__(self):
self._config_path = settings.CONFIG_PATH / 'category.yaml'
def load(self) -> CategoryConfig:
"""
加载配置
"""
config = CategoryConfig()
if not self._config_path.exists():
return config
try:
with open(self._config_path, 'r', encoding='utf-8') as f:
data = yaml.safe_load(f)
if data:
config = CategoryConfig(**data)
except Exception as e:
logger.error(f"Load category config failed: {e}")
return config
def save(self, config: CategoryConfig) -> bool:
"""
保存配置
"""
data = config.model_dump(exclude_none=True)
try:
with open(self._config_path, 'w', encoding='utf-8') as f:
f.write(HEADER_COMMENTS)
yaml.dump(data, f, allow_unicode=True, sort_keys=False, default_flow_style=False)
# 触发模块重新加载配置
themoviedb_module = ModuleManager().get_running_module("TheMovieDbModule")
if themoviedb_module and hasattr(themoviedb_module, 'category'):
themoviedb_module.category.init()
logger.info("已触发分类配置重新加载")
return True
except Exception as e:
logger.error(f"Save category config failed: {e}")
return False
+14
View File
@@ -14,10 +14,12 @@ from app.modules.themoviedb.category import CategoryHelper
from app.modules.themoviedb.scraper import TmdbScraper from app.modules.themoviedb.scraper import TmdbScraper
from app.modules.themoviedb.tmdb_cache import TmdbCache from app.modules.themoviedb.tmdb_cache import TmdbCache
from app.modules.themoviedb.tmdbapi import TmdbApi from app.modules.themoviedb.tmdbapi import TmdbApi
from app.schemas.category import CategoryConfig
from app.schemas.types import MediaType, MediaImageType, ModuleType, MediaRecognizeType from app.schemas.types import MediaType, MediaImageType, ModuleType, MediaRecognizeType
from app.utils.http import RequestUtils from app.utils.http import RequestUtils
class TheMovieDbModule(_ModuleBase): class TheMovieDbModule(_ModuleBase):
""" """
TMDB媒体信息匹配 TMDB媒体信息匹配
@@ -1290,3 +1292,15 @@ class TheMovieDbModule(_ModuleBase):
self.tmdb.clear_cache() self.tmdb.clear_cache()
self.cache.clear() self.cache.clear()
logger.info("TMDB缓存清除完成") logger.info("TMDB缓存清除完成")
def load_category_config(self) -> CategoryConfig:
"""
加载分类配置
"""
return self.category.load()
def save_category_config(self, config: CategoryConfig) -> bool:
"""
保存分类配置
"""
return self.category.save(config)
+50 -2
View File
@@ -1,4 +1,5 @@
import shutil import shutil
import yaml
from pathlib import Path from pathlib import Path
from typing import Union from typing import Union
@@ -7,8 +8,23 @@ from ruamel.yaml import CommentedMap
from app.core.config import settings from app.core.config import settings
from app.log import logger from app.log import logger
from app.schemas.category import CategoryConfig
from app.utils.singleton import WeakSingleton from app.utils.singleton import WeakSingleton
HEADER_COMMENTS = """####### 配置说明 #######
# 1. 该配置文件用于配置电影和电视剧的分类策略,配置后程序会按照配置的分类策略名称进行分类,配置文件采用yaml格式,需要严格符合语法规则
# 2. 配置文件中的一级分类名称:`movie`、`tv` 为固定名称不可修改,二级名称同时也是目录名称,会按先后顺序匹配,匹配后程序会按这个名称建立二级目录
# 3. 支持的分类条件:
# `original_language` 语种,具体含义参考下方字典
# `production_countries` 国家或地区(电影)、`origin_country` 国家或地区(电视剧),具体含义参考下方字典
# `genre_ids` 内容类型,具体含义参考下方字典
# `release_year` 发行年份,格式:YYYY,电影实际对应`release_date`字段,电视剧实际对应`first_air_date`字段,支持范围设定,如:`YYYY-YYYY`
# themoviedb 详情API返回的其它一级字段
# 4. 配置多项条件时需要同时满足,一个条件需要匹配多个值是使用`,`分隔
# 5. !条件值表示排除该值
"""
class CategoryHelper(metaclass=WeakSingleton): class CategoryHelper(metaclass=WeakSingleton):
""" """
@@ -31,8 +47,8 @@ class CategoryHelper(metaclass=WeakSingleton):
shutil.copy(settings.INNER_CONFIG_PATH / "category.yaml", self._category_path) shutil.copy(settings.INNER_CONFIG_PATH / "category.yaml", self._category_path)
with open(self._category_path, mode='r', encoding='utf-8') as f: with open(self._category_path, mode='r', encoding='utf-8') as f:
try: try:
yaml = ruamel.yaml.YAML() yaml_loader = ruamel.yaml.YAML()
self._categorys = yaml.load(f) self._categorys = yaml_loader.load(f)
except Exception as e: except Exception as e:
logger.warn(f"二级分类策略配置文件格式出现严重错误!请检查:{str(e)}") logger.warn(f"二级分类策略配置文件格式出现严重错误!请检查:{str(e)}")
self._categorys = {} self._categorys = {}
@@ -44,6 +60,38 @@ class CategoryHelper(metaclass=WeakSingleton):
self._tv_categorys = self._categorys.get('tv') self._tv_categorys = self._categorys.get('tv')
logger.info(f"已加载二级分类策略 category.yaml") logger.info(f"已加载二级分类策略 category.yaml")
def load(self) -> CategoryConfig:
"""
加载配置
"""
config = CategoryConfig()
if not self._category_path.exists():
return config
try:
with open(self._category_path, 'r', encoding='utf-8') as f:
data = yaml.safe_load(f)
if data:
config = CategoryConfig(**data)
except Exception as e:
logger.error(f"Load category config failed: {e}")
return config
def save(self, config: CategoryConfig) -> bool:
"""
保存配置
"""
data = config.model_dump(exclude_none=True)
try:
with open(self._category_path, 'w', encoding='utf-8') as f:
f.write(HEADER_COMMENTS)
yaml.dump(data, f, allow_unicode=True, sort_keys=False, default_flow_style=False)
# 保存后重新加载配置
self.init()
return True
except Exception as e:
logger.error(f"Save category config failed: {e}")
return False
@property @property
def is_movie_category(self) -> bool: def is_movie_category(self) -> bool:
""" """