diff --git a/backend/services/themes.py b/backend/services/themes.py index 9665535..e9380b8 100644 --- a/backend/services/themes.py +++ b/backend/services/themes.py @@ -399,10 +399,11 @@ async def _rebuild_task(cache_key: str, sort_field: int, top_n: int, lock: async async def fetch_theme_graph(sort_field: int = 1, top_n: int = 30, limit: int = 1000) -> dict: - """获取热点穿透图数据(盘中 60s 缓存 + stale-while-revalidate) + """获取热点穿透图数据(盘中 60s 缓存,盘中过期同步重建,非盘中 stale-while-revalidate) - 缓存命中且未过期 → 直接返回;已过期 → 返回旧数据并后台异步重建(秒开); - 无缓存 → 同步构建(并发下加锁去重)。返回前按 limit 裁剪 stocks。 + 缓存新鲜 → 直接返回; + 非交易时段过期 → 返回旧数据并后台异步重建(秒开,非盘中行情无实时变化,旧值可接受); + 交易时段过期 / 无缓存 → 同步重建(加锁去重),绝不返回上个交易日的旧图。 Args: sort_field: 题材排序 1=涨幅, 4=热度 @@ -412,20 +413,21 @@ async def fetch_theme_graph(sort_field: int = 1, top_n: int = 30, limit: int = 1 cache_key = f"theme_graph:{sort_field}:{top_n}" data, expires_at = _get_graph_cache(cache_key) - if data is not None: - # 有缓存:新鲜直接返回;过期返回旧数据并后台刷新 - if not (expires_at and expires_at > time.time()): - _spawn_rebuild(cache_key, sort_field, top_n) + if data is not None and expires_at and expires_at > time.time(): + # 缓存新鲜 → 直接返回 return _trim_graph_result(data, limit) - # 无缓存:同步构建(并发下加锁去重) + # 非交易时段过期:行情无实时变化,先返回旧值(秒开),后台异步重建 + if data is not None and not _is_trading_time(): + _spawn_rebuild(cache_key, sort_field, top_n) + return _trim_graph_result(data, limit) + + # 交易时段过期 / 无缓存:同步重建,加锁去重 lock = _REBUILD_LOCKS.setdefault(cache_key, asyncio.Lock()) async with lock: data, expires_at = _get_graph_cache(cache_key) - if data is not None: - # 等待锁期间已被其他请求写入 - if not (expires_at and expires_at > time.time()): - _spawn_rebuild(cache_key, sort_field, top_n) + if data is not None and expires_at and expires_at > time.time(): + # 等待锁期间已被其他请求刷新 return _trim_graph_result(data, limit) data = await _build_theme_graph(sort_field, top_n) _set_graph_cache(cache_key, data)