# coding=utf-8 """当天排餐缓存:启动拉取,界面不展示,供识别比对。""" import json import os import threading from datetime import datetime from dao.settings import ( DISH_BASE_CODE, DISH_BASE_NAME, DISH_MGMT_MEAL_ON_START, DISH_SQLITE_PATH, ) from service.mgmt_client import MgmtApiError, extract_dish_names_from_meals, fetch_today_meals _CACHE_DIR = os.path.join(os.path.dirname(DISH_SQLITE_PATH) or '.', 'mgmt_cache') _CACHE_FILE = os.path.join(_CACHE_DIR, 'today_meals.json') class MealScheduleStore: """内存 + 本地文件缓存;不对 UI 暴露明细。""" def __init__(self): self._lock = threading.RLock() self._raw = None self._names = set() self._date = None self._loaded_at = None self._error = None self._load_file() def _load_file(self): try: if not os.path.isfile(_CACHE_FILE): return with open(_CACHE_FILE, 'r', encoding='utf-8') as f: data = json.load(f) self._raw = data.get('raw') self._date = data.get('date') self._names = set(data.get('names') or []) self._loaded_at = data.get('loaded_at') except Exception as exc: print(f'[WARN] meal cache load failed: {exc}') def _save_file(self): try: os.makedirs(_CACHE_DIR, exist_ok=True) with open(_CACHE_FILE, 'w', encoding='utf-8') as f: json.dump({ 'date': self._date, 'names': sorted(self._names), 'loaded_at': self._loaded_at, 'raw': self._raw, }, f, ensure_ascii=False, indent=2) except Exception as exc: print(f'[WARN] meal cache save failed: {exc}') def refresh(self, base_name=None, base_code=None, base_id=None, force=False): if not force and not DISH_MGMT_MEAL_ON_START and self._names: return {'ok': True, 'skipped': True, 'names': len(self._names)} base_name = (base_name or DISH_BASE_NAME or '').strip() or None base_code = (base_code or DISH_BASE_CODE or '').strip() or None try: data = fetch_today_meals( base_name=base_name, base_code=base_code, base_id=base_id, ) except MgmtApiError as exc: self._error = str(exc) print(f'[WARN] meal schedule fetch failed: {exc}') return {'ok': False, 'error': str(exc)} names = extract_dish_names_from_meals(data) with self._lock: self._raw = data self._names = names self._date = (data or {}).get('date') or datetime.now().strftime('%Y-%m-%d') self._loaded_at = datetime.now().strftime('%Y-%m-%d %H:%M:%S') self._error = None self._save_file() print( f"[OK] meal schedule loaded: date={self._date} " f"base={base_name or base_code} dishes={len(names)}" ) return {'ok': True, 'date': self._date, 'names': len(names)} def today_dish_names(self): with self._lock: # 跨日自动视为空,避免用昨天菜单比对 today = datetime.now().strftime('%Y-%m-%d') if self._date and self._date != today: return set() return set(self._names) def has_schedule(self): return bool(self.today_dish_names()) def is_in_today_menu(self, dish_name): name = (dish_name or '').strip() if not name: return False return name in self.today_dish_names() def summary(self): with self._lock: return { 'date': self._date, 'loaded_at': self._loaded_at, 'dish_count': len(self._names), 'error': self._error, } # 进程内单例,供识别比对使用(不在界面展示) _store = None _store_lock = threading.Lock() def get_meal_schedule_store(): global _store with _store_lock: if _store is None: _store = MealScheduleStore() return _store