diff --git a/backend/services/themes.py b/backend/services/themes.py index 8bc4f20..9665535 100644 --- a/backend/services/themes.py +++ b/backend/services/themes.py @@ -55,9 +55,31 @@ def _is_trading_time() -> bool: or _TRADING_AFTERNOON[0] <= t <= _TRADING_AFTERNOON[1]) -def _dynamic_ttl() -> int: - """盘中返回 2 分钟缓存 TTL,非交易时段 18 小时(覆盖到下一交易日)""" - return 0 if _is_trading_time() else 18 +def _next_open_delta_seconds() -> int: + """非交易时段写入的缓存距下次开盘的秒数。 + + 缓存只允许存活到下一次开盘(早盘 9:30 / 午休后 13:00)前一刻, + 保证交易日开盘后缓存必然过期并实时拉取,不会读到上个交易日写入的旧数据。 + """ + now = datetime.now(_CST) + # 午休 11:30-13:00 → 截止今天 13:00 + if _TRADING_MORNING[1] < now.time() < _TRADING_AFTERNOON[0]: + open_dt = now.replace(hour=13, minute=0, second=0, microsecond=0) + return max(0, int((open_dt - now).total_seconds())) + # 其余非交易时段(早盘前 / 收盘后 / 周末 / 节假日)→ 下一个工作日 9:30 + for days in range(0, 8): + d = (now + timedelta(days=days)).date() + if d.weekday() >= 5: # 跳过周末 + continue + open_dt = datetime(d.year, d.month, d.day, 9, 30, tzinfo=_CST) + if open_dt > now: + return max(0, int((open_dt - now).total_seconds())) + return 0 + + +def _list_ttl_seconds() -> int: + """题材列表缓存秒数:交易时段 0(不缓存、实时拉取);非交易时段缓存到下次开盘前失效""" + return 0 if _is_trading_time() else _next_open_delta_seconds() # ---- 请求封装 ---- @@ -126,9 +148,11 @@ async def fetch_theme_list(sort_field: int = 1, asc: bool = False) -> list[dict] asc: True=升序, False=降序 """ cache_key = f"theme_list:{sort_field}:{asc}" - cached = get_cache(cache_key) - if cached is not None: - return json.loads(cached) + # 交易时段强制实时:跳过缓存读取,避免命中非交易时段写入的上个交易日旧数据 + if not _is_trading_time(): + cached = get_cache(cache_key) + if cached is not None: + return json.loads(cached) sort = 1 if asc else -1 # hotRank 数值越小越热,"热度降序(最热在前)" 需反转为接口升序 @@ -156,9 +180,9 @@ async def fetch_theme_list(sort_field: int = 1, asc: bool = False) -> list[dict] page += 1 if items: - ttl = _dynamic_ttl() - if ttl > 0: - set_cache(cache_key, json.dumps(items, ensure_ascii=False), ttl_hours=ttl) + ttl_s = _list_ttl_seconds() + if ttl_s > 0: + set_cache(cache_key, json.dumps(items, ensure_ascii=False), ttl_seconds=ttl_s) return items @@ -234,8 +258,8 @@ _GRAPH_CACHE_SECONDS = 60 def _graph_ttl_seconds() -> int: - """图聚合结果与题材股票子层的缓存秒数:盘中 60 秒,非盘中 18 小时""" - return _GRAPH_CACHE_SECONDS if _is_trading_time() else 18 * 3600 + """图聚合结果与题材股票子层的缓存秒数:盘中 60 秒;非盘中缓存到下次开盘前失效""" + return _GRAPH_CACHE_SECONDS if _is_trading_time() else _next_open_delta_seconds() # 后台重建锁:cache_key -> asyncio.Lock,幂等去重,防止并发重复聚合