refactor: 收口 V3 分层架构与插件兼容边界

This commit is contained in:
jxxghp
2026-08-18 13:22:02 +08:00
parent cca99bd421
commit 8472bcff43
274 changed files with 10730 additions and 6130 deletions
+80 -93
View File
@@ -1,8 +1,6 @@
from typing import List, Any, Dict, Optional
from fastapi import Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import Session
from starlette.background import BackgroundTasks
from app.schemas.common import JsonObject as _SchemaJsonObject
@@ -19,32 +17,28 @@ from app.schemas.token import TokenPayload as _SchemaTokenPayload
from app.schemas.workflow import Site as _SchemaSite
from app.api.response import ResponseAPIRouter
from app.application.site.mutation import SiteMutationCommand
from app.application.site.query import SiteQueryService
from app.api.endpoints.plugin import register_plugin_api
from app.chain.site import SiteChain
from app.chain.torrents import TorrentsChain
from app.command import Command
from app.runtime.events import eventmanager
from app.runtime.extensions.plugin_manager import PluginManager
from app.application.security.access import verify_token
from app.db import get_db, get_async_db
from app.db.models import User
from app.db.models.site import Site
from app.db.models.siteicon import SiteIcon
from app.db.models.sitestatistic import SiteStatistic
from app.db.models.siteuserdata import SiteUserData
from app.db.oper.site import SiteOper
from app.db.oper.systemconfig import SystemConfigOper
from app.application.plugin.runtime import get_plugin_manager as PluginManager
from app.adapters.web.security.access import verify_token
from app.api.principal import ApiPrincipal
from app.application.configuration import get_configured_system_config
from app.api.deps import (
get_current_active_manage_user,
get_current_active_manage_user_async,
get_current_active_superuser,
get_current_active_superuser_async,
get_site_mutation_command,
get_site_query_service,
get_site_sync_query_service,
)
from app.application.site.sites import SitesHelper # pylint: disable=no-name-in-module
from app.runtime.log import logger
from app.scheduler import Scheduler
from app.schemas.types import SystemConfigKey, EventType, MediaType
from app.application.scheduling import Scheduler
from app.schemas.types import SystemConfigKey, MediaType
from app.domain import site as site_rules
router = ResponseAPIRouter()
@@ -88,13 +82,13 @@ def _indexer_supports_media_type(indexer: dict, media_type: MediaType) -> bool:
@router.get("/", summary="所有站点", response_model=List[_SchemaSite])
async def read_sites(
db: AsyncSession = Depends(get_async_db),
_: User = Depends(get_current_active_manage_user_async),
query: SiteQueryService = Depends(get_site_query_service),
_: ApiPrincipal = Depends(get_current_active_manage_user_async),
) -> List[dict]:
"""
获取站点列表
"""
return await Site.async_list_order_by_pri(db)
return await query.list_ordered()
@router.get(
@@ -104,14 +98,14 @@ async def read_sites(
)
async def read_sites_by_media_type(
media_type: str,
db: AsyncSession = Depends(get_async_db),
_: User = Depends(get_current_active_manage_user_async),
) -> List[Site]:
query: SiteQueryService = Depends(get_site_query_service),
_: ApiPrincipal = Depends(get_current_active_manage_user_async),
) -> List[_SchemaSite]:
"""
获取支持指定媒体类型的已配置启用站点。
:param media_type: Agent 媒体类型名称或中文媒体类型
:param db: 异步数据库会话
:param query: 站点查询服务
:return: 按优先级排序的可搜索站点
"""
target_media_type = MediaType.from_agent(media_type)
@@ -134,7 +128,7 @@ async def read_sites_by_media_type(
if domain:
supported_domains.add(domain)
sites = await Site.async_list_order_by_pri(db)
sites = await query.list_ordered()
return [
site
for site in sites
@@ -148,7 +142,7 @@ async def add_site(
*,
site_in: _SchemaSite,
command: SiteMutationCommand = Depends(get_site_mutation_command),
_: User = Depends(get_current_active_manage_user_async),
_: ApiPrincipal = Depends(get_current_active_manage_user_async),
) -> Any:
"""
新增站点
@@ -162,7 +156,7 @@ async def update_site(
*,
site_in: _SchemaSite,
command: SiteMutationCommand = Depends(get_site_mutation_command),
_: User = Depends(get_current_active_manage_user_async),
_: ApiPrincipal = Depends(get_current_active_manage_user_async),
) -> Any:
"""
更新站点信息
@@ -174,7 +168,7 @@ async def update_site(
@router.get("/cookiecloud", summary="CookieCloud同步", response_model=_SchemaResponse[None])
async def cookie_cloud_sync(
background_tasks: BackgroundTasks,
_: User = Depends(get_current_active_superuser_async),
_: ApiPrincipal = Depends(get_current_active_superuser_async),
) -> Any:
"""
运行CookieCloud同步站点信息
@@ -184,20 +178,20 @@ async def cookie_cloud_sync(
@router.get("/reset", summary="重置站点", response_model=_SchemaResponse[None])
def reset(
db: AsyncSession = Depends(get_db), _: User = Depends(get_current_active_superuser)
async def reset(
command: SiteMutationCommand = Depends(get_site_mutation_command),
_: ApiPrincipal = Depends(get_current_active_superuser_async),
) -> Any:
"""
清空所有站点数据并重新同步CookieCloud站点信息
"""
Site.reset(db)
SystemConfigOper().set(SystemConfigKey.IndexerSites, [])
SystemConfigOper().set(SystemConfigKey.RssSites, [])
result = await command.reset()
get_configured_system_config().set(SystemConfigKey.IndexerSites, [])
get_configured_system_config().set(SystemConfigKey.RssSites, [])
# 启动定时服务
Scheduler().start("cookiecloud", manual=True)
# 插件站点删除
eventmanager.send_event(EventType.SiteDeleted, {"site_id": "*"})
return _SchemaResponse(success=True, message="站点已重置!")
return _SchemaResponse(success=result.success, message="站点已重置!")
@router.post(
@@ -206,7 +200,7 @@ def reset(
async def update_sites_priority(
priorities: List[dict],
command: SiteMutationCommand = Depends(get_site_mutation_command),
_: User = Depends(get_current_active_manage_user_async),
_: ApiPrincipal = Depends(get_current_active_manage_user_async),
) -> Any:
"""
批量更新站点优先级
@@ -220,7 +214,7 @@ def _update_site_cookie(
username: str,
password: str,
code: Optional[str],
db: Session,
query: SiteQueryService,
) -> _SchemaResponse:
"""
执行站点 Cookie 与 UA 更新。
@@ -229,10 +223,10 @@ def _update_site_cookie(
:param username: 站点登录用户名
:param password: 站点登录密码
:param code: 二步验证码或密钥
:param db: 数据库会话
:param query: 站点查询服务
:return: 更新结果
"""
site_info = Site.get(db, site_id)
site_info = query.get_sync(site_id)
if not site_info:
raise HTTPException(
status_code=404,
@@ -255,8 +249,8 @@ def _update_site_cookie(
def update_cookie_by_body(
site_id: int,
site_cookie_update: _SchemaSiteCookieUpdate,
db: Session = Depends(get_db),
_: User = Depends(get_current_active_manage_user),
query: SiteQueryService = Depends(get_site_sync_query_service),
_: ApiPrincipal = Depends(get_current_active_manage_user),
) -> Any:
"""
使用请求体中的用户密码更新站点Cookie
@@ -266,7 +260,7 @@ def update_cookie_by_body(
username=site_cookie_update.username,
password=site_cookie_update.password,
code=site_cookie_update.code,
db=db,
query=query,
)
@@ -278,8 +272,8 @@ def update_cookie(
username: str,
password: str,
code: Optional[str] = None,
db: Session = Depends(get_db),
_: User = Depends(get_current_active_manage_user),
query: SiteQueryService = Depends(get_site_sync_query_service),
_: ApiPrincipal = Depends(get_current_active_manage_user),
) -> Any:
"""
使用用户密码更新站点Cookie
@@ -289,7 +283,7 @@ def update_cookie(
username=username,
password=password,
code=code,
db=db,
query=query,
)
@@ -300,13 +294,13 @@ def update_cookie(
)
def refresh_userdata(
site_id: int,
db: Session = Depends(get_db),
_: User = Depends(get_current_active_manage_user),
query: SiteQueryService = Depends(get_site_sync_query_service),
_: ApiPrincipal = Depends(get_current_active_manage_user),
) -> Any:
"""
刷新站点用户数据
"""
site = Site.get(db, site_id)
site = query.get_sync(site_id)
if not site:
raise HTTPException(
status_code=404,
@@ -327,16 +321,13 @@ def refresh_userdata(
response_model=List[_SchemaSiteUserData],
)
async def read_userdata_latest(
db: AsyncSession = Depends(get_async_db),
_: User = Depends(get_current_active_manage_user_async),
query: SiteQueryService = Depends(get_site_query_service),
_: ApiPrincipal = Depends(get_current_active_manage_user_async),
) -> Any:
"""
查询所有站点最新用户数据
"""
user_datas = await SiteUserData.async_get_latest(db)
if not user_datas:
return []
return [user_data.to_dict() for user_data in user_datas]
return await query.userdata_latest()
@router.get(
@@ -347,36 +338,34 @@ async def read_userdata_latest(
async def read_userdata(
site_id: int,
workdate: Optional[str] = None,
db: AsyncSession = Depends(get_async_db),
_: User = Depends(get_current_active_manage_user_async),
query: SiteQueryService = Depends(get_site_query_service),
_: ApiPrincipal = Depends(get_current_active_manage_user_async),
) -> Any:
"""
查询站点用户数据
"""
site = await Site.async_get(db, site_id)
site = await query.get(site_id)
if not site:
raise HTTPException(
status_code=404,
detail=f"站点 {site_id} 不存在",
)
user_datas = await SiteUserData.async_get_by_domain(
db, domain=site.domain, workdate=workdate
)
user_datas = await query.userdata(site.domain, workdate)
if not user_datas:
return _SchemaResponse(success=False, data=[])
return _SchemaResponse(success=True, data=[data.to_dict() for data in user_datas])
return _SchemaResponse(success=True, data=user_datas)
@router.get("/test/{site_id}", summary="连接测试", response_model=_SchemaResponse[None])
def test_site(
site_id: int,
db: Session = Depends(get_db),
query: SiteQueryService = Depends(get_site_sync_query_service),
_: _SchemaTokenPayload = Depends(verify_token),
) -> Any:
"""
测试站点是否可用
"""
site = Site.get(db, site_id)
site = query.get_sync(site_id)
if not site:
raise HTTPException(
status_code=404,
@@ -393,24 +382,22 @@ def test_site(
)
async def site_icon(
site_id: int,
db: AsyncSession = Depends(get_async_db),
query: SiteQueryService = Depends(get_site_query_service),
_: _SchemaTokenPayload = Depends(verify_token),
) -> Any:
"""
获取站点图标:base64或者url
"""
site = await Site.async_get(db, site_id)
site = await query.get(site_id)
if not site:
raise HTTPException(
status_code=404,
detail=f"站点 {site_id} 不存在",
)
icon = await SiteIcon.async_get_by_domain(db, site.domain)
icon = await query.icon(site.domain)
if not icon:
return _SchemaResponse(success=False, message="站点图标不存在!")
return _SchemaResponse(
success=True, data={"icon": icon.base64 if icon.base64 else icon.url}
)
return _SchemaResponse(success=True, data=icon.model_dump())
@router.get(
@@ -418,13 +405,13 @@ async def site_icon(
)
async def site_category(
site_id: int,
db: AsyncSession = Depends(get_async_db),
query: SiteQueryService = Depends(get_site_query_service),
_: _SchemaTokenPayload = Depends(verify_token),
) -> Any:
"""
获取站点分类
"""
site = await Site.async_get(db, site_id)
site = await query.get(site_id)
if not site:
raise HTTPException(
status_code=404,
@@ -456,13 +443,13 @@ async def site_resource(
mtype: Optional[str] = None,
cat: Optional[str] = None,
page: Optional[int] = 0,
db: AsyncSession = Depends(get_async_db),
_: User = Depends(get_current_active_manage_user_async),
query: SiteQueryService = Depends(get_site_query_service),
_: ApiPrincipal = Depends(get_current_active_manage_user_async),
) -> Any:
"""
浏览站点资源
"""
site = await Site.async_get(db, site_id)
site = await query.get(site_id)
if not site:
raise HTTPException(
status_code=404,
@@ -483,14 +470,14 @@ async def site_resource(
@router.get("/domain/{site_url}", summary="站点详情", response_model=_SchemaSite)
async def read_site_by_domain(
site_url: str,
db: AsyncSession = Depends(get_async_db),
query: SiteQueryService = Depends(get_site_query_service),
_: _SchemaTokenPayload = Depends(verify_token),
) -> Any:
"""
通过域名获取站点信息
"""
domain = site_rules.extract_domain(site_url)
site = await Site.async_get_by_domain(db, domain)
site = await query.get_by_domain(domain)
if not site:
raise HTTPException(
status_code=404,
@@ -506,45 +493,42 @@ async def read_site_by_domain(
)
async def read_statistic_by_domain(
site_url: str,
db: AsyncSession = Depends(get_async_db),
query: SiteQueryService = Depends(get_site_query_service),
_: _SchemaTokenPayload = Depends(verify_token),
) -> Any:
"""
通过域名获取站点统计信息
"""
domain = site_rules.extract_domain(site_url)
sitestatistic = await SiteStatistic.async_get_by_domain(db, domain)
if sitestatistic:
return sitestatistic
return _SchemaSiteStatistic(domain=domain)
return await query.statistic(domain)
@router.get(
"/statistic", summary="所有站点统计信息", response_model=List[_SchemaSiteStatistic]
)
async def read_statistics(
db: AsyncSession = Depends(get_async_db),
query: SiteQueryService = Depends(get_site_query_service),
_: _SchemaTokenPayload = Depends(verify_token),
) -> Any:
"""
获取所有站点统计信息
"""
return await SiteStatistic.async_list(db)
return await query.statistics()
@router.get("/rss", summary="所有订阅站点", response_model=List[_SchemaSite])
async def read_rss_sites(
db: AsyncSession = Depends(get_async_db),
query: SiteQueryService = Depends(get_site_query_service),
_: _SchemaTokenPayload = Depends(verify_token),
) -> List[dict]:
"""
获取站点列表
"""
# 选中的rss站点
selected_sites = SystemConfigOper().get(SystemConfigKey.RssSites) or []
selected_sites = get_configured_system_config().get(SystemConfigKey.RssSites) or []
# 所有站点
all_site = await Site.async_list_order_by_pri(db)
all_site = await query.list_ordered()
if not selected_sites:
return all_site
@@ -563,7 +547,7 @@ async def read_auth_sites(_: _SchemaTokenPayload = Depends(verify_token)) -> dic
@router.post("/auth", summary="用户站点认证", response_model=_SchemaResponse[None])
def auth_site(
auth_info: _SchemaSiteAuth, _: User = Depends(get_current_active_superuser)
auth_info: _SchemaSiteAuth, _: ApiPrincipal = Depends(get_current_active_superuser)
) -> Any:
"""
用户站点认证
@@ -571,7 +555,7 @@ def auth_site(
if not auth_info or not auth_info.site or not auth_info.params:
return _SchemaResponse(success=False, message="请输入认证站点和认证参数")
status, msg = SitesHelper().check_user(auth_info.site, auth_info.params)
SystemConfigOper().set(SystemConfigKey.UserSiteAuthParams, auth_info.model_dump())
get_configured_system_config().set(SystemConfigKey.UserSiteAuthParams, auth_info.model_dump())
# 认证成功后,重新初始化插件
PluginManager().init_config()
Scheduler().init_plugin_jobs()
@@ -585,12 +569,15 @@ def auth_site(
summary="获取站点域名到名称的映射",
response_model=_SchemaResponse[_SchemaSiteMappingData],
)
async def site_mapping(_: User = Depends(get_current_active_superuser_async)):
async def site_mapping(
query: SiteQueryService = Depends(get_site_sync_query_service),
_: ApiPrincipal = Depends(get_current_active_superuser_async),
):
"""
获取站点域名到名称的映射关系
"""
try:
sites = await SiteOper().async_list()
sites = query.list_sync()
mapping = {}
for site in sites:
mapping[site.domain] = site.name
@@ -604,7 +591,7 @@ async def site_mapping(_: User = Depends(get_current_active_superuser_async)):
summary="获取支持的站点列表",
response_model=_SchemaJsonObject,
)
async def support_sites(_: User = Depends(get_current_active_superuser_async)):
async def support_sites(_: ApiPrincipal = Depends(get_current_active_superuser_async)):
"""
获取支持的站点列表
"""
@@ -614,13 +601,13 @@ async def support_sites(_: User = Depends(get_current_active_superuser_async)):
@router.get("/{site_id}", summary="站点详情", response_model=_SchemaSite)
async def read_site(
site_id: int,
db: AsyncSession = Depends(get_async_db),
_: User = Depends(get_current_active_manage_user_async),
query: SiteQueryService = Depends(get_site_query_service),
_: ApiPrincipal = Depends(get_current_active_manage_user_async),
) -> Any:
"""
通过ID获取站点信息
"""
site = await Site.async_get(db, site_id)
site = await query.get(site_id)
if not site:
raise HTTPException(
status_code=404,
@@ -633,7 +620,7 @@ async def read_site(
async def delete_site(
site_id: int,
command: SiteMutationCommand = Depends(get_site_mutation_command),
_: User = Depends(get_current_active_manage_user_async),
_: ApiPrincipal = Depends(get_current_active_manage_user_async),
) -> Any:
"""
删除站点