Files
DDSRem 2de976f6b3 Merge pull request #7050 from DDSRem-Dev/codex-comment-migration
docs: 统一全项目中文注释和结构化契约说明
2026-10-17 19:26:29 +08:00

246 lines
7.9 KiB
Python

"""
订阅完成应用命令及其同步事务端口。
"""
from __future__ import annotations
from collections.abc import Callable, Mapping
from contextlib import AbstractContextManager
from datetime import datetime, timezone
from typing import Optional
from uuid import uuid4
from app.application.classification.reference import EffectiveClassificationSnapshot
from app.application.outbox import (
SUBSCRIBE_COMPLETED_TOPIC,
OutboxDispatchStore,
OutboxIntent,
OutboxStager,
SyncUnitOfWork,
deliver_outbox_effect,
)
from app.application.subscription.contract import (
SubscriptionHistoryPatch,
SubscriptionStagingPort,
)
from app.runtime.log import logger
from app.schemas.common import JsonData
CompletionEffect = Callable[[], None]
CompletionReporter = Callable[[Mapping[str, JsonData]], object]
class CompleteSubscriptionCommand:
"""
原子完成订阅,并按通知、事件、统计顺序执行提交后副作用。
"""
def __init__(
self,
repository: SubscriptionStagingPort,
unit_of_work: SyncUnitOfWork,
outbox: Optional[OutboxStager],
dispatch_store: Optional[OutboxDispatchStore],
publish: Callable[[dict[str, JsonData]], None],
) -> None:
"""
注入共享同步会话、事件发布端口和可选 durable outbox。
"""
self._repository = repository
self._unit_of_work = unit_of_work
self._outbox = outbox
self._dispatch_store = dispatch_store
self._publish = publish
def execute(
self,
subscribe_id: int,
subscribe_info: Mapping[str, JsonData],
mediainfo: Mapping[str, JsonData],
notify: CompletionEffect,
report: CompletionReporter,
notification: Mapping[str, JsonData] | None = None,
classification_snapshot: EffectiveClassificationSnapshot | None = None,
) -> None:
"""
在同一事务中写历史、删订阅并暂存完成事件、通知与统计意图。
"""
info = dict(subscribe_info)
if classification_snapshot is not None:
info.update(
{
"media_category_id": classification_snapshot.category_id,
"media_category": classification_snapshot.path,
"classification_rule_id": classification_snapshot.rule_id,
"classification_policy_revision": classification_snapshot.policy_revision,
"classification_source": classification_snapshot.source,
}
)
occurrence_id = uuid4().hex
event_key = completion_event_key(
subscribe_id,
info,
occurrence_id=occurrence_id,
)
event_payload: dict[str, JsonData] = {
"subscribe_id": subscribe_id,
"subscribe_info": info,
"mediainfo": dict(mediainfo),
"idempotency_key": event_key,
}
report_key = completion_report_key(
subscribe_id,
info,
occurrence_id=occurrence_id,
)
notification_key = completion_notification_key(
subscribe_id,
info,
occurrence_id=occurrence_id,
)
report_info = _completion_report_payload(info, report_key)
report_payload: dict[str, JsonData] = {"subscribe_info": report_info}
try:
self._repository.stage_history(SubscriptionHistoryPatch.from_subscription(info))
self._repository.stage_delete_sync(subscribe_id)
if self._outbox:
now = datetime.now(timezone.utc)
self._outbox.stage(
OutboxIntent(
event_key=event_key,
topic=SUBSCRIBE_COMPLETED_TOPIC,
payload=event_payload,
),
now,
)
if notification:
self._outbox.stage(
OutboxIntent(
event_key=notification_key,
topic="subscribe.complete.notification",
payload={
"idempotency_key": notification_key,
"message": dict(notification),
},
),
now,
)
self._outbox.stage(
OutboxIntent(
event_key=report_key,
topic="subscribe.complete.report",
payload=report_payload,
),
now,
)
self._unit_of_work.commit()
except Exception:
self._unit_of_work.rollback()
raise
if notification:
if self._dispatch_store:
deliver_outbox_effect(
self._dispatch_store,
notification_key,
notify,
)
else:
notify()
else:
notify()
if self._dispatch_store:
deliver_outbox_effect(
self._dispatch_store,
event_key,
lambda: self._publish(event_payload),
)
else:
self._publish(event_payload)
if self._dispatch_store:
try:
report_delivered = deliver_outbox_effect(
self._dispatch_store,
report_key,
lambda: report(report_info),
)
except Exception as error:
logger.warning(f"订阅完成统计上报失败,将由后台重试:{error}")
else:
if report_delivered is False:
logger.warning("订阅完成统计上报未确认,将由后台重试")
else:
try:
report(report_info)
except Exception as error:
logger.warning(f"订阅完成统计上报失败:{error}")
def completion_event_key(
subscribe_id: int,
subscribe_info: Mapping[str, JsonData],
*,
occurrence_id: Optional[str] = None,
) -> str:
"""
由订阅身份和本次完成事实构造跨重试稳定且不复用的幂等键。
"""
resolved_occurrence_id = occurrence_id or uuid4().hex
return (
f"subscribe.complete:{subscribe_id}:"
f"{subscribe_info.get('media_source') or 'unknown'}:"
f"{subscribe_info.get('media_id') or 'unknown'}:{resolved_occurrence_id}:v1"
)
def completion_report_key(
subscribe_id: int,
subscribe_info: Mapping[str, JsonData],
*,
occurrence_id: Optional[str] = None,
) -> str:
"""
构造可独立重试的订阅完成统计幂等键。
"""
event_key = completion_event_key(
subscribe_id,
subscribe_info,
occurrence_id=occurrence_id,
)
return f"{event_key}:report"
def completion_notification_key(
subscribe_id: int,
subscribe_info: Mapping[str, JsonData],
*,
occurrence_id: Optional[str] = None,
) -> str:
"""
构造订阅完成通知的稳定幂等键,避免恢复时重复生成不同消息。
"""
event_key = completion_event_key(
subscribe_id,
subscribe_info,
occurrence_id=occurrence_id,
)
return f"{event_key}:notification"
def _completion_report_payload(
subscribe_info: Mapping[str, JsonData],
report_key: str,
) -> dict[str, JsonData]:
"""
保留旧统计接口字段,同时为恢复 handler 固化幂等键。
"""
return {
"media_source": subscribe_info.get("media_source"),
"media_id": subscribe_info.get("media_id"),
"season": subscribe_info.get("season"),
"idempotency_key": report_key,
}
CompletionScope = Callable[[], AbstractContextManager[CompleteSubscriptionCommand]]