diff --git a/config.py b/config.py index ed19d6c..9850c90 100644 --- a/config.py +++ b/config.py @@ -320,7 +320,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.108" +SYSTEM_VERSION = "V10.109" LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log') public_url = PUBLIC_URL # 用於模板顯示 diff --git a/routes/dashboard_routes.py b/routes/dashboard_routes.py index 4cce971..62e9823 100644 --- a/routes/dashboard_routes.py +++ b/routes/dashboard_routes.py @@ -11,6 +11,7 @@ import math import time import hashlib import pickle +import threading from datetime import datetime, timezone, timedelta from flask import Blueprint, request, render_template from sqlalchemy import func, and_, text, bindparam @@ -699,6 +700,10 @@ class FileLock: _DASHBOARD_STALE_CACHE_MAX_AGE = 86400 +_DASHBOARD_REFRESH_STATE = { + 'started_at': 0, + 'running': False, +} def _new_dashboard_file_lock(): @@ -759,6 +764,38 @@ def _load_stale_full_dashboard_cache(now): ) +def _load_expired_shared_full_dashboard_cache(now): + """只讀原 shared cache 檔的過期版本;不讀 clear_cache 後搬走的 stale 檔。""" + return _load_dashboard_cache_file( + now, + _DASHBOARD_SHARED_CACHE_FILE, + allow_stale=True, + label='過期共享', + ) + + +def _trigger_dashboard_background_refresh(reason): + """使用過期 shared cache 先回首頁時,背景補一次 fresh cache,避免使用者卡 10s。""" + now_ts = time.time() + if _DASHBOARD_REFRESH_STATE['running']: + return + if now_ts - _DASHBOARD_REFRESH_STATE['started_at'] < 60: + return + + def _refresh(): + _DASHBOARD_REFRESH_STATE['running'] = True + try: + warm_full_dashboard_cache(reason=reason, force_rebuild=True) + except Exception as exc: + sys_log.warning(f"[Dashboard] [Cache] 背景預熱失敗: {exc}") + finally: + _DASHBOARD_REFRESH_STATE['running'] = False + + _DASHBOARD_REFRESH_STATE['started_at'] = now_ts + thread = threading.Thread(target=_refresh, name='dashboard-cache-refresh', daemon=True) + thread.start() + + def _write_shared_full_dashboard_cache(full_data): """原子寫入跨 worker 共享的商品看板深度快取。""" cache_file = str(_DASHBOARD_SHARED_CACHE_FILE) @@ -1031,6 +1068,11 @@ def get_full_dashboard_data(force_rebuild=False): if shared_full_data: return shared_full_data + expired_shared_data = None if force_rebuild else _load_expired_shared_full_dashboard_cache(now) + if expired_shared_data: + _trigger_dashboard_background_refresh('expired_shared_cache') + return expired_shared_data + # V-Opt: 使用檔案鎖避免多 gunicorn worker 同時計算 dashboard_lock = _new_dashboard_file_lock() lock_acquired = dashboard_lock.acquire(blocking=False)