共用業績分析頁面快取
All checks were successful
CD Pipeline / deploy (push) Successful in 57s

This commit is contained in:
OoO
2026-05-13 12:15:27 +08:00
parent 5ee7fd9a17
commit f8b9b1abf7
3 changed files with 69 additions and 12 deletions

View File

@@ -320,7 +320,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
# ==========================================
# 系統版本與路徑
# ==========================================
SYSTEM_VERSION = "V10.109"
SYSTEM_VERSION = "V10.110"
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
public_url = PUBLIC_URL # 用於模板顯示

View File

@@ -11,6 +11,8 @@
import hashlib
import io
import math
import os
import pickle
import time
import traceback
from datetime import datetime, timezone, timedelta
@@ -20,7 +22,7 @@ from sqlalchemy import inspect, text
import pandas as pd
import numpy as np
from config import BASE_DIR, DATABASE_TYPE
from config import BASE_DIR, DATABASE_TYPE, SYSTEM_VERSION
from database.manager import DatabaseManager
from services.logger_manager import SystemLogger
from services.daily_sales_service import prepare_marketing_summary
@@ -28,6 +30,7 @@ from services.cache_manager import (
_SALES_PROCESSED_CACHE,
_SALES_OPTIONS_CACHE,
_SALES_ANALYSIS_RESULT_CACHE,
_SALES_ANALYSIS_PAGE_CACHE_DIR,
set_sales_processed_cache,
)
from utils.text_helpers import get_color_for_string
@@ -47,6 +50,7 @@ _SALES_PREVIEW_CACHE_TTL = 600
_SALES_OPTIONS_CACHE_TTL = 1800
_SALES_PAGE_CONTEXT_CACHE_TTL = 180
_SALES_PAGE_CONTEXT_CACHE_MAX = 24
_SALES_SHARED_PAGE_CONTEXT_CACHE_TTL = 1800
# ==========================================
@@ -101,6 +105,45 @@ def _set_sales_page_context_cache(cache_key, context):
)
def _sales_page_context_cache_file(cache_key):
return _SALES_ANALYSIS_PAGE_CACHE_DIR / f"{cache_key}.pkl"
def _get_sales_shared_page_context_cache(cache_key):
path = _sales_page_context_cache_file(cache_key)
if not path.exists():
return None
try:
if time.time() - path.stat().st_mtime >= _SALES_SHARED_PAGE_CONTEXT_CACHE_TTL:
path.unlink(missing_ok=True)
return None
with open(path, 'rb') as f:
payload = pickle.load(f)
if payload.get('version') != SYSTEM_VERSION:
return None
return payload.get('context')
except Exception as exc:
sys_log.warning(f"[Sales Analysis] 共享頁面快取讀取失敗: {exc}")
return None
def _set_sales_shared_page_context_cache(cache_key, context):
path = _sales_page_context_cache_file(cache_key)
tmp_path = path.with_suffix(f".{os.getpid()}.tmp")
try:
os.makedirs(path.parent, exist_ok=True)
with open(tmp_path, 'wb') as f:
pickle.dump({'version': SYSTEM_VERSION, 'context': context}, f, protocol=pickle.HIGHEST_PROTOCOL)
os.replace(tmp_path, path)
except Exception as exc:
sys_log.warning(f"[Sales Analysis] 共享頁面快取寫入失敗: {exc}")
try:
if tmp_path.exists():
tmp_path.unlink()
except OSError:
pass
def _format_sales_data_range(min_date, max_date):
if not min_date or not max_date:
return ''
@@ -854,6 +897,19 @@ def sales_analysis():
else:
cache_key = f"{table_name}_{data_range_months}m"
page_cache_key = "sales_analysis:page_context:" + _sales_analysis_args_fingerprint(
table_name,
cache_key,
SYSTEM_VERSION,
)
cached_context = (
_get_sales_page_context_cache(page_cache_key)
or _get_sales_shared_page_context_cache(page_cache_key)
)
if cached_context:
_set_sales_page_context_cache(page_cache_key, cached_context)
return render_template('sales_analysis.html', **cached_context)
# 2. 讀取與處理資料 (V-Opt: 使用二級快取機制 Raw -> Processed)
df = None
cols_map = {}
@@ -1161,16 +1217,6 @@ def sales_analysis():
}
set_sales_processed_cache(cache_key, cache_entry, aliases=(table_name,))
processed_entry = _SALES_PROCESSED_CACHE.get(cache_key, {})
page_cache_key = "sales_analysis:page_context:" + _sales_analysis_args_fingerprint(
table_name,
cache_key,
processed_entry.get('time'),
)
cached_context = _get_sales_page_context_cache(page_cache_key)
if cached_context:
return render_template('sales_analysis.html', **cached_context)
# 🚩 V-Opt: 使用共用篩選函式
target_df, cols_map, err = _get_filtered_sales_data(cache_key)
if err:
@@ -1705,6 +1751,7 @@ def sales_analysis():
'db_data_range': db_data_range,
}
_set_sales_page_context_cache(page_cache_key, context)
_set_sales_shared_page_context_cache(page_cache_key, context)
return render_template('sales_analysis.html', **context)
except Exception as e:

View File

@@ -69,6 +69,7 @@ _DASHBOARD_CACHE_TTL = 1800
_BASE_DIR = Path(__file__).resolve().parents[1]
_DASHBOARD_SHARED_CACHE_FILE = _BASE_DIR / "data" / "dashboard_full_cache.pkl"
_DASHBOARD_STALE_CACHE_FILE = _BASE_DIR / "data" / "dashboard_full_cache_stale.pkl"
_SALES_ANALYSIS_PAGE_CACHE_DIR = _BASE_DIR / "data" / "sales_analysis_page_cache"
def cleanup_sales_cache():
@@ -116,6 +117,15 @@ def clear_sales_cache():
_SALES_PROCESSED_CACHE.clear()
_SALES_OPTIONS_CACHE.clear()
_SALES_ANALYSIS_RESULT_CACHE.clear()
try:
if _SALES_ANALYSIS_PAGE_CACHE_DIR.exists():
for cache_file in _SALES_ANALYSIS_PAGE_CACHE_DIR.glob("*.pkl"):
try:
cache_file.unlink()
except OSError:
logger.debug("sales analysis page cache file cleanup failed: %s", cache_file, exc_info=True)
except OSError:
logger.debug("sales analysis page cache cleanup failed", exc_info=True)
def clear_daily_sales_cache():