refactor: 收口 V3 分层架构与插件兼容边界

This commit is contained in:
jxxghp
2026-08-18 13:22:02 +08:00
parent cca99bd421
commit 8472bcff43
274 changed files with 10730 additions and 6130 deletions
+143 -1
View File
@@ -8,6 +8,7 @@ from app.domain.context import MediaInfo
from app.domain.meta.metabase import MetaBase
from app.schemas.media import resolve_media_identity
from app.schemas.types import MediaType
from app.schemas.workflow import Subscribe as SubscribeView
class SubscriptionQueryRepository(Protocol):
@@ -26,6 +27,54 @@ class SubscriptionQueryRepository(Protocol):
...
class AsyncSubscriptionQueryRepository(Protocol):
"""公开订阅查询所需的异步持久化端口。"""
async def async_list(self) -> list[Any]:
"""读取全部订阅。"""
...
async def async_list_by_username(self, username: str) -> list[Any]:
"""读取指定用户订阅。"""
...
async def async_get(self, subscribe_id: int) -> Optional[Any]:
"""按 ID 读取订阅。"""
...
async def async_list_by_media_identity(
self,
media_source: Any,
media_id: str,
music_type: Optional[str] = None,
) -> list[Any]:
"""按规范媒体身份读取订阅。"""
...
class AsyncSubscriptionHistoryQueryRepository(Protocol):
"""订阅历史公开查询所需的异步持久化端口。"""
async def async_list_by_type(
self,
mtype: str,
page: int = 1,
count: int = 30,
) -> list[Any]:
"""按媒体类型分页读取订阅历史。"""
...
async def async_list_by_type_and_username(
self,
mtype: str,
username: str,
page: int = 1,
count: int = 30,
) -> list[Any]:
"""按媒体类型和用户分页读取订阅历史。"""
...
class SubscriptionQueryService:
"""封装不修改订阅状态的三个公开查询用例。"""
@@ -37,9 +86,102 @@ class SubscriptionQueryService:
"music_type",
}
def __init__(self, repository: SubscriptionQueryRepository) -> None:
def __init__(
self,
repository: SubscriptionQueryRepository,
*,
async_repository: Optional[AsyncSubscriptionQueryRepository] = None,
history_repository: Optional[AsyncSubscriptionHistoryQueryRepository] = None,
) -> None:
"""保存订阅查询仓储端口。"""
self._repository = repository
self._async_repository = async_repository
self._history_repository = history_repository
async def list_public(
self,
username: Optional[str] = None,
) -> list[SubscribeView]:
"""读取公开订阅列表并转换为稳定 DTO。"""
if self._async_repository is None:
raise RuntimeError("异步订阅查询端口未注册")
if username:
records = await self._async_repository.async_list_by_username(
username=username
)
else:
records = await self._async_repository.async_list()
return [SubscribeView.model_validate(record) for record in records]
async def get_public(self, subscribe_id: int) -> Optional[SubscribeView]:
"""按 ID 读取订阅 DTO。"""
if self._async_repository is None:
raise RuntimeError("异步订阅查询端口未注册")
record = await self._async_repository.async_get(subscribe_id)
return SubscribeView.model_validate(record) if record else None
async def list_by_media_identity(
self,
media_source: Any,
media_id: str,
music_type: Optional[str] = None,
) -> list[SubscribeView]:
"""按媒体身份读取订阅 DTO,并兼容旧音乐记录。"""
if self._async_repository is None:
raise RuntimeError("异步订阅查询端口未注册")
records = await self._async_repository.async_list_by_media_identity(
media_source=media_source,
media_id=media_id,
music_type=music_type,
)
return [
SubscribeView.model_validate(record)
for record in records
if self._matches_music_type(record, music_type)
]
async def list_history(
self,
mtype: str,
*,
page: int = 1,
count: int = 30,
username: Optional[str] = None,
) -> list[SubscribeView]:
"""分页读取订阅历史 DTO。"""
if self._history_repository is None:
raise RuntimeError("订阅历史查询端口未注册")
if username:
records = await self._history_repository.async_list_by_type_and_username(
mtype,
username,
page,
count,
)
else:
records = await self._history_repository.async_list_by_type(
mtype,
page,
count,
)
result = []
for record in records:
item = SubscribeView.model_validate(record)
if item.type == MediaType.TV.value:
item.total_episode = 0
item.lack_episode = 0
result.append(item)
return result
@staticmethod
def _matches_music_type(record: Any, music_type: Optional[str]) -> bool:
"""把迁移前未标注音乐类型的记录兼容为单曲。"""
if not music_type:
return True
value = getattr(record, "music_type", None)
return value == music_type or (
music_type == "recording" and value is None
)
def exists(
self,