From 804b720b3c31d161736323328f623f37bccf84a8 Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Mon, 25 May 2026 06:12:17 +0800 Subject: [PATCH] =?UTF-8?q?=E6=89=AB=E6=8F=8F=E7=BB=88=E7=AB=AF=E4=BC=98?= =?UTF-8?q?=E5=8C=96=EF=BC=9A=E5=A2=9E=E5=8A=A0=E5=B9=B6=E5=8F=91=E5=88=B0?= =?UTF-8?q?6=E3=80=81=E8=B6=85=E6=97=B660s=E3=80=81=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E9=A2=84=E7=83=AD=E5=85=A8=E5=9F=8E=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MAX_WORKERS 2→6,BUILD_TIMEOUT 22→60s。新增 start_scan_terminal_prewarm:服务启动时后台跑全城 _analyze(),后续扫描直接命中缓存。app_factory 在路由注册后触发热身。 --- web/app_factory.py | 2 ++ web/scan_terminal_service.py | 55 ++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/web/app_factory.py b/web/app_factory.py index 3f141c96..ba87a0cf 100644 --- a/web/app_factory.py +++ b/web/app_factory.py @@ -16,6 +16,7 @@ from web.routers.payments import router as payments_router from web.routers.scan import router as scan_router from web.routers.system import router as system_router from web.routes import router as legacy_router +from web.scan_terminal_service import start_scan_terminal_prewarm _ROUTES_REGISTERED_FLAG = "_polyweather_routes_registered" @@ -32,4 +33,5 @@ def create_app() -> FastAPI: core_app.include_router(ops_router) core_app.include_router(legacy_router) setattr(core_app.state, _ROUTES_REGISTERED_FLAG, True) + start_scan_terminal_prewarm() return core_app diff --git a/web/scan_terminal_service.py b/web/scan_terminal_service.py index f0554702..c0a82994 100644 --- a/web/scan_terminal_service.py +++ b/web/scan_terminal_service.py @@ -1544,3 +1544,58 @@ def build_scan_terminal_ai_payload( reason=str(exc), duration_ms=duration_ms, ) + + +_SCAN_PREWARM_STARTED = False +_SCAN_PREWARM_LOCK = threading.Lock() + + +def start_scan_terminal_prewarm() -> None: + """Warm analysis caches for all cities at startup so the first terminal + scan returns quickly instead of forcing every city through a cold + _analyze() path. + + Runs once per process. Safe to call from any thread and at any point + during the server lifecycle. + """ + global _SCAN_PREWARM_STARTED + with _SCAN_PREWARM_LOCK: + if _SCAN_PREWARM_STARTED: + return + _SCAN_PREWARM_STARTED = True + + city_names = list(CITIES.keys()) + logger.info( + "scan terminal pre-warm starting cities={} workers={}", + len(city_names), + SCAN_TERMINAL_MAX_WORKERS, + ) + + def _warm_one(city: str) -> str: + try: + _analyze(city, force_refresh=False, detail_mode="panel") + except Exception: + pass + return city + + def _run(): + started = time.time() + ok = 0 + workers = max(1, min(SCAN_TERMINAL_MAX_WORKERS, len(city_names))) + with ThreadPoolExecutor(max_workers=workers) as ex: + futures = {ex.submit(_warm_one, c): c for c in city_names} + for f in as_completed(futures): + try: + f.result() + ok += 1 + except Exception: + pass + elapsed = int(time.time() - started) + logger.info( + "scan terminal pre-warm finished ok={}/{} elapsed={}s", + ok, + len(city_names), + elapsed, + ) + + threading.Thread(target=_run, name="scan-prewarm", daemon=True).start()