feat(web): cache dashboard aggregations with version invalidation
This commit is contained in:
1 parent
91aa3e1f95
commit
9fd277c05f
15 files changed
+399
-30
No files matched your search
@@ -28,6 +28,7 @@ from ..analytics import (
|
||||
from ..config import ConfigStore
|
||||
from ..database import Database
|
||||
from ..timeutil import day_bounds, next_daily_run, shanghai_date, shanghai_datetime
|
||||
from .cache import DASHBOARD_CACHE, TTL_FANS, TTL_VIDEOS, build_cache_key
|
||||
|
||||
TRACKED_STATUSES = ("active", "unfaved")
|
||||
VIDEO_SORTS = {
|
||||
@@ -179,6 +180,30 @@ class DashboardQueries:
|
||||
self.db = db
|
||||
self.config_store = config_store
|
||||
self._clock = clock
|
||||
self._cache_scope = str(getattr(db, "path", "db"))
|
||||
|
||||
def _cache_version(self, group: str) -> int:
|
||||
def run(conn: sqlite3.Connection) -> int:
|
||||
row = conn.execute(
|
||||
"SELECT v FROM kv WHERE k = ?", (f"cache:v:{group}",)
|
||||
).fetchone()
|
||||
if row is None:
|
||||
return 0
|
||||
try:
|
||||
return int(str(row["v"]))
|
||||
except ValueError:
|
||||
return 0
|
||||
return self.db.read(run)
|
||||
|
||||
def _cached(self, group: str, params: dict[str, Any], ttl: float, builder):
|
||||
version = self._cache_version(group)
|
||||
key = build_cache_key(self._cache_scope, group, version, params)
|
||||
hit = DASHBOARD_CACHE.get(key)
|
||||
if hit is not None:
|
||||
return hit
|
||||
value = builder()
|
||||
DASHBOARD_CACHE.set(key, ttl, value)
|
||||
return value
|
||||
|
||||
# ------------------------------------------------------------ helpers
|
||||
|
||||
@@ -358,6 +383,9 @@ class DashboardQueries:
|
||||
# ------------------------------------------------------------ overview
|
||||
|
||||
def overview(self) -> dict[str, Any]:
|
||||
return self._cached("videos", {}, TTL_VIDEOS, self._overview_uncached)
|
||||
|
||||
def _overview_uncached(self) -> dict[str, Any]:
|
||||
def run(conn: sqlite3.Connection) -> dict[str, Any]:
|
||||
latest = self._latest_date(conn)
|
||||
snapshot_days = self._snapshot_days(conn)
|
||||
@@ -602,6 +630,38 @@ class DashboardQueries:
|
||||
q: str | None = None,
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
) -> dict[str, Any]:
|
||||
params = {
|
||||
"sort": sort,
|
||||
"within_days": within_days,
|
||||
"growth_days": growth_days,
|
||||
"folder_id": folder_id,
|
||||
"q": q or "",
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
}
|
||||
return self._cached(
|
||||
"videos", params, TTL_VIDEOS, lambda: self._list_videos_uncached(
|
||||
sort=sort,
|
||||
within_days=within_days,
|
||||
growth_days=growth_days,
|
||||
folder_id=folder_id,
|
||||
q=q,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
),
|
||||
)
|
||||
|
||||
def _list_videos_uncached(
|
||||
self,
|
||||
*,
|
||||
sort: str = "heat",
|
||||
within_days: int | None = None,
|
||||
growth_days: int = 7,
|
||||
folder_id: int | None = None,
|
||||
q: str | None = None,
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
) -> dict[str, Any]:
|
||||
if sort not in VIDEO_SORTS:
|
||||
raise ValueError(f"不支持的排序: {sort}")
|
||||
@@ -809,6 +869,35 @@ class DashboardQueries:
|
||||
min_videos: int = 1,
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
) -> dict[str, Any]:
|
||||
params = {
|
||||
"sort": sort,
|
||||
"within_days": within_days,
|
||||
"q": q or "",
|
||||
"min_videos": min_videos,
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
}
|
||||
return self._cached(
|
||||
"videos", params, TTL_VIDEOS, lambda: self._list_ups_uncached(
|
||||
sort=sort,
|
||||
within_days=within_days,
|
||||
q=q,
|
||||
min_videos=min_videos,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
),
|
||||
)
|
||||
|
||||
def _list_ups_uncached(
|
||||
self,
|
||||
*,
|
||||
sort: str = "avg_views",
|
||||
within_days: int | None = None,
|
||||
q: str | None = None,
|
||||
min_videos: int = 1,
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
) -> dict[str, Any]:
|
||||
if sort not in UP_SORTS:
|
||||
raise ValueError(f"不支持的 UP 排序: {sort}")
|
||||
@@ -981,6 +1070,32 @@ class DashboardQueries:
|
||||
start: str | None = None,
|
||||
end: str | None = None,
|
||||
recent_days: int = 14,
|
||||
) -> dict[str, Any]:
|
||||
params = {
|
||||
"mid": mid if mid is not None else "",
|
||||
"days": days,
|
||||
"start": start or "",
|
||||
"end": end or "",
|
||||
"recent_days": recent_days,
|
||||
}
|
||||
return self._cached(
|
||||
"fans", params, TTL_FANS, lambda: self._fans_uncached(
|
||||
mid=mid,
|
||||
days=days,
|
||||
start=start,
|
||||
end=end,
|
||||
recent_days=recent_days,
|
||||
),
|
||||
)
|
||||
|
||||
def _fans_uncached(
|
||||
self,
|
||||
mid: int | None = None,
|
||||
*,
|
||||
days: int = 90,
|
||||
start: str | None = None,
|
||||
end: str | None = None,
|
||||
recent_days: int = 14,
|
||||
) -> dict[str, Any]:
|
||||
if days < 1:
|
||||
raise ValueError("days 必须大于 0")
|
||||
|
||||
Reference in new issue
Block a user