快取當日業績頁面內容
All checks were successful
CD Pipeline / deploy (push) Successful in 55s

This commit is contained in:
OoO
2026-05-13 11:35:28 +08:00
parent 2ac751f0e5
commit 6c236ebad0
2 changed files with 64 additions and 19 deletions

View File

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

View File

@@ -38,11 +38,39 @@ sys_log = SystemLogger("DailySalesRoutes").get_logger()
daily_sales_bp = Blueprint('daily_sales', __name__)
_CACHE_EXPIRY_SECONDS = 300 # 5 分鐘緩存過期
_VIEW_CACHE_EXPIRY_SECONDS = 120
_DAILY_SALES_VIEW_CACHE = {}
_DAILY_SALES_VIEW_CACHE_MAX = 24
def _get_daily_view_cache(cache_key):
cache_data = _DAILY_SALES_VIEW_CACHE.get(cache_key)
if not cache_data:
return None
elapsed = (datetime.now() - cache_data['timestamp']).total_seconds()
if elapsed >= _VIEW_CACHE_EXPIRY_SECONDS:
_DAILY_SALES_VIEW_CACHE.pop(cache_key, None)
return None
return cache_data['context']
def _set_daily_view_cache(cache_key, context):
if len(_DAILY_SALES_VIEW_CACHE) >= _DAILY_SALES_VIEW_CACHE_MAX:
oldest_key = min(
_DAILY_SALES_VIEW_CACHE,
key=lambda key: _DAILY_SALES_VIEW_CACHE[key]['timestamp']
)
_DAILY_SALES_VIEW_CACHE.pop(oldest_key, None)
_DAILY_SALES_VIEW_CACHE[cache_key] = {
'context': context,
'timestamp': datetime.now()
}
def clear_daily_sales_cache():
"""清除當日業績緩存(供匯入服務調用)"""
_clear_daily_sales_cache()
_DAILY_SALES_VIEW_CACHE.clear()
sys_log.info("已清除當日業績緩存")
@@ -51,8 +79,9 @@ def clear_daily_sales_cache():
@login_required
def api_clear_daily_sales_cache():
"""手動清除快取(保留為 ops escape hatch正常失效靠 DB fingerprint"""
cache_size = len(_SALES_PROCESSED_CACHE)
cache_size = len(_SALES_PROCESSED_CACHE) + len(_DAILY_SALES_VIEW_CACHE)
_clear_daily_sales_cache()
_DAILY_SALES_VIEW_CACHE.clear()
sys_log.info(f"[API] 已清除當日業績緩存 (原有 {cache_size} 個緩存項目)")
return {'success': True, 'message': f'已清除 {cache_size} 個緩存項目'}
@@ -450,9 +479,22 @@ def daily_sales():
)
data_end = max(selected_date, month_end)
current_fingerprint = _get_data_fingerprint(engine, table_name)
view_cache_key = "|".join([
table_name,
'view',
selected_date.strftime('%Y-%m-%d'),
selected_month.strftime('%Y-%m'),
'month' if is_month_view else 'day',
str(current_fingerprint),
])
cached_context = _get_daily_view_cache(view_cache_key)
if cached_context:
return render_template('daily_sales.html', **cached_context)
cache_key = f"{table_name}_daily_{data_start.strftime('%Y%m%d')}_{data_end.strftime('%Y%m%d')}"
# TTL + DB fingerprint 雙閘檢查(資料變動即自動失效,跨 worker 強一致)
if _is_cache_valid(cache_key, engine, table_name):
if _is_cache_valid(cache_key) and _SALES_PROCESSED_CACHE[cache_key].get('fingerprint') == current_fingerprint:
df = _SALES_PROCESSED_CACHE[cache_key]['df']
sys_log.debug(f"使用緩存數據,剩餘有效時間: {_CACHE_EXPIRY_SECONDS - (datetime.now() - _SALES_PROCESSED_CACHE[cache_key]['timestamp']).total_seconds():.0f}")
else:
@@ -467,7 +509,7 @@ def daily_sales():
df = preprocess_daily_sales_data(df)
_SALES_PROCESSED_CACHE[cache_key] = {
'df': df, 'timestamp': datetime.now(),
'fingerprint': _get_data_fingerprint(engine, table_name),
'fingerprint': current_fingerprint,
}
sys_log.info(
f"已載入當日業績窗口 {data_start.strftime('%Y-%m-%d')}~{data_end.strftime('%Y-%m-%d')},共 {len(df)} 筆記錄"
@@ -522,21 +564,24 @@ def daily_sales():
month_end=month_end if is_month_view else None
)
return render_template('daily_sales.html',
selected_date=selected_date.strftime('%Y-%m-%d') if isinstance(selected_date, pd.Timestamp) else selected_date,
available_dates=available_dates_str,
current=current_kpi,
dod=dod_kpi,
wow=wow_kpi,
month_kpi=month_kpi,
is_month_view=is_month_view,
chart_data=chart_data,
categories=category_list,
calendar_data=calendar_data,
marketing_data=marketing_data,
selected_month=selected_month.strftime('%Y-%m') if isinstance(selected_month, pd.Timestamp) else selected_month,
datetime_now=datetime_now_str,
active_page='daily_sales')
context = {
'selected_date': selected_date.strftime('%Y-%m-%d') if isinstance(selected_date, pd.Timestamp) else selected_date,
'available_dates': available_dates_str,
'current': current_kpi,
'dod': dod_kpi,
'wow': wow_kpi,
'month_kpi': month_kpi,
'is_month_view': is_month_view,
'chart_data': chart_data,
'categories': category_list,
'calendar_data': calendar_data,
'marketing_data': marketing_data,
'selected_month': selected_month.strftime('%Y-%m') if isinstance(selected_month, pd.Timestamp) else selected_month,
'datetime_now': datetime_now_str,
'active_page': 'daily_sales',
}
_set_daily_view_cache(view_cache_key, context)
return render_template('daily_sales.html', **context)
except Exception as e:
sys_log.error(f"[Web] [DailySales] Error: {e}")