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
@@ -4,7 +4,7 @@ from typing import Any, Optional, Union
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import Session
from app.db import DbOper
from app.db.base import DbOper
from app.db.models.agentchat import AgentChat
from app.schemas.types import NotificationChannel
+1 -1
View File
@@ -4,7 +4,7 @@ from datetime import datetime
from typing import Optional
from uuid import uuid4
from app.db import DbOper
from app.db.base import DbOper
from app.db.models.agenttask import AgentTask
from app.db.models.agenttaskrun import AgentTaskRun
+1 -1
View File
@@ -1,6 +1,6 @@
from typing import Dict, List, Optional
from app.db import DbOper
from app.db.base import DbOper
from app.db.models.downloadfailure import DownloadFailure
+22 -1
View File
@@ -1,6 +1,8 @@
from typing import Dict, List, Optional, cast
from app.db import DbOper
from sqlalchemy import delete as sqlalchemy_delete, update as sqlalchemy_update
from app.db.base import DbOper
from app.db.models.downloadhistory import DownloadHistory, DownloadFiles
from app.schemas.types import MediaSource
@@ -110,6 +112,17 @@ class DownloadHistoryOper(DbOper):
"""
DownloadFiles.delete_by_fullpath(self._db, fullpath)
def stage_delete_file_by_fullpath(self, fullpath: str) -> None:
"""暂存指定完整路径的下载文件记录删除。"""
self._db.execute(
sqlalchemy_update(DownloadFiles)
.where(
DownloadFiles.fullpath == fullpath,
DownloadFiles.state == 1,
)
.values(state=0)
)
def get_hash_by_fullpath(self, fullpath: str) -> Optional[str]:
"""
按fullpath查询下载文件记录hash
@@ -192,6 +205,14 @@ class DownloadHistoryOper(DbOper):
"""
DownloadHistory.delete(self._db, historyid)
def stage_delete_history(self, historyid: int) -> None:
"""暂存下载记录删除,不由模型装饰器提交事务。"""
self._db.execute(
sqlalchemy_delete(DownloadHistory).where(
DownloadHistory.id == historyid
)
)
def delete_downloadfile(self, downloadfileid):
"""
删除下载文件记录
+1 -1
View File
@@ -2,7 +2,7 @@ from typing import Optional
from sqlalchemy.orm import Session
from app.db import DbOper
from app.db.base import DbOper
from app.db.models.mediaserver import MediaServerItem
+3 -2
View File
@@ -4,9 +4,10 @@ from typing import Optional, Union
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import Session
from app.db import DbOper
from app.db.base import DbOper
from app.db.models.message import Message
from app.schemas import NotificationChannel, MessageType
from app.schemas.notification import NotificationChannel
from app.schemas.message import MessageType
class MessageOper(DbOper):
+1 -1
View File
@@ -1,6 +1,6 @@
from typing import Any, Optional
from app.db import DbOper
from app.db.base import DbOper
from app.db.models.plugindata import PluginData
+47 -3
View File
@@ -1,9 +1,11 @@
from datetime import datetime
from typing import List, Tuple, Optional
from typing import Any, List, Mapping, Tuple, Optional
from app.db import DbOper
from app.db.models import SiteIcon
from sqlalchemy import delete as sqlalchemy_delete
from app.db.base import DbOper
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
@@ -35,6 +37,48 @@ class SiteOper(DbOper):
"""
return await Site.async_get(self._db, sid)
async def get_by_id(self, site_id: int) -> Optional[Site]:
"""读取站点写用例需要的目标站点。"""
return await self.async_get(site_id)
async def get_by_domain(self, domain: str) -> Optional[Site]:
"""按域名读取站点写用例的重复目标。"""
return await Site.async_get_by_domain(self._db, domain)
async def stage_create(self, payload: Mapping[str, Any]) -> None:
"""暂存新增站点,不由仓储自行提交。"""
values = dict(payload)
values.pop("id", None)
self._db.add(Site(**values))
async def stage_update(
self,
site_id: int,
payload: Mapping[str, Any],
) -> bool:
"""暂存站点字段更新,不由模型装饰器提前提交。"""
site = await self.async_get(site_id)
if not site:
return False
for key, value in payload.items():
if key != "id":
setattr(site, key, value)
return True
async def stage_delete(self, site_id: int) -> None:
"""暂存站点删除,由请求级 UnitOfWork 统一提交。"""
await self._db.execute(
sqlalchemy_delete(Site).where(Site.id == site_id)
)
async def stage_priorities(self, priorities: list[dict]) -> None:
"""暂存批量优先级更新,避免逐行独立提交。"""
for priority in priorities:
site_id = priority.get("id")
site = await self.async_get(site_id) if site_id else None
if site:
site.pri = priority.get("pri")
def list(self) -> List[Site]:
"""
获取站点列表
+79 -1
View File
@@ -10,7 +10,10 @@
import time
from typing import Any, Tuple, List, Optional
from app.db import DbOper
from sqlalchemy import delete as sqlalchemy_delete
from app.application.subscription.delete import SubscribeDeletionCandidate
from app.db.base import DbOper
from app.db.models.subscribe import Subscribe
from app.db.models.subscribehistory import SubscribeHistory
from app.schemas.types import MediaSource
@@ -154,6 +157,75 @@ class SubscribeOper(DbOper):
"""
return await Subscribe.async_get(self._db, rid=sid)
async def get_candidate(
self,
subscribe_id: int,
) -> Optional[SubscribeDeletionCandidate]:
"""读取订阅删除用例需要的权限字段与完整事件快照。"""
subscribe = await self.async_get(subscribe_id)
if not subscribe:
return None
values = subscribe.__dict__
event_payload = {
column.name: values.get(column.name)
for column in subscribe.__table__.columns
}
return SubscribeDeletionCandidate(
subscribe_id=subscribe_id,
username=subscribe.username,
event_payload=event_payload,
)
async def list_candidates_by_identity(
self,
media_source: MediaSource,
media_id: str,
season: Optional[int],
music_type: Optional[str],
) -> List[SubscribeDeletionCandidate]:
"""按媒体身份读取去重后的订阅删除快照。"""
subscribes = await Subscribe.async_list_by_media_identity(
self._db,
media_source=media_source,
media_id=media_id,
music_type=music_type,
)
candidates = []
seen_ids = set()
for subscribe in subscribes or []:
subscribe_music_type = getattr(subscribe, "music_type", None)
if music_type and not (
subscribe_music_type == music_type
or (music_type == "recording" and subscribe_music_type is None)
):
continue
if season is not None and subscribe.season != season:
continue
if not subscribe.id or subscribe.id in seen_ids:
continue
seen_ids.add(subscribe.id)
values = subscribe.__dict__
candidates.append(
SubscribeDeletionCandidate(
subscribe_id=subscribe.id,
username=subscribe.username,
event_payload={
column.name: values.get(column.name)
for column in subscribe.__table__.columns
},
)
)
return candidates
async def list_search_ids(self, username: str, state: str) -> List[int]:
"""返回用户指定状态的订阅编号,不向应用用例暴露 ORM 列表。"""
subscribes = await Subscribe.async_list_by_username(
self._db,
username,
state=state,
)
return [subscribe.id for subscribe in subscribes if subscribe.id]
def get_by(
self, type: str, media_source: MediaSource, media_id: str,
season: Optional[str] = None,
@@ -206,6 +278,12 @@ class SubscribeOper(DbOper):
"""
await Subscribe.async_delete(self._db, rid=sid)
async def stage_delete(self, sid: int) -> None:
"""登记订阅删除但不提交,由 Application UnitOfWork 控制事务边界。"""
await self._db.execute(
sqlalchemy_delete(Subscribe).where(Subscribe.id == sid)
)
async def async_update(self, sid: int, payload: dict) -> Optional[Subscribe]:
"""
异步更新订阅。
+2 -2
View File
@@ -1,6 +1,6 @@
from typing import List, Optional
from typing import List
from app.db import DbOper
from app.db.base import DbOper
from app.db.models.subscribehistory import SubscribeHistory
+1 -1
View File
@@ -3,7 +3,7 @@ import copy
import threading
from typing import Any, Optional, Union
from app.db import DbOper
from app.db.base import DbOper
from app.db.models.systemconfig import SystemConfig
from app.schemas.types import SystemConfigKey
from app.foundation.singleton import Singleton
+15 -1
View File
@@ -1,7 +1,9 @@
import time
from typing import Any, List, Optional
from app.db import DbOper
from sqlalchemy import delete as sqlalchemy_delete
from app.db.base import DbOper
from app.db.models.transferhistory import TransferHistory
from app.schemas.types import MediaSource
@@ -207,6 +209,18 @@ class TransferHistoryOper(DbOper):
"""
TransferHistory.delete(self._db, historyid)
def stage_delete(self, historyid: int) -> None:
"""暂存整理记录删除,不由模型装饰器提交事务。"""
self._db.execute(
sqlalchemy_delete(TransferHistory).where(
TransferHistory.id == historyid
)
)
def stage_truncate(self) -> None:
"""暂存全部整理记录删除,由请求级事务统一提交。"""
self._db.execute(sqlalchemy_delete(TransferHistory))
async def async_delete(self, historyid):
"""
异步删除转移记录。
+1 -1
View File
@@ -1,7 +1,7 @@
from datetime import datetime
from typing import List, Optional, Tuple
from app.db import DbOper
from app.db.base import DbOper
from app.db.models.transferpending import TransferPending
+1 -1
View File
@@ -11,7 +11,7 @@ runtime 兼容映射指向 SDK 薄门面;canonical 数据访问模块仍只依
"""
from typing import List, Optional
from app.db import DbOper
from app.db.base import DbOper
from app.db.models.user import User
+1 -1
View File
@@ -1,6 +1,6 @@
from typing import Any, Union, Dict, Optional
from app.db import DbOper
from app.db.base import DbOper
from app.db.models.userconfig import UserConfig
from app.schemas.types import UserConfigKey
from app.foundation.singleton import Singleton
+57 -2
View File
@@ -1,6 +1,8 @@
from typing import List, Tuple, Optional, Any, Coroutine, Sequence
from typing import List, Mapping, Tuple, Optional, Any
from app.db import DbOper
from sqlalchemy import delete as sqlalchemy_delete
from app.db.base import DbOper
from app.db.models.workflow import Workflow
@@ -25,6 +27,34 @@ class WorkflowOper(DbOper):
"""
return Workflow.get(self._db, wid)
def stage_state(self, workflow_id: int, state: str) -> bool:
"""暂存工作流状态变更,不由模型方法自行提交。"""
workflow = self.get(workflow_id)
if not workflow:
return False
workflow.state = state
return True
def stage_update(
self,
workflow_id: int,
payload: Mapping[str, Any],
) -> Optional[Workflow]:
"""暂存工作流字段更新并返回同一会话中的对象。"""
workflow = self.get(workflow_id)
if not workflow:
return None
for key, value in payload.items():
if key != "id":
setattr(workflow, key, value)
return workflow
def stage_delete(self, workflow_id: int) -> None:
"""暂存工作流删除,由请求级 UnitOfWork 统一提交。"""
self._db.execute(
sqlalchemy_delete(Workflow).where(Workflow.id == workflow_id)
)
async def async_get(self, wid: int) -> Optional[Workflow]:
"""
异步查询单个工作流
@@ -73,6 +103,31 @@ class WorkflowOper(DbOper):
"""
return await Workflow.async_get_by_name(self._db, name)
async def stage_create(self, payload: Mapping[str, Any]) -> Workflow:
"""暂存新工作流,不在操作器内提交事务。"""
workflow = Workflow(**dict(payload))
self._db.add(workflow)
await self._db.flush()
return workflow
async def stage_reset(
self,
workflow_id: int,
reset_count: bool = False,
) -> Optional[Workflow]:
"""暂存工作流重置字段,不触发模型装饰器的隐式提交。"""
workflow = await self.async_get(workflow_id)
if not workflow:
return None
workflow.state = "W"
workflow.result = None
workflow.current_action = None
workflow.context = {}
workflow.execution_state = {}
if reset_count:
workflow.run_count = 0
return workflow
def start(self, wid: int) -> bool:
"""
启动