From eb66ab02d0658ac995ddfd6e4ba41bbeeb6f9ebf Mon Sep 17 00:00:00 2001 From: Sakurasan <26715255+Sakurasan@users.noreply.github.com> Date: Tue, 11 Aug 2026 16:56:56 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BA=A4=E6=98=93=E6=97=A59:31?= =?UTF-8?q?=E6=B8=85=E7=A9=BA=E5=85=A8=E9=83=A8=E7=BC=93=E5=AD=98=EF=BC=8C?= =?UTF-8?q?=E4=BF=9D=E8=AF=81=E5=BC=80=E7=9B=98=E5=90=8E=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=85=A8=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - services/cache.py 新增 clear_all() 清空整个 cache 表 - daily_collector.py 新增 cache_cleanup_loop() 后台循环:精确 sleep 到最近一个 工作日 9:31 清空缓存,跳过周末,出错1分钟重试 - main.py lifespan 启动清理任务,与采集任务一同优雅退出 与既有"非交易时段 TTL 截止到下次开盘前"的惰性过期形成双保险 Co-Authored-By: Claude --- backend/main.py | 15 ++++++---- backend/services/cache.py | 10 +++++++ backend/services/daily_collector.py | 46 +++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 6 deletions(-) diff --git a/backend/main.py b/backend/main.py index dc11b50..5d72787 100644 --- a/backend/main.py +++ b/backend/main.py @@ -8,7 +8,7 @@ from dotenv import load_dotenv from database import init_db from routes import stock, collections, shares, sectors, themes, core_stocks -from services.daily_collector import collector_loop +from services.daily_collector import collector_loop, cache_cleanup_loop load_dotenv() @@ -17,14 +17,17 @@ load_dotenv() async def lifespan(app: FastAPI): init_db() collector_task = asyncio.create_task(collector_loop()) + cache_cleanup_task = asyncio.create_task(cache_cleanup_loop()) try: yield finally: - collector_task.cancel() - try: - await collector_task - except asyncio.CancelledError: - pass + for t in (collector_task, cache_cleanup_task): + t.cancel() + for t in (collector_task, cache_cleanup_task): + try: + await t + except asyncio.CancelledError: + pass app = FastAPI(title="AUV API", version="1.0.0", lifespan=lifespan) diff --git a/backend/services/cache.py b/backend/services/cache.py index 7927016..346a92c 100644 --- a/backend/services/cache.py +++ b/backend/services/cache.py @@ -56,3 +56,13 @@ def clean_expired(): conn.commit() finally: conn.close() + + +def clear_all(): + """清空全部缓存(每日开盘后 9:31 调用,保证当日数据全新)""" + conn = get_connection() + try: + conn.execute("DELETE FROM cache") + conn.commit() + finally: + conn.close() diff --git a/backend/services/daily_collector.py b/backend/services/daily_collector.py index 414c309..ba387ef 100644 --- a/backend/services/daily_collector.py +++ b/backend/services/daily_collector.py @@ -9,6 +9,7 @@ from datetime import datetime, time as dtime, timezone, timedelta from typing import Optional from database import get_connection +from services.cache import clear_all from services.themes import fetch_theme_list, _build_theme_graph _CST = timezone(timedelta(hours=8)) @@ -22,6 +23,9 @@ HOTMAP_TOP_N = 50 # 热点穿透采样题材数:涨幅榜+热度榜 HOTMAP_CORE_LIMIT = 100 # 热点穿透核心股前100(按覆盖题材数降序) CORE_COVER_THRESHOLD = 2 # 热点穿透核心股门槛:覆盖题材数 ≥2 +# 每日缓存清理:交易日 9:31 清空全部缓存,保证开盘后数据全新 +CLEANUP_TIME = dtime(9, 31) + def _is_trading_day(d: datetime) -> bool: """仅按工作日判断:周一至周五视为交易日,不处理法定节假日""" @@ -185,3 +189,45 @@ async def collector_loop(stop: Optional[asyncio.Event] = None) -> None: if stop is not None and stop.is_set(): break await asyncio.sleep(CHECK_INTERVAL_SECONDS) + + +def _next_cleanup_dt(now: datetime) -> datetime: + """计算下一个缓存清理时刻:最近一个工作日 9:31(今天已过则取下一个工作日)""" + for days in range(0, 8): + d = (now + timedelta(days=days)).date() + if d.weekday() >= 5: # 跳过周末 + continue + dt = datetime(d.year, d.month, d.day, CLEANUP_TIME.hour, CLEANUP_TIME.minute, tzinfo=_CST) + if dt > now: + return dt + return now + timedelta(days=1) # 兜底:理论不可达 + + +async def cache_cleanup_loop(stop: Optional[asyncio.Event] = None) -> None: + """后台循环:每个交易日 9:31 清空全部缓存,保证开盘后读到全新数据 + + 到点前精确 sleep 至 9:31;若进程在 9:31 后启动,则等下一个交易日。 + """ + while True: + try: + now = datetime.now(_CST) + next_dt = _next_cleanup_dt(now) + delay = max(0, int((next_dt - now).total_seconds())) + if stop is not None: + try: + await asyncio.wait_for(stop.wait(), timeout=delay) + except asyncio.TimeoutError: + pass # 到点 + if stop.is_set(): + break + else: + await asyncio.sleep(delay) + + clear_all() + print(f"[cache-cleanup] 已清空全部缓存: {datetime.now(_CST).strftime('%Y-%m-%d %H:%M:%S')}") + except asyncio.CancelledError: + raise + except Exception: + print("[cache-cleanup] 清理异常:") + traceback.print_exc() + await asyncio.sleep(60) # 出错 1 分钟后再试