refactor: finish transactional runtime migration

This commit is contained in:
jxxghp
2026-08-22 15:18:16 +08:00
parent fe2e6809f7
commit be18cace1f
59 changed files with 1006 additions and 568 deletions
+58 -36
View File
@@ -24,14 +24,16 @@ class AgentTaskOper(DbOper):
新增 Agent 定时任务。
"""
now = self._now()
task_id = AgentTask.add_task(
self._db,
**kwargs,
enabled=True,
last_status="waiting",
run_count=0,
created_at=now,
updated_at=now,
task_id = self._execute_sync_write(
lambda session: AgentTask.add_task(
session,
**kwargs,
enabled=True,
last_status="waiting",
run_count=0,
created_at=now,
updated_at=now,
)
)
return self.get(task_id)
@@ -81,38 +83,50 @@ class AgentTaskOper(DbOper):
if not normalized_payload:
return False
normalized_payload["updated_at"] = self._now()
return AgentTask.update_task(
self._db,
task_id=task_id,
payload=normalized_payload,
user_id=user_id,
return self._execute_sync_write(
lambda session: AgentTask.update_task(
session,
task_id=task_id,
payload=normalized_payload,
user_id=user_id,
)
)
def delete(self, task_id: int, user_id: Optional[str] = None) -> bool:
"""
删除非运行中的 Agent 定时任务及其运行历史。
"""
return AgentTaskRun.delete_task_and_runs(
self._db,
task_id=task_id,
user_id=user_id,
return self._execute_sync_write(
lambda session: AgentTaskRun.delete_task_and_runs(
session,
task_id=task_id,
user_id=user_id,
)
)
def begin_run(
self,
task_id: int,
trigger_source: str = "scheduled",
*,
run_id: Optional[str] = None,
started_at: Optional[str] = None,
) -> Optional[AgentTaskRun]:
"""
原子创建一次运行并返回其任务快照。
可选运行 ID 和开始时间用于恢复/幂等验证;正常调度入口由本方法生成。
"""
run_id = uuid4().hex
created_run_id = AgentTaskRun.begin_run(
self._db,
task_id=task_id,
run_id=run_id,
trigger_source=trigger_source,
started_at=self._now(),
resolved_run_id = run_id or uuid4().hex
resolved_started_at = started_at or self._now()
created_run_id = self._execute_sync_write(
lambda session: AgentTaskRun.begin_run(
session,
task_id=task_id,
run_id=resolved_run_id,
trigger_source=trigger_source,
started_at=resolved_started_at,
)
)
return self.get_run(created_run_id) if created_run_id else None
@@ -124,11 +138,15 @@ class AgentTaskOper(DbOper):
"""
将遗留的运行中任务标记为中断且结果未知。
"""
return AgentTaskRun.interrupt_task(
self._db,
task_id=task_id,
result=(result or "")[:20000],
finished_at=self._now(),
finished_at = self._now()
normalized_result = (result or "")[:20000]
return self._execute_sync_write(
lambda session: AgentTaskRun.interrupt_task(
session,
task_id=task_id,
result=normalized_result,
finished_at=finished_at,
)
)
def get_run(self, run_id: str) -> Optional[AgentTaskRun]:
@@ -157,13 +175,17 @@ class AgentTaskOper(DbOper):
disable_date_task: bool = False,
) -> bool:
"""收口精确运行并更新仍匹配的任务投影。"""
return AgentTaskRun.finish_run(
self._db,
run_id=run_id,
success=success,
result=(result or "")[:20000],
finished_at=self._now(),
disable_date_task=disable_date_task,
finished_at = self._now()
normalized_result = (result or "")[:20000]
return self._execute_sync_write(
lambda session: AgentTaskRun.finish_run(
session,
run_id=run_id,
success=success,
result=normalized_result,
finished_at=finished_at,
disable_date_task=disable_date_task,
)
)
def finish(
+6 -4
View File
@@ -54,8 +54,10 @@ class DownloadFailureOper(DbOper):
"""
删除已过期较久的失败记录。
"""
return DownloadFailure.delete_expired(
self._db,
before_time=before_time,
limit=limit,
return self._execute_sync_write(
lambda session: DownloadFailure.delete_expired(
session,
before_time=before_time,
limit=limit,
)
)
+3 -1
View File
@@ -127,7 +127,9 @@ class DownloadHistoryOper(DbOper):
按fullpath删除下载文件记录
:param fullpath: 数据key
"""
DownloadFiles.delete_by_fullpath(self._db, fullpath)
self._execute_sync_write(
lambda session: DownloadFiles.delete_by_fullpath(session, fullpath)
)
def stage_delete_file_by_fullpath(self, fullpath: str) -> None:
"""暂存指定完整路径的下载文件记录删除。"""
+16 -3
View File
@@ -61,19 +61,32 @@ class MediaServerOper(DbOper):
"""
清空媒体服务器数据
"""
MediaServerItem.empty(self._db, server)
self._execute_sync_write(
lambda session: MediaServerItem.empty(session, server)
)
def delete_stale(self, server: str, sync_time: str) -> int:
"""
删除本轮同步未更新的旧数据
"""
return MediaServerItem.delete_stale(self._db, server, sync_time)
return self._execute_sync_write(
lambda session: MediaServerItem.delete_stale(
session,
server,
sync_time,
)
)
def delete_excluded_servers(self, servers: list[str]) -> int:
"""
删除未启用或已移除媒体服务器的数据
"""
return MediaServerItem.delete_excluded_servers(self._db, servers)
return self._execute_sync_write(
lambda session: MediaServerItem.delete_excluded_servers(
session,
servers,
)
)
def exists(self, **kwargs) -> Optional[MediaServerItem]:
"""
+2 -1
View File
@@ -62,7 +62,8 @@ class MessageOper(DbOper):
if k not in Message.__table__.columns.keys(): # noqa
kwargs.pop(k)
return Message(**kwargs).create_and_to_dict(self._db)
message = Message(**kwargs)
return self._execute_sync_write(message.create_and_to_dict)
async def async_add(self,
channel: Optional[NotificationChannel] = None,
+21 -3
View File
@@ -24,13 +24,31 @@ class PassKeyOper(DbOper):
def create(self, payload: dict[str, Any]) -> PassKey:
"""创建 PassKey 凭证。"""
passkey = PassKey(**payload)
passkey.create(self._db)
self._execute_sync_write(lambda session: self._stage_create(session, passkey))
return passkey
@staticmethod
def _stage_create(session: Any, passkey: PassKey) -> None:
"""在调用方事务中暂存凭证并分配主键。"""
session.add(passkey)
session.flush()
def update_last_used(self, passkey: PassKey, sign_count: int) -> bool:
"""更新凭证最后使用时间和签名计数。"""
return bool(passkey.update_last_used(self._db, sign_count))
return bool(self._execute_sync_write(
lambda session: passkey.update_last_used(session, sign_count)
))
def delete_by_id(self, passkey_id: int, user_id: int) -> bool:
"""删除指定用户的凭证。"""
return bool(PassKey.delete_by_id(self._db, passkey_id, user_id))
return bool(self._execute_sync_write(
lambda session: PassKey.delete_by_id(session, passkey_id, user_id)
))
async def async_delete_by_id(self, passkey_id: int, user_id: int) -> bool:
"""在独立异步事务中删除指定用户的凭证。"""
return bool(await self._execute_async_write(
lambda session: PassKey.async_delete_by_id(
session, passkey_id, user_id
)
))
+8 -4
View File
@@ -80,10 +80,14 @@ class PluginDataOper(DbOper):
:param plugin_id: 插件id
:param key: 数据key
"""
if key:
PluginData.del_plugin_data_by_key(self._db, plugin_id, key)
else:
PluginData.del_plugin_data(self._db, plugin_id)
def stage(session: Session) -> None:
"""把兼容删除入口映射到调用方或组合根持有的事务。"""
if key:
PluginData.del_plugin_data_by_key(session, plugin_id, key)
else:
PluginData.del_plugin_data(session, plugin_id)
self._execute_sync_write(stage)
def stage_delete(self, plugin_id: str) -> None:
"""暂存目标插件全部数据删除并 flush,不提交调用方事务。"""
+2 -2
View File
@@ -116,8 +116,8 @@ class SiteOper(DbOper):
Site.delete(self._db, sid)
def reset(self) -> None:
"""清空站点表,保留站点模型细节在数据库适配层"""
Site.reset(self._db)
"""清空站点表;兼容入口的事务由组合根统一持有"""
self._execute_sync_write(Site.reset)
async def stage_reset(self) -> None:
"""暂存清空站点表,由应用事务统一提交。"""
+17 -7
View File
@@ -264,14 +264,18 @@ class TransferHistoryOper(DbOper):
kwargs.update({
"date": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
})
TransferHistory.replace_by_src(self._db, **kwargs)
def stage(session: Session) -> Optional[TransferHistory]:
"""在同一事务替换记录并返回兼容查询投影。"""
TransferHistory.replace_by_src(session, **kwargs)
return TransferHistory.get_by_src(
session,
kwargs.get("src"),
kwargs["src_storage"],
)
# 保持 add_force 的既有返回契约:返回可被调用方安全读取字段的查询结果,
# 而非事务提交后可能已脱离会话的新建实例。
return TransferHistory.get_by_src(
self._db,
kwargs.get("src"),
kwargs["src_storage"],
)
return self._execute_sync_write(stage)
def stage_replace_by_src(self, **kwargs) -> TransferHistory:
"""在调用方事务内按源路径替换整理历史并返回已分配 ID 的新记录。"""
@@ -295,7 +299,13 @@ class TransferHistoryOper(DbOper):
"""
补充转移记录download_hash
"""
TransferHistory.update_download_hash(self._db, historyid, download_hash)
self._execute_sync_write(
lambda session: TransferHistory.update_download_hash(
session,
historyid,
download_hash,
)
)
def list_by_date(self, date: str) -> List[TransferHistory]:
"""
+16 -7
View File
@@ -20,11 +20,14 @@ class TransferPendingOper(DbOper):
:param src_path: 源文件路径
:return: 登记记录
"""
return TransferPending.register(
self._db,
storage=storage,
src_path=src_path,
now_time=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
now_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
return self._execute_sync_write(
lambda session: TransferPending.register(
session,
storage=storage,
src_path=src_path,
now_time=now_time,
)
)
def discard(self, storage: str, src_path: str) -> int:
@@ -34,7 +37,13 @@ class TransferPendingOper(DbOper):
:param src_path: 源文件路径
:return: 删除的记录数
"""
return TransferPending.discard(self._db, storage=storage, src_path=src_path)
return self._execute_sync_write(
lambda session: TransferPending.discard(
session,
storage=storage,
src_path=src_path,
)
)
def list_all(self, limit: Optional[int] = 5000) -> List[Tuple[str, str]]:
"""
@@ -56,4 +65,4 @@ class TransferPendingOper(DbOper):
清空全部待整理登记。
:return: 删除的记录数
"""
return TransferPending.clear(self._db)
return self._execute_sync_write(TransferPending.clear)
+34 -6
View File
@@ -11,6 +11,8 @@ runtime 兼容映射指向 SDK 薄门面;canonical 数据访问模块仍只依
"""
from typing import List, Optional
from sqlalchemy.ext.asyncio import AsyncSession
from app.db.base import DbOper
from app.db.models.user import User
@@ -49,27 +51,53 @@ class UserOper(DbOper):
async def async_create(self, payload: dict) -> Optional[User]:
"""异步创建用户。"""
return await User(**payload).async_create(self._db)
user = User(**payload)
async def stage(session: AsyncSession) -> User:
"""在当前异步事务中暂存用户并分配主键。"""
session.add(user)
await session.flush()
return user
return await self._execute_async_write(stage)
async def async_update(self, user_id: int, payload: dict) -> Optional[User]:
"""异步更新用户。"""
user = await self.async_get_by_id(user_id)
if user:
await user.async_update(self._db, payload)
async def stage(session: AsyncSession) -> User:
"""在当前事务中更新用户字段,必要时重新附加游离对象。"""
for key, value in payload.items():
setattr(user, key, value)
return await session.merge(user)
await self._execute_async_write(stage)
return user
async def async_delete(self, user_id: int) -> None:
async def async_delete(self, user_id: int) -> bool:
"""异步删除用户。"""
await User.async_delete_by_id(self._db, user_id)
return bool(await self._execute_async_write(
lambda session: User.async_delete_by_id(session, user_id)
))
async def async_delete_by_name(self, name: str) -> bool:
"""在独立异步事务中按用户名删除用户。"""
return bool(await self._execute_async_write(
lambda session: User().async_delete_by_name(session, name)
))
async def async_update_otp_by_name(
self,
name: str,
otp: bool,
secret: str,
) -> None:
) -> bool:
"""异步更新用户 OTP 状态。"""
await User.async_update_otp_by_name(self._db, name, otp, secret)
return bool(await self._execute_async_write(
lambda session: User.async_update_otp_by_name(
session, name, otp, secret
)
))
async def async_get_by_name(self, name: str) -> Optional[User]:
"""