diff --git a/config.py b/config.py index cac39c6..ddad0d9 100644 --- a/config.py +++ b/config.py @@ -320,7 +320,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.102" +SYSTEM_VERSION = "V10.103" LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log') public_url = PUBLIC_URL # 用於模板顯示 diff --git a/routes/edm_routes.py b/routes/edm_routes.py index c88a182..6d8bf6f 100644 --- a/routes/edm_routes.py +++ b/routes/edm_routes.py @@ -27,6 +27,9 @@ sys_log = SystemLogger("EDMRoutes").get_logger() # Blueprint 定義 edm_bp = Blueprint('edm', __name__) +_PROMO_DASHBOARD_CACHE = {} +_PROMO_DASHBOARD_CACHE_MAX = 32 + # ========================================== # 輔助函數 @@ -62,11 +65,51 @@ def load_scheduler_stats(): return {} +def _get_promo_dashboard_fingerprint(session, page_type): + """取得促銷資料指紋,用於判斷快取是否仍可復用。""" + max_id, max_crawled_at, row_count = session.query( + func.max(PromoProduct.id), + func.max(PromoProduct.crawled_at), + func.count(PromoProduct.id) + ).filter(PromoProduct.page_type == page_type).one() + return ( + max_id or 0, + max_crawled_at.isoformat() if max_crawled_at else '', + row_count or 0 + ) + + +def _get_promo_dashboard_cache_key(session, page_type, sort_by, order, requested_slot): + """建立 dashboard data 快取鍵;日期/小時避免自動時段跨時段後仍命中舊首屏。""" + now_taipei = datetime.now(TAIPEI_TZ) + auto_slot_bucket = now_taipei.strftime('%Y-%m-%d-%H') if not requested_slot else '' + return ( + page_type, + sort_by, + order, + requested_slot or '', + auto_slot_bucket, + _get_promo_dashboard_fingerprint(session, page_type) + ) + + +def _remember_promo_dashboard_data(cache_key, data): + """保留少量活動 dashboard data,避免常用時段每次重算。""" + if len(_PROMO_DASHBOARD_CACHE) >= _PROMO_DASHBOARD_CACHE_MAX: + _PROMO_DASHBOARD_CACHE.pop(next(iter(_PROMO_DASHBOARD_CACHE))) + _PROMO_DASHBOARD_CACHE[cache_key] = data + + def _build_promo_dashboard_data(session, page_type, page_name, sort_by, order, requested_slot=None): """ 通用的促銷儀表板數據建構函數 用於 edm 和 festival 兩種頁面類型 """ + cache_key = _get_promo_dashboard_cache_key(session, page_type, sort_by, order, requested_slot) + cached_data = _PROMO_DASHBOARD_CACHE.get(cache_key) + if cached_data is not None: + return cached_data + # 1. 基礎統計 last_update = session.query(PromoProduct.crawled_at).filter( PromoProduct.page_type == page_type @@ -288,7 +331,7 @@ def _build_promo_dashboard_data(session, page_type, page_name, sort_by, order, r slot_stats[slot]['on_shelf'] = on_shelf_count slot_stats[slot]['delisted_total'] = delisted_total_count - return { + data = { 'sorted_grouped_items': sorted_grouped_items, 'slot_stats': slot_stats, 'items_in_batch': items_in_batch, @@ -297,6 +340,8 @@ def _build_promo_dashboard_data(session, page_type, page_name, sort_by, order, r 'active_tab': active_tab, 'current_batch_id': current_batch_id } + _remember_promo_dashboard_data(cache_key, data) + return data # ==========================================