diff --git a/backend/app/api/backtest.py b/backend/app/api/backtest.py index 1350252..d27e42c 100644 --- a/backend/app/api/backtest.py +++ b/backend/app/api/backtest.py @@ -3,6 +3,7 @@ from __future__ import annotations import asyncio import json +import logging import queue import threading from dataclasses import asdict @@ -21,6 +22,8 @@ from app.services.backtest import ( is_available, ) +logger = logging.getLogger(__name__) + router = APIRouter(prefix="/api/backtest", tags=["backtest"]) FACTOR_DEFAULT_DAYS = 180 @@ -664,10 +667,13 @@ async def optimize_stream( try: base_params = json.loads(params) if params else {} except (json.JSONDecodeError, TypeError): + # 静默降级会让"用户配置丢失"变成无声 bug: 至少 warn 供诊断 (前端应传合法 JSON)。 + logger.warning("optimize: params JSON 解析失败, 降级为空 params: %r", params) base_params = {} try: ov = json.loads(overrides) if overrides else None except (json.JSONDecodeError, TypeError): + logger.warning("optimize: overrides JSON 解析失败, 降级为 None: %r", overrides) ov = None ocfg = OptimizeConfig( strategy_id=strategy_id, diff --git a/frontend/src/lib/optimizerTask.ts b/frontend/src/lib/optimizerTask.ts index 5c6beae..f9c848e 100644 --- a/frontend/src/lib/optimizerTask.ts +++ b/frontend/src/lib/optimizerTask.ts @@ -174,6 +174,9 @@ function connectSSE(url: string): void { if (reconnectAttempts > MAX_RECONNECT) { es.close() eventSource = null + // 清 localStorage: 否则刷新页面 tryReconnect 会重连到这个已放弃的任务。 + localStorage.removeItem(RECONNECT_KEY) + localStorage.removeItem(JOB_KEY_KEY) current = { ...current, isPending: false, error: '连接中断, 重连多次失败' } emit() } @@ -244,9 +247,16 @@ export function stopOptimize(): void { localStorage.removeItem(RECONNECT_KEY) localStorage.removeItem(JOB_KEY_KEY) } else if (eventSource) { - // 保持连接等 job 事件; 兜底: 5s 后仍没 key 就强关 + // 保持连接等 job 事件; 兜底: 5s 后仍没 key 就强关并清 localStorage, + // 避免刷新重连到未取消任务。(若期间 job 到达, handler 已清并置 null, 下面条件不成立跳过) const es = eventSource - setTimeout(() => { if (es === eventSource) { es.close(); eventSource = null } }, 5000) + setTimeout(() => { + if (es === eventSource) { + es.close(); eventSource = null + localStorage.removeItem(RECONNECT_KEY) + localStorage.removeItem(JOB_KEY_KEY) + } + }, 5000) } if (current?.isPending) { current = { ...current, isPending: false, error: '已取消' }