fix: CTP登录冷却持久化到数据库,取消页面自动连并刷新JS缓存
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+65
-5
@@ -24,6 +24,48 @@ CONNECT_WAIT_SEC = 60
|
||||
CONNECT_POLL_INTERVAL_SEC = 0.5
|
||||
LOGIN_BAN_COOLDOWN_SEC = 45 * 60
|
||||
LOGIN_FAIL_COOLDOWN_SEC = 5 * 60
|
||||
CTP_COOLDOWN_UNTIL_KEY = "ctp_login_cooldown_until"
|
||||
CTP_LAST_ERROR_KEY = "ctp_last_error"
|
||||
|
||||
|
||||
def _persist_login_cooldown(seconds: float) -> None:
|
||||
from fee_specs import get_setting, set_setting
|
||||
|
||||
new_until = time.time() + max(0.0, seconds)
|
||||
try:
|
||||
old = float(get_setting(CTP_COOLDOWN_UNTIL_KEY, "0") or 0)
|
||||
except (TypeError, ValueError):
|
||||
old = 0.0
|
||||
if new_until > old:
|
||||
set_setting(CTP_COOLDOWN_UNTIL_KEY, str(new_until))
|
||||
|
||||
|
||||
def _persisted_login_cooldown_remaining() -> int:
|
||||
from fee_specs import get_setting
|
||||
|
||||
try:
|
||||
until = float(get_setting(CTP_COOLDOWN_UNTIL_KEY, "0") or 0)
|
||||
return max(0, int(until - time.time()))
|
||||
except (TypeError, ValueError):
|
||||
return 0
|
||||
|
||||
|
||||
def _clear_persisted_login_cooldown() -> None:
|
||||
from fee_specs import set_setting
|
||||
|
||||
set_setting(CTP_COOLDOWN_UNTIL_KEY, "0")
|
||||
|
||||
|
||||
def _persist_last_error(msg: str) -> None:
|
||||
from fee_specs import set_setting
|
||||
|
||||
set_setting(CTP_LAST_ERROR_KEY, (msg or "").strip())
|
||||
|
||||
|
||||
def _load_persisted_last_error() -> str:
|
||||
from fee_specs import get_setting
|
||||
|
||||
return (get_setting(CTP_LAST_ERROR_KEY, "") or "").strip()
|
||||
|
||||
_position_refresh_callback: Optional[Callable[[], None]] = None
|
||||
|
||||
@@ -138,6 +180,7 @@ class CtpBridge:
|
||||
self._connect_lock = threading.Lock()
|
||||
self._connect_in_progress = False
|
||||
self._login_cooldown_until: float = 0.0
|
||||
self._restore_persisted_state()
|
||||
self._commission_waiters: dict[int, threading.Event] = {}
|
||||
self._commission_lists: dict[int, list] = {}
|
||||
self._commission_hooked = False
|
||||
@@ -180,10 +223,18 @@ class CtpBridge:
|
||||
def connect_in_progress(self) -> bool:
|
||||
return self._connect_in_progress
|
||||
|
||||
def _restore_persisted_state(self) -> None:
|
||||
err = _load_persisted_last_error()
|
||||
if err:
|
||||
self._last_error = err
|
||||
db_remain = _persisted_login_cooldown_remaining()
|
||||
if db_remain > 0:
|
||||
self._login_cooldown_until = time.monotonic() + db_remain
|
||||
|
||||
def login_cooldown_remaining(self) -> int:
|
||||
"""距允许再次登录的剩余秒数。"""
|
||||
remain = int(self._login_cooldown_until - time.monotonic())
|
||||
return max(0, remain)
|
||||
"""距允许再次登录的剩余秒数(内存 + 数据库,重启后仍有效)。"""
|
||||
mem = max(0, int(self._login_cooldown_until - time.monotonic()))
|
||||
return max(mem, _persisted_login_cooldown_remaining())
|
||||
|
||||
def _is_login_cooldown_active(self) -> bool:
|
||||
return self.login_cooldown_remaining() > 0
|
||||
@@ -192,6 +243,11 @@ class CtpBridge:
|
||||
until = time.monotonic() + max(0.0, seconds)
|
||||
if until > self._login_cooldown_until:
|
||||
self._login_cooldown_until = until
|
||||
_persist_login_cooldown(seconds)
|
||||
|
||||
def _clear_login_cooldown(self) -> None:
|
||||
self._login_cooldown_until = 0.0
|
||||
_clear_persisted_login_cooldown()
|
||||
|
||||
def _apply_login_failure_cooldown(self, ctp_logs: list[str]) -> None:
|
||||
text = "\n".join(ctp_logs)
|
||||
@@ -253,6 +309,7 @@ class CtpBridge:
|
||||
missing = [k for k in ("用户名", "密码", "交易服务器") if not st.get(k)]
|
||||
cooldown = self.login_cooldown_remaining()
|
||||
connecting = bool(self._connect_in_progress and cooldown <= 0)
|
||||
last_error = self._last_error or _load_persisted_last_error()
|
||||
return {
|
||||
"vnpy_installed": self.available(),
|
||||
"connected": self._connected_mode == mode,
|
||||
@@ -260,8 +317,8 @@ class CtpBridge:
|
||||
"connected_mode": self._connected_mode,
|
||||
"mode_label": _mode_label(mode),
|
||||
"missing_config": missing,
|
||||
"last_error": self._last_error,
|
||||
"login_cooldown_sec": self.login_cooldown_remaining(),
|
||||
"last_error": last_error,
|
||||
"login_cooldown_sec": cooldown,
|
||||
"broker_id": st.get("经纪商代码", ""),
|
||||
"td_address": st.get("交易服务器", ""),
|
||||
}
|
||||
@@ -336,6 +393,8 @@ class CtpBridge:
|
||||
if self._wait_connected(mode, ctp_logs):
|
||||
self._connected_mode = mode
|
||||
self._last_error = ""
|
||||
_persist_last_error("")
|
||||
self._clear_login_cooldown()
|
||||
logger.info("CTP 已连接 [%s] td_login=%s accounts=%s",
|
||||
mode, self._td_logged_in(),
|
||||
len(self._engine.get_all_accounts() or []))
|
||||
@@ -354,6 +413,7 @@ class CtpBridge:
|
||||
self._apply_login_failure_cooldown(ctp_logs)
|
||||
hint = _format_ctp_failure(ctp_logs, td_address=setting.get("交易服务器", ""))
|
||||
self._last_error = hint
|
||||
_persist_last_error(hint)
|
||||
logger.warning("CTP 连接失败 [%s]: %s | logs=%s", mode, hint, ctp_logs[-5:])
|
||||
raise RuntimeError(hint)
|
||||
finally:
|
||||
|
||||
Reference in New Issue
Block a user