Files
jxxghp 6ca7b294a0 fix(download): 共享站点流控并保留频控错误原因 (#7246)
* fix(download): share site limits and preserve frequency errors

* test(startup): record shared site throttle module
2026-10-30 14:53:32 +08:00

249 lines
7.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
站点及站点运行数据的只读应用服务。
"""
from __future__ import annotations
import builtins
from typing import Optional
from app.application.site.contract import SiteQueryPort
from app.schemas.site import Site, SiteIconData, SiteStatistic, SiteUserData
class SiteQueryService:
"""
把站点持久化快照投影为 API/Chain 可复用的 DTO。
"""
def __init__(self, repository: SiteQueryPort) -> None:
"""
保存站点查询仓储端口。
"""
self._repository = repository
async def list_ordered(
self,
*,
is_active: Optional[bool] = None,
name: Optional[str] = None,
site_ids: Optional[builtins.list[int]] = None,
domains: Optional[builtins.list[str]] = None,
page: Optional[int] = None,
count: Optional[int] = None,
) -> builtins.list[Site]:
"""
按筛选、优先级和可选分页窗口返回配置 DTO。
"""
if all(
value is None
for value in (is_active, name, site_ids, domains, page, count)
):
items = await self._repository.async_list_order_by_pri()
else:
items = await self._repository.async_list_order_by_pri(
is_active=is_active,
name=name,
site_ids=site_ids,
domains=domains,
page=page,
count=count,
)
return [Site.model_validate(item) for item in items]
async def count_ordered(
self,
*,
is_active: Optional[bool] = None,
name: Optional[str] = None,
site_ids: Optional[builtins.list[int]] = None,
domains: Optional[builtins.list[str]] = None,
) -> int:
"""
按与站点列表一致的筛选条件返回总数。
"""
return await self._repository.async_count_sites(
is_active=is_active,
name=name,
site_ids=site_ids,
domains=domains,
)
async def list(self) -> builtins.list[Site]:
"""
返回全部站点配置 DTO。
"""
return [Site.model_validate(item) for item in await self._repository.async_list()]
async def get(self, site_id: int) -> Optional[Site]:
"""
按 ID 返回站点配置 DTO。
"""
item = await self._repository.async_get(site_id)
return Site.model_validate(item) if item else None
def get_sync(self, site_id: int) -> Optional[Site]:
"""
同步按 ID 返回站点配置 DTO。
"""
item = self._repository.get(site_id)
return Site.model_validate(item) if item else None
async def get_by_domain(self, domain: str) -> Optional[Site]:
"""
按域名返回站点配置 DTO。
"""
item = await self._repository.async_get_by_domain(domain)
return Site.model_validate(item) if item else None
def get_by_domain_sync(self, domain: str) -> Optional[Site]:
"""
同步按规范域名读取站点配置,供独立种子下载匹配流控规则。
:param domain: 请求地址提取出的规范站点域名
:return: 站点配置;没有匹配站点时为 None
"""
item = self._repository.get_by_domain(domain)
return Site.model_validate(item) if item else None
async def userdata_latest(
self,
*,
page: Optional[int] = None,
count: Optional[int] = None,
) -> builtins.list[SiteUserData]:
"""
按可选分页窗口返回各站点最新用户数据 DTO。
"""
if page is None and count is None:
items = await self._repository.async_get_userdata_latest()
else:
items = await self._repository.async_get_userdata_latest(
page=page,
count=count,
)
return [SiteUserData.model_validate(item) for item in items]
async def count_userdata_latest(self) -> int:
"""
返回各站点最新用户数据查询的结果总数。
"""
return await self._repository.async_count_userdata_latest()
async def userdata(
self,
domain: str,
workdate: Optional[str] = None,
*,
page: Optional[int] = None,
count: Optional[int] = None,
) -> builtins.list[SiteUserData]:
"""
按可选分页窗口返回指定站点用户数据 DTO。
"""
if page is None and count is None:
items = await self._repository.async_get_userdata_by_domain(
domain,
workdate,
)
else:
items = await self._repository.async_get_userdata_by_domain(
domain,
workdate,
page=page,
count=count,
)
return [
SiteUserData.model_validate(item)
for item in items
]
async def count_userdata(
self,
domain: str,
workdate: Optional[str] = None,
) -> int:
"""
返回指定站点和日期的用户数据总数。
"""
return await self._repository.async_count_userdata_by_domain(
domain,
workdate,
)
async def icon(self, domain: str) -> Optional[SiteIconData]:
"""
返回站点图标 DTO。
"""
item = await self._repository.async_get_icon_by_domain(domain)
if not item:
return None
return SiteIconData(
icon=item.base64 if item.base64 else item.url,
)
async def statistic(self, domain: str) -> SiteStatistic:
"""
返回指定站点统计 DTO,未命中时返回空统计。
"""
item = await self._repository.async_get_statistic_by_domain(domain)
return SiteStatistic.model_validate(item) if item else SiteStatistic(domain=domain)
async def statistics(
self,
*,
page: Optional[int] = None,
count: Optional[int] = None,
) -> builtins.list[SiteStatistic]:
"""
按可选分页窗口返回站点统计 DTO。
"""
if page is None and count is None:
items = await self._repository.async_list_statistics()
else:
items = await self._repository.async_list_statistics(
page=page,
count=count,
)
return [SiteStatistic.model_validate(item) for item in items]
async def count_statistics(self) -> int:
"""
返回站点统计记录总数。
"""
return await self._repository.async_count_statistics()
def userdata_latest_sync(self) -> builtins.list[SiteUserData]:
"""
同步返回各站点最新用户数据 DTO。
"""
return [SiteUserData.model_validate(item) for item in self._repository.get_userdata_latest()]
_configured_site_query_service: SiteQueryService | None = None
def configure_site_query_service(service: SiteQueryService) -> None:
"""
由启动组合根登记站点查询服务。
"""
global _configured_site_query_service
_configured_site_query_service = service
def reset_site_query_service() -> None:
"""
清除当前 lifespan 的站点查询服务。
"""
global _configured_site_query_service
_configured_site_query_service = None
def get_configured_site_query_service() -> SiteQueryService:
"""
返回启动阶段登记的站点查询服务。
"""
if _configured_site_query_service is None:
raise RuntimeError("站点查询服务尚未配置")
return _configured_site_query_service