refactor: own subscription create transactions

This commit is contained in:
jxxghp
2026-08-21 20:37:31 +08:00
parent de2957b9de
commit 7de72e0e83
11 changed files with 840 additions and 120 deletions
+166 -92
View File
@@ -3,6 +3,7 @@ import json
import random
import threading
import time
from dataclasses import dataclass
from datetime import datetime
from typing import Any, Callable, Dict, List, Optional, Union, Tuple
@@ -88,6 +89,24 @@ SystemConfigOper = get_configured_system_config
_DEFAULT_SYSTEM_CONFIG_PROVIDER = get_configured_system_config
@dataclass(frozen=True, slots=True)
class _SubscribePostCommitContext:
"""订阅提交后副作用所需的不可变业务快照。"""
title: str
year: str
metainfo: MetaBase
mediainfo: MediaInfo
media_source: Optional[MediaSource]
media_id: Optional[str]
season: Optional[int]
channel: Optional[NotificationChannel]
source: Optional[str]
userid: Optional[str]
username: Optional[str]
message: bool
def _system_config():
"""返回配置端口,并兼容旧测试对本地别名的替换。"""
if SystemConfigOper is not _DEFAULT_SYSTEM_CONFIG_PROVIDER:
@@ -851,6 +870,98 @@ class SubscribeChain(MusicSubscribeMixin, InteractionChainMixin, ChainBase):
})
return defaults
@staticmethod
def __subscribe_added_link(mtype: MediaType) -> str:
"""返回订阅类型对应的前端详情入口。"""
if mtype == MediaType.TV:
return settings.MP_DOMAIN('#/subscribe/tv?tab=mysub')
if mtype == MediaType.MUSIC:
return settings.MP_DOMAIN('#/subscribe/music?tab=mysub')
return settings.MP_DOMAIN('#/subscribe/movie?tab=mysub')
@staticmethod
def __subscribe_report_payload(context: _SubscribePostCommitContext) -> dict:
"""构造保持旧字段和值语义的订阅统计上报。"""
mediainfo = context.mediainfo
music_type = getattr(mediainfo, "music_type", None)
return {
"name": context.title,
"year": context.year,
"type": context.metainfo.type.value,
"media_source": context.media_source,
"media_id": context.media_id,
"music_type": music_type,
"total_tracks": getattr(mediainfo, "total_tracks", None)
if music_type == MUSIC_ENTITY_ALBUM else None,
"season": context.season,
"poster": mediainfo.get_poster_image(),
"backdrop": mediainfo.get_backdrop_image(),
"vote": mediainfo.vote_average,
"description": mediainfo.overview,
}
def __post_subscribe_added(
self,
subscribe_id: int,
context: _SubscribePostCommitContext,
) -> None:
"""同步执行提交后消息、事件和统计,异常不再触碰数据库事务。"""
if context.message:
self.post_message(
_SchemaMessage(
channel=context.channel,
source=context.source,
mtype=MessageType.Subscribe,
ctype=ContentType.SubscribeAdded,
image=context.mediainfo.get_message_image(),
link=self.__subscribe_added_link(context.mediainfo.type),
userid=context.userid,
username=context.username,
),
meta=context.metainfo,
mediainfo=context.mediainfo,
username=context.username,
)
eventmanager.send_event(EventType.SubscribeAdded, {
"subscribe_id": subscribe_id,
"username": context.username,
"mediainfo": context.mediainfo.to_dict(),
})
MoviePilotServerHelper.sub_reg_async(
self.__subscribe_report_payload(context)
)
async def __async_post_subscribe_added(
self,
subscribe_id: int,
context: _SubscribePostCommitContext,
) -> None:
"""异步执行提交后消息、事件和统计,保持与同步入口相同顺序。"""
if context.message:
await self.async_post_message(
_SchemaMessage(
channel=context.channel,
source=context.source,
mtype=MessageType.Subscribe,
ctype=ContentType.SubscribeAdded,
image=context.mediainfo.get_message_image(),
link=self.__subscribe_added_link(context.mediainfo.type),
userid=context.userid,
username=context.username,
),
meta=context.metainfo,
mediainfo=context.mediainfo,
username=context.username,
)
await eventmanager.async_send_event(EventType.SubscribeAdded, {
"subscribe_id": subscribe_id,
"username": context.username,
"mediainfo": context.mediainfo.to_dict(),
})
await MoviePilotServerHelper.async_sub_reg(
self.__subscribe_report_payload(context)
)
def add(self, title: str, year: str,
mtype: MediaType = None,
episode_group: Optional[str] = None,
@@ -992,8 +1103,33 @@ class SubscribeChain(MusicSubscribeMixin, InteractionChainMixin, ChainBase):
# 添加订阅
kwargs.update(self.__get_default_kwargs(mediainfo.type, **kwargs))
post_commit_context = _SubscribePostCommitContext(
title=title,
year=year,
metainfo=metainfo,
mediainfo=mediainfo,
media_source=media_source,
media_id=media_id,
season=season,
channel=channel,
source=source,
userid=userid,
username=username,
message=bool(message),
)
def _after_commit(subscribe_id: int) -> None:
"""把同步提交后的副作用委托给单一顺序实现。"""
self.__post_subscribe_added(subscribe_id, post_commit_context)
# 操作数据库
sid, err_msg = add_subscribe(mediainfo=mediainfo, season=season, username=username, **kwargs)
sid, err_msg = add_subscribe(
mediainfo=mediainfo,
season=season,
username=username,
after_commit=_after_commit,
**kwargs,
)
if not sid:
logger.error(f'{mediainfo.title_year} {err_msg}')
if not exist_ok and message:
@@ -1007,51 +1143,6 @@ class SubscribeChain(MusicSubscribeMixin, InteractionChainMixin, ChainBase):
image=mediainfo.get_message_image(),
userid=userid))
return None, err_msg
elif message:
if mediainfo.type == MediaType.TV:
link = settings.MP_DOMAIN('#/subscribe/tv?tab=mysub')
elif mediainfo.type == MediaType.MUSIC:
link = settings.MP_DOMAIN('#/subscribe/music?tab=mysub')
else:
link = settings.MP_DOMAIN('#/subscribe/movie?tab=mysub')
# 订阅成功按规则发送消息
self.post_message(
_SchemaMessage(
channel=channel,
source=source,
mtype=MessageType.Subscribe,
ctype=ContentType.SubscribeAdded,
image=mediainfo.get_message_image(),
link=link,
userid=userid,
username=username
),
meta=metainfo,
mediainfo=mediainfo,
username=username
)
# 发送事件
eventmanager.send_event(EventType.SubscribeAdded, {
"subscribe_id": sid,
"username": username,
"mediainfo": mediainfo.to_dict(),
})
# 统计订阅
MoviePilotServerHelper.sub_reg_async({
"name": title,
"year": year,
"type": metainfo.type.value,
"media_source": media_source,
"media_id": media_id,
"music_type": getattr(mediainfo, "music_type", None),
"total_tracks": getattr(mediainfo, "total_tracks", None)
if getattr(mediainfo, "music_type", None) == MUSIC_ENTITY_ALBUM else None,
"season": season,
"poster": mediainfo.get_poster_image(),
"backdrop": mediainfo.get_backdrop_image(),
"vote": mediainfo.vote_average,
"description": mediainfo.overview
})
# 返回结果
return sid, err_msg
@@ -1196,8 +1287,36 @@ class SubscribeChain(MusicSubscribeMixin, InteractionChainMixin, ChainBase):
# 列新默认参数
kwargs.update(self.__get_default_kwargs(mediainfo.type, **kwargs))
post_commit_context = _SubscribePostCommitContext(
title=title,
year=year,
metainfo=metainfo,
mediainfo=mediainfo,
media_source=media_source,
media_id=media_id,
season=season,
channel=channel,
source=source,
userid=userid,
username=username,
message=bool(message),
)
async def _after_commit(subscribe_id: int) -> None:
"""把异步提交后的副作用委托给单一顺序实现。"""
await self.__async_post_subscribe_added(
subscribe_id,
post_commit_context,
)
# 操作数据库
sid, err_msg = await async_add_subscribe(mediainfo=mediainfo, season=season, username=username, **kwargs)
sid, err_msg = await async_add_subscribe(
mediainfo=mediainfo,
season=season,
username=username,
after_commit=_after_commit,
**kwargs,
)
if not sid:
logger.error(f'{mediainfo.title_year} {err_msg}')
if not exist_ok and message:
@@ -1211,51 +1330,6 @@ class SubscribeChain(MusicSubscribeMixin, InteractionChainMixin, ChainBase):
image=mediainfo.get_message_image(),
userid=userid))
return None, err_msg
elif message:
if mediainfo.type == MediaType.TV:
link = settings.MP_DOMAIN('#/subscribe/tv?tab=mysub')
elif mediainfo.type == MediaType.MUSIC:
link = settings.MP_DOMAIN('#/subscribe/music?tab=mysub')
else:
link = settings.MP_DOMAIN('#/subscribe/movie?tab=mysub')
# 订阅成功按规则发送消息
await self.async_post_message(
_SchemaMessage(
channel=channel,
source=source,
mtype=MessageType.Subscribe,
ctype=ContentType.SubscribeAdded,
image=mediainfo.get_message_image(),
link=link,
userid=userid,
username=username
),
meta=metainfo,
mediainfo=mediainfo,
username=username
)
# 发送事件
await eventmanager.async_send_event(EventType.SubscribeAdded, {
"subscribe_id": sid,
"username": username,
"mediainfo": mediainfo.to_dict(),
})
# 统计订阅
await MoviePilotServerHelper.async_sub_reg({
"name": title,
"year": year,
"type": metainfo.type.value,
"media_source": media_source,
"media_id": media_id,
"music_type": getattr(mediainfo, "music_type", None),
"total_tracks": getattr(mediainfo, "total_tracks", None)
if getattr(mediainfo, "music_type", None) == MUSIC_ENTITY_ALBUM else None,
"season": season,
"poster": mediainfo.get_poster_image(),
"backdrop": mediainfo.get_backdrop_image(),
"vote": mediainfo.vote_average,
"description": mediainfo.overview
})
# 返回结果
return sid, err_msg