mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 15:38:19 +08:00
refactor: finish transactional runtime migration
This commit is contained in:
@@ -45,6 +45,10 @@ class CleanupRepository(Protocol):
|
||||
"""返回一次维护运行共用的数据库会话上下文。"""
|
||||
...
|
||||
|
||||
def unit_of_work(self, db: Any) -> "CleanupUnitOfWork":
|
||||
"""返回绑定到当前维护会话的事务边界。"""
|
||||
...
|
||||
|
||||
def delete_messages(self, db: Any, cutoff: str, limit: int) -> int:
|
||||
"""删除早于截止时间的消息。"""
|
||||
...
|
||||
@@ -70,6 +74,18 @@ class CleanupRepository(Protocol):
|
||||
...
|
||||
|
||||
|
||||
class CleanupUnitOfWork(Protocol):
|
||||
"""数据维护每一批删除所需的最小事务能力。"""
|
||||
|
||||
def commit(self) -> None:
|
||||
"""提交当前批次。"""
|
||||
...
|
||||
|
||||
def rollback(self) -> None:
|
||||
"""回滚失败批次并恢复会话可用状态。"""
|
||||
...
|
||||
|
||||
|
||||
class DataCleanupService:
|
||||
"""按配置执行分批数据清理并生成兼容报告。"""
|
||||
|
||||
@@ -175,16 +191,19 @@ class DataCleanupService:
|
||||
value=plan_index / total_plans * 100,
|
||||
text=f"正在清理数据表 {plan.name} ...",
|
||||
)
|
||||
unit_of_work = self._repository.unit_of_work(db)
|
||||
table_report = self._cleanup_in_batches(
|
||||
db=db,
|
||||
table_name=plan.name,
|
||||
delete_batch=plan.delete_batch,
|
||||
unit_of_work=unit_of_work,
|
||||
)
|
||||
table_report["cutoff"] = plan.cutoff
|
||||
table_report["retention_days"] = plan.retention_days
|
||||
report["tables"][plan.name] = table_report
|
||||
report["total_deleted"] += table_report["deleted"]
|
||||
except Exception as err:
|
||||
self._repository.unit_of_work(db).rollback()
|
||||
errors.append(f"{plan.name}: {str(err)}")
|
||||
logger.error(f"数据表 {plan.name} 清理失败:{str(err)}")
|
||||
report["tables"][plan.name] = {
|
||||
@@ -279,12 +298,13 @@ class DataCleanupService:
|
||||
),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def _cleanup_in_batches(
|
||||
self,
|
||||
*,
|
||||
db: Any,
|
||||
table_name: str,
|
||||
delete_batch: Callable[[Any], int],
|
||||
unit_of_work: CleanupUnitOfWork,
|
||||
) -> Dict[str, int]:
|
||||
"""循环执行单表分批删除,直到持久化端口返回零。"""
|
||||
total_deleted = 0
|
||||
@@ -293,6 +313,7 @@ class DataCleanupService:
|
||||
deleted = delete_batch(db) or 0
|
||||
if deleted <= 0:
|
||||
break
|
||||
unit_of_work.commit()
|
||||
batches += 1
|
||||
total_deleted += deleted
|
||||
logger.info(
|
||||
|
||||
@@ -146,14 +146,16 @@ class OutboxDispatcher:
|
||||
lease_seconds: int = 60,
|
||||
clock: Callable[[], datetime] | None = None,
|
||||
close: Callable[[], None] | None = None,
|
||||
failure_observer: Callable[[bool], None] | None = None,
|
||||
) -> None:
|
||||
"""注入持久端口、topic handler 和有界重试策略。"""
|
||||
"""注入持久端口、topic handler、有界重试策略与失败观测端口。"""
|
||||
self._repository = repository
|
||||
self._handlers = handlers
|
||||
self._max_attempts = max_attempts
|
||||
self._lease_seconds = lease_seconds
|
||||
self._clock = clock or (lambda: datetime.now(timezone.utc))
|
||||
self._close = close or (lambda: None)
|
||||
self._failure_observer = failure_observer or (lambda _dead: None)
|
||||
|
||||
def dispatch_one(self) -> bool:
|
||||
"""处理一条到期消息;无消息返回 False,handler 失败留待重试。"""
|
||||
@@ -176,6 +178,7 @@ class OutboxDispatcher:
|
||||
last_error=str(error)[:4000],
|
||||
dead=dead,
|
||||
)
|
||||
self._failure_observer(dead)
|
||||
return True
|
||||
self._repository.complete(message.message_id, now)
|
||||
return True
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
避免 API 层同时承担 HTTP 编排和 ORM 适配职责。
|
||||
"""
|
||||
|
||||
from collections.abc import Callable
|
||||
from collections.abc import Awaitable, Callable
|
||||
from typing import Any, Protocol
|
||||
|
||||
|
||||
@@ -33,12 +33,27 @@ class UserRepository(Protocol):
|
||||
"""更新用户 OTP 状态。"""
|
||||
|
||||
|
||||
class AsyncUnitOfWork(Protocol):
|
||||
"""用户写用例所需的异步事务边界。"""
|
||||
|
||||
async def commit(self) -> None:
|
||||
"""提交用户写入。"""
|
||||
|
||||
async def rollback(self) -> None:
|
||||
"""回滚失败的用户写入。"""
|
||||
|
||||
|
||||
class UserService:
|
||||
"""用户管理应用服务。"""
|
||||
|
||||
def __init__(self, repository: UserRepository) -> None:
|
||||
"""创建用户服务。"""
|
||||
def __init__(
|
||||
self,
|
||||
repository: UserRepository,
|
||||
unit_of_work: AsyncUnitOfWork | None = None,
|
||||
) -> None:
|
||||
"""创建用户服务;旧独立仓储可暂不提供请求级 UoW。"""
|
||||
self._repository = repository
|
||||
self._unit_of_work = unit_of_work
|
||||
|
||||
async def list(self) -> list[Any]:
|
||||
"""返回用户列表。"""
|
||||
@@ -54,19 +69,35 @@ class UserService:
|
||||
|
||||
async def create(self, payload: dict[str, Any]) -> Any | None:
|
||||
"""创建用户。"""
|
||||
return await self._repository.async_create(payload)
|
||||
return await self._write(lambda: self._repository.async_create(payload))
|
||||
|
||||
async def update(self, user_id: int, payload: dict[str, Any]) -> Any | None:
|
||||
"""更新用户。"""
|
||||
return await self._repository.async_update(user_id, payload)
|
||||
return await self._write(
|
||||
lambda: self._repository.async_update(user_id, payload)
|
||||
)
|
||||
|
||||
async def delete(self, user_id: int) -> None:
|
||||
"""删除用户。"""
|
||||
await self._repository.async_delete(user_id)
|
||||
await self._write(lambda: self._repository.async_delete(user_id))
|
||||
|
||||
async def update_otp(self, name: str, otp: bool, secret: str) -> None:
|
||||
"""更新用户 OTP 状态。"""
|
||||
await self._repository.async_update_otp_by_name(name, otp, secret)
|
||||
await self._write(
|
||||
lambda: self._repository.async_update_otp_by_name(name, otp, secret)
|
||||
)
|
||||
|
||||
async def _write(self, operation: Callable[[], Awaitable[Any]]) -> Any:
|
||||
"""执行用户写入,并在正式请求路径统一提交或回滚。"""
|
||||
try:
|
||||
result = await operation()
|
||||
if self._unit_of_work is not None:
|
||||
await self._unit_of_work.commit()
|
||||
return result
|
||||
except Exception:
|
||||
if self._unit_of_work is not None:
|
||||
await self._unit_of_work.rollback()
|
||||
raise
|
||||
|
||||
|
||||
_configured_user_id_lookup: Callable[[int], Any | None] | None = None
|
||||
|
||||
Reference in New Issue
Block a user