快取當日業績頁面 context
Some checks failed
CD Pipeline / deploy (push) Has been cancelled

This commit is contained in:
OoO
2026-05-13 12:19:11 +08:00
parent d384c35e51
commit d8c7f6f19c
3 changed files with 84 additions and 2 deletions

View File

@@ -7,6 +7,9 @@
import io
import calendar
import hashlib
import os
import pickle
from datetime import datetime, timezone, timedelta
from urllib.parse import quote
from flask import Blueprint, request, render_template, send_file
@@ -14,7 +17,7 @@ from auth import login_required
from sqlalchemy import inspect, text
import pandas as pd
from config import BASE_DIR
from config import BASE_DIR, SYSTEM_VERSION
from database.manager import DatabaseManager
from services.logger_manager import SystemLogger
from utils.df_helpers import find_col
@@ -25,6 +28,7 @@ from services.daily_sales_service import (
)
from services.cache_manager import (
_DAILY_SALES_PROCESSED_CACHE as _SALES_PROCESSED_CACHE,
_DAILY_SALES_VIEW_CACHE_DIR,
clear_daily_sales_cache as _clear_daily_sales_cache,
)
@@ -39,6 +43,7 @@ daily_sales_bp = Blueprint('daily_sales', __name__)
_CACHE_EXPIRY_SECONDS = 300 # 5 分鐘緩存過期
_VIEW_CACHE_EXPIRY_SECONDS = 120
_SHARED_VIEW_CACHE_EXPIRY_SECONDS = 1800
_DAILY_SALES_VIEW_CACHE = {}
_DAILY_SALES_VIEW_CACHE_MAX = 24
@@ -67,6 +72,46 @@ def _set_daily_view_cache(cache_key, context):
}
def _daily_view_cache_file(cache_key):
digest = hashlib.md5(cache_key.encode('utf-8'), usedforsecurity=False).hexdigest()
return _DAILY_SALES_VIEW_CACHE_DIR / f"{digest}.pkl"
def _get_shared_daily_view_cache(cache_key):
path = _daily_view_cache_file(cache_key)
if not path.exists():
return None
try:
if (datetime.now().timestamp() - path.stat().st_mtime) >= _SHARED_VIEW_CACHE_EXPIRY_SECONDS:
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"[DailySales] 共享 view cache 讀取失敗: {exc}")
return None
def _set_shared_daily_view_cache(cache_key, context):
path = _daily_view_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"[DailySales] 共享 view cache 寫入失敗: {exc}")
try:
if tmp_path.exists():
tmp_path.unlink()
except OSError:
pass
def clear_daily_sales_cache():
"""清除當日業績緩存(供匯入服務調用)"""
_clear_daily_sales_cache()
@@ -488,8 +533,9 @@ def daily_sales():
'month' if is_month_view else 'day',
str(current_fingerprint),
])
cached_context = _get_daily_view_cache(view_cache_key)
cached_context = _get_daily_view_cache(view_cache_key) or _get_shared_daily_view_cache(view_cache_key)
if cached_context:
_set_daily_view_cache(view_cache_key, 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')}"
@@ -581,6 +627,7 @@ def daily_sales():
'active_page': 'daily_sales',
}
_set_daily_view_cache(view_cache_key, context)
_set_shared_daily_view_cache(view_cache_key, context)
return render_template('daily_sales.html', **context)
except Exception as e:

View File

@@ -70,6 +70,7 @@ _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"
_DAILY_SALES_VIEW_CACHE_DIR = _BASE_DIR / "data" / "daily_sales_view_cache"
def cleanup_sales_cache():
@@ -131,6 +132,15 @@ def clear_sales_cache():
def clear_daily_sales_cache():
"""清除當日業績 cache。"""
_DAILY_SALES_PROCESSED_CACHE.clear()
try:
if _DAILY_SALES_VIEW_CACHE_DIR.exists():
for cache_file in _DAILY_SALES_VIEW_CACHE_DIR.glob("*.pkl"):
try:
cache_file.unlink()
except OSError:
logger.debug("daily sales view cache file cleanup failed: %s", cache_file, exc_info=True)
except OSError:
logger.debug("daily sales view cache cleanup failed", exc_info=True)
def clear_dashboard_cache():

View File

@@ -119,6 +119,31 @@ def test_clear_sales_cache_removes_shared_page_cache_files(tmp_path, monkeypatch
assert not cache_file.exists()
def test_daily_sales_shared_view_cache_roundtrip(tmp_path, monkeypatch):
from routes import daily_sales_routes
monkeypatch.setattr(daily_sales_routes, "_DAILY_SALES_VIEW_CACHE_DIR", tmp_path)
cache_key = "daily_sales:view:test"
context = {"summary": {"revenue": 456}, "active_page": "daily_sales"}
daily_sales_routes._set_shared_daily_view_cache(cache_key, context)
assert daily_sales_routes._get_shared_daily_view_cache(cache_key) == context
def test_clear_daily_sales_cache_removes_shared_view_cache_files(tmp_path, monkeypatch):
from services import cache_manager
monkeypatch.setattr(cache_manager, "_DAILY_SALES_VIEW_CACHE_DIR", tmp_path)
tmp_path.mkdir(parents=True, exist_ok=True)
cache_file = tmp_path / "daily_sales_view.pkl"
cache_file.write_bytes(b"stale")
cache_manager.clear_daily_sales_cache()
assert not cache_file.exists()
def test_cache_dicts_are_only_defined_in_cache_manager():
assignments = []
for path in [ROOT / "app.py", *ROOT.glob("routes/*.py"), *ROOT.glob("services/*.py")]: