From 2ac0e564e1d66f5122629ea8763bddacc2647484 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Sun, 25 Jan 2026 09:39:48 +0800 Subject: [PATCH] =?UTF-8?q?feat(category)=EF=BC=9A=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E4=BA=8C=E7=BA=A7=E5=88=86=E7=B1=BB=E7=BB=B4=E6=8A=A4API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/apiv1.py | 3 +- app/api/endpoints/category.py | 28 +++++++++++++++++++ app/helper/category.py | 52 +++++++++++++++++++++++++++++++++++ app/schemas/category.py | 31 +++++++++++++++++++++ 4 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 app/api/endpoints/category.py create mode 100644 app/helper/category.py create mode 100644 app/schemas/category.py diff --git a/app/api/apiv1.py b/app/api/apiv1.py index b519acb9..9aa64723 100644 --- a/app/api/apiv1.py +++ b/app/api/apiv1.py @@ -2,7 +2,7 @@ from fastapi import APIRouter from app.api.endpoints import login, user, webhook, message, site, subscribe, \ media, douban, search, plugin, tmdb, history, system, download, dashboard, \ - transfer, mediaserver, bangumi, storage, discover, recommend, workflow, torrent, mcp, mfa + transfer, mediaserver, bangumi, storage, discover, recommend, workflow, torrent, mcp, mfa, category api_router = APIRouter() api_router.include_router(login.router, prefix="/login", tags=["login"]) @@ -18,6 +18,7 @@ api_router.include_router(douban.router, prefix="/douban", tags=["douban"]) api_router.include_router(tmdb.router, prefix="/tmdb", tags=["tmdb"]) api_router.include_router(history.router, prefix="/history", tags=["history"]) api_router.include_router(system.router, prefix="/system", tags=["system"]) +api_router.include_router(category.router, prefix="/category", tags=["category"]) api_router.include_router(plugin.router, prefix="/plugin", tags=["plugin"]) api_router.include_router(download.router, prefix="/download", tags=["download"]) api_router.include_router(dashboard.router, prefix="/dashboard", tags=["dashboard"]) diff --git a/app/api/endpoints/category.py b/app/api/endpoints/category.py new file mode 100644 index 00000000..8859a3c7 --- /dev/null +++ b/app/api/endpoints/category.py @@ -0,0 +1,28 @@ +from fastapi import APIRouter, Depends +from app import schemas +from app.db.models import 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 + +router = APIRouter() + + +@router.get("/", summary="获取分类策略配置", response_model=schemas.Response) +def get_category_config(_: User = Depends(get_current_active_user)): + """ + 获取分类策略配置 + """ + config = CategoryHelper().load() + return schemas.Response(success=True, data=config.model_dump()) + + +@router.post("/", summary="保存分类策略配置", response_model=schemas.Response) +def save_category_config(config: CategoryConfig, _: User = Depends(get_current_active_superuser)): + """ + 保存分类策略配置 + """ + if CategoryHelper().save(config): + return schemas.Response(success=True, message="保存成功") + else: + return schemas.Response(success=False, message="保存失败") diff --git a/app/helper/category.py b/app/helper/category.py new file mode 100644 index 00000000..9bf23e18 --- /dev/null +++ b/app/helper/category.py @@ -0,0 +1,52 @@ +import yaml +from app.core.config import settings +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) + return True + except Exception as e: + logger.error(f"Save category config failed: {e}") + return False diff --git a/app/schemas/category.py b/app/schemas/category.py new file mode 100644 index 00000000..454e2380 --- /dev/null +++ b/app/schemas/category.py @@ -0,0 +1,31 @@ +from typing import Dict, Optional + +from pydantic import BaseModel, ConfigDict + + +class CategoryRule(BaseModel): + """ + 分类规则详情 + """ + # 内容类型 + genre_ids: Optional[str] = None + # 语种 + original_language: Optional[str] = None + # 国家或地区(电视剧) + origin_country: Optional[str] = None + # 国家或地区(电影) + production_countries: Optional[str] = None + # 发行年份 + release_year: Optional[str] = None + # 允许接收其他动态字段 + model_config = ConfigDict(extra='allow') + + +class CategoryConfig(BaseModel): + """ + 分类策略配置 + """ + # 电影分类策略 + movie: Optional[Dict[str, Optional[CategoryRule]]] = {} + # 电视剧分类策略 + tv: Optional[Dict[str, Optional[CategoryRule]]] = {}