feat: make subscribe completion durable

This commit is contained in:
jxxghp
2026-08-23 00:59:17 +08:00
parent f26bde2250
commit 903bc090ed
7 changed files with 369 additions and 43 deletions
+12
View File
@@ -249,6 +249,13 @@ def _build_outbox_dispatcher() -> OutboxDispatcher:
):
raise RuntimeError("订阅新增统计上报未确认")
def dispatch_subscribe_complete_report(message) -> None:
"""重放订阅完成统计;未确认时抛错以进入有限重试。"""
if not MoviePilotServerHelper.sub_done_durable(
message.payload.get("subscribe_info") or {}
):
raise RuntimeError("订阅完成统计上报未确认")
session = SessionFactory()
return OutboxDispatcher(
repository=SqlAlchemyOutboxRepository(session),
@@ -267,6 +274,11 @@ def _build_outbox_dispatcher() -> OutboxDispatcher:
message.payload,
),
"subscribe.deleted.report": dispatch_subscribe_deleted_report,
"subscribe.complete": lambda message: EventManager().send_event(
EventType.SubscribeComplete,
message.payload,
),
"subscribe.complete.report": dispatch_subscribe_complete_report,
"download.added": lambda message: EventManager().send_event(
EventType.DownloadAdded,
restore_download_added(message.payload),
+27 -1
View File
@@ -1,7 +1,7 @@
"""订阅写入事务适配器的启动装配。"""
from collections.abc import Callable
from contextlib import AbstractAsyncContextManager, asynccontextmanager
from contextlib import AbstractAsyncContextManager, asynccontextmanager, contextmanager
from datetime import datetime, timezone
from typing import Any
@@ -20,6 +20,10 @@ from app.application.subscription.delete import (
DeleteSubscribeCommand,
configure_delete_subscribe_scope,
)
from app.application.subscription.complete import (
CompleteSubscriptionCommand,
configure_subscription_completion_scope,
)
from app.application.subscription.mutation import (
SubscriptionMutationService,
configure_subscription_mutation_scope,
@@ -28,6 +32,7 @@ from app.adapters.external.server import MoviePilotServerHelper
from app.db.oper.subscribe import SubscribeOper
from app.db.oper.subscribehistory import SubscribeHistoryOper
from app.db.session import async_session_scope
from app.db.session import SessionFactory
from app.db.uow import SqlAlchemyAsyncUnitOfWork, SqlAlchemyUnitOfWork
from app.startup.outbox import (
SqlAlchemyAsyncOutboxStager,
@@ -133,6 +138,26 @@ async def _publish_deleted(payload: dict[str, Any]) -> None:
await EventManager().async_send_event(EventType.SubscribeDeleted, payload)
def _publish_completed(payload: dict[str, Any]) -> None:
"""发布已提交的订阅完成事件。"""
EventManager().send_event(EventType.SubscribeComplete, payload)
@contextmanager
def subscription_completion_scope():
"""为同步完成链创建独占 Session、UoW 与 durable outbox。"""
session = SessionFactory()
try:
yield CompleteSubscriptionCommand(
repository=SubscribeOper(session),
unit_of_work=SqlAlchemyUnitOfWork(session),
outbox=SqlAlchemyOutboxRepository(session),
publish=_publish_completed,
)
finally:
session.close()
@asynccontextmanager
async def subscription_mutation_scope():
"""为非 HTTP 入口创建独占订阅修改会话、UoW 与 outbox。"""
@@ -163,3 +188,4 @@ def configure_transactional_subscription_scopes() -> None:
"""登记 Agent 等非 HTTP 入口复用的订阅事务作用域。"""
configure_subscription_mutation_scope(subscription_mutation_scope)
configure_delete_subscribe_scope(delete_subscribe_scope)
configure_subscription_completion_scope(subscription_completion_scope)