feat(web): cache dashboard aggregations with version invalidation

This commit is contained in:
kairee committed 2026-11-01 03:27:49 +08:00
1 parent 91aa3e1f95
commit 9fd277c05f
15 files changed
+399 -30

No files matched your search

+115
View File
@@ -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")