From 42d8731ff7f32e90c15f4a4a325cf57f08332219 Mon Sep 17 00:00:00 2001 From: SmartUp Developer Date: Mon, 25 May 2026 00:19:14 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20website=5Fsync=20=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=20+=20custom=5Fpages=20origin=20TTL=20?= =?UTF-8?q?=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 ++++ backend/app/routers/custom_pages.py | 42 ++++++++++++++++++++++++++-- backend/app/services/website_sync.py | 18 ++++++++++-- 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 05fdcb8..54c24fa 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,11 @@ npm run dev # 代理到 localhost:8000 SQLite 数据库位于 `./data/app.db`,直接复制即可备份: +> **WAL 模式说明**:启用 WAL 后,备份前请先执行 checkpoint 将 WAL 合并入主库: +> ```bash +> sqlite3 ./data/app.db "PRAGMA wal_checkpoint(TRUNCATE);" +> ``` + ```bash cp ./data/app.db ./data/app.db.$(date +%Y%m%d) ``` diff --git a/backend/app/routers/custom_pages.py b/backend/app/routers/custom_pages.py index 0aae3ba..1c83791 100644 --- a/backend/app/routers/custom_pages.py +++ b/backend/app/routers/custom_pages.py @@ -321,13 +321,49 @@ def _same_origin(a: str, b: str) -> bool: return _origin(a).rstrip("/") == _origin(b).rstrip("/") +# ── TTL 缓存:origin → upstream_id(30 秒自动过期,无手动 invalidate)── +import time as _time +from functools import wraps as _wraps + +def _ttl_cache(ttl_seconds: int = 30, maxsize: int = 64): + def decorator(fn): + cache: dict = {} + @_wraps(fn) + def wrapper(*args): + key = args + now = _time.monotonic() + if key in cache: + value, ts = cache[key] + if now - ts < ttl_seconds: + return value + value = fn(*args) + if len(cache) >= maxsize: + cache.pop(next(iter(cache))) + cache[key] = (value, now) + return value + return wrapper + return decorator + + def _find_matching_upstream(db: Session, page: CustomPage) -> Optional[Upstream]: page_origin = _origin(page.url) if not page_origin: return None - for upstream in db.query(Upstream).order_by(Upstream.id).all(): - if _origin(upstream.base_url) == page_origin: - return upstream + upstream_id = _origin_to_upstream_id(page_origin) + if upstream_id is None: + return None + return db.query(Upstream).filter(Upstream.id == upstream_id).first() + + +@_ttl_cache(ttl_seconds=30, maxsize=64) +def _origin_to_upstream_id(origin: str) -> Optional[int]: + """按 origin 查匹配的上游 ID,结果缓存 30 秒。""" + from app.database import SessionLocal + from app.models.upstream import Upstream + with SessionLocal() as sess: + for upstream in sess.query(Upstream).order_by(Upstream.id).all(): + if _origin(upstream.base_url) == origin: + return upstream.id return None diff --git a/backend/app/services/website_sync.py b/backend/app/services/website_sync.py index dab1152..0c570b6 100644 --- a/backend/app/services/website_sync.py +++ b/backend/app/services/website_sync.py @@ -96,13 +96,27 @@ def sync_binding(db: Session, binding: WebsiteGroupBinding, write: bool = True) if not website: raise WebsiteError("网站不存在") sources = binding_sources(binding) + # ── 批量预查:收集所有上游 ID,一次查询上游名称 ── + upstream_ids = {int(s.get("upstream_id") or 0) for s in sources if s.get("upstream_id")} + upstreams = {} + if upstream_ids: + rows = db.query(Upstream).filter(Upstream.id.in_(upstream_ids)).all() + upstreams = {u.id: u for u in rows} + # ── 同一轮 sync 内的快照缓存(调用级,函数返回即释放)── + _snap_cache: dict[int, dict[str, Any]] = {} + + def _get_snap(upstream_id: int) -> dict[str, Any]: + if upstream_id not in _snap_cache: + _snap_cache[upstream_id] = latest_rate_map(db, upstream_id) + return _snap_cache[upstream_id] + source_rates: list[dict[str, Any]] = [] for source in sources: upstream_id = int(source.get("upstream_id") or 0) group_id = str(source.get("group_id") or "") - groups = latest_rate_map(db, upstream_id) + groups = _get_snap(upstream_id) group = groups.get(group_id) if group_id else None - upstream = db.query(Upstream).filter(Upstream.id == upstream_id).first() + upstream = upstreams.get(upstream_id) source_rates.append({ "upstream_id": upstream_id, "upstream_name": source.get("upstream_name") or (upstream.name if upstream else ""),