From 5eb9b9f5c545ec2140b1630277fea99ad9c420df Mon Sep 17 00:00:00 2001 From: dekun Date: Sun, 12 Jul 2026 09:03:15 +0800 Subject: [PATCH] Force-refresh header balances after transfers and balance SSE events. Invalidate balance caches on mutation, push SSE balance tick, and bypass 60s cache when refreshing funds. Co-authored-by: Cursor --- crypto_monitor_okx/app.py | 11 ++++++++++ lib/common/static/instance_live.js | 7 +++--- lib/common/static/options_settings.js | 14 +++++++++--- lib/exchange/okx_options_lib.py | 5 +++++ lib/instance/instance_live_push_lib.py | 5 +++++ .../templates/embed_boot_scripts.html | 4 ++-- lib/instance/templates/embed_shell.html | 2 +- lib/instance/templates/index.html | 4 ++-- lib/options/options_register.py | 22 ++++++++++++++++++- .../templates/options_settings_panel.html | 2 +- 10 files changed, 63 insertions(+), 13 deletions(-) diff --git a/crypto_monitor_okx/app.py b/crypto_monitor_okx/app.py index 473ad55..25196be 100644 --- a/crypto_monitor_okx/app.py +++ b/crypto_monitor_okx/app.py @@ -2526,6 +2526,10 @@ def friendly_okx_error(err, available_usdt=None): friendly_exchange_error = friendly_okx_error +def invalidate_account_balance_cache() -> None: + ACCOUNT_BALANCE_CACHE["updated_at"] = 0 + + def get_exchange_capitals(force=False): ok_live, _ = ensure_okx_live_ready() if not ok_live: @@ -9286,6 +9290,13 @@ def manual_transfer(): conn.commit() conn.close() if ok: + invalidate_account_balance_cache() + try: + from lib.instance.instance_live_push_lib import notify_instance_balance_changed + + notify_instance_balance_changed() + except Exception: + pass flash(f"手动划转成功:{amount}U {from_account}->{to_account}") else: flash(f"手动划转失败:{msg}") diff --git a/lib/common/static/instance_live.js b/lib/common/static/instance_live.js index b5d1125..b948542 100644 --- a/lib/common/static/instance_live.js +++ b/lib/common/static/instance_live.js @@ -32,12 +32,13 @@ } } - function scheduleRefresh() { + function scheduleRefresh(opts) { if (refreshTimer) return; + const options = opts || {}; refreshTimer = setTimeout(function () { refreshTimer = null; if (document.hidden) return; - refreshTabData(currentTab(), { silent: true }); + refreshTabData(currentTab(), { silent: true, force: !!options.force }); }, 80); } @@ -52,7 +53,7 @@ } if (ver === localLiveVersion) return; localLiveVersion = ver; - scheduleRefresh(); + scheduleRefresh({ force: reason === "balance" }); } function closeLiveStream() { diff --git a/lib/common/static/options_settings.js b/lib/common/static/options_settings.js index e89b497..5464b88 100644 --- a/lib/common/static/options_settings.js +++ b/lib/common/static/options_settings.js @@ -9,6 +9,14 @@ return r.json(); } + function refreshFundsAfterMutation() { + if (typeof refreshAccountSnapshot !== "function") return; + refreshAccountSnapshot({ force: true }); + setTimeout(function () { + refreshAccountSnapshot({ force: true, silent: true }); + }, 1500); + } + function setMsg(id, text, isErr) { const el = document.getElementById(id); if (!el) return; @@ -50,7 +58,7 @@ }), }); setMsg("opt-set-swap-msg", d.ok ? "兑换单已提交" : (d.msg || "失败"), !d.ok); - if (d.ok && typeof refreshAccountSnapshot === "function") refreshAccountSnapshot(); + if (d.ok && typeof refreshAccountSnapshot === "function") refreshFundsAfterMutation(); } const swapBtn = document.getElementById("opt-set-swap-btn"); @@ -96,7 +104,7 @@ }), }); setMsg("opt-set-int-msg", d.ok ? "划转成功" : (d.msg || "失败"), !d.ok); - if (d.ok && typeof refreshAccountSnapshot === "function") refreshAccountSnapshot(); + if (d.ok && typeof refreshAccountSnapshot === "function") refreshFundsAfterMutation(); } const intBtn = document.getElementById("opt-set-int-btn"); @@ -143,7 +151,7 @@ }), }); setMsg("opt-set-cross-msg", d.ok ? "划转成功" : (d.msg || "失败"), !d.ok); - if (d.ok && typeof refreshAccountSnapshot === "function") refreshAccountSnapshot(); + if (d.ok && typeof refreshAccountSnapshot === "function") refreshFundsAfterMutation(); } const crossBtn = document.getElementById("opt-set-cross-btn"); diff --git a/lib/exchange/okx_options_lib.py b/lib/exchange/okx_options_lib.py index 9445876..9de50d7 100644 --- a/lib/exchange/okx_options_lib.py +++ b/lib/exchange/okx_options_lib.py @@ -26,6 +26,11 @@ _OKX_OPTION_ERR_ZH: dict[str, str] = { _OPTIONS_BALANCE_CACHE: dict[str, Any] = {"updated_at": 0.0, "data": None} +def invalidate_options_balance_cache() -> None: + _OPTIONS_BALANCE_CACHE["updated_at"] = 0.0 + _OPTIONS_BALANCE_CACHE["data"] = None + + def _okx_trade_error_message(exc: BaseException | None = None, resp: Any = None) -> str: row: dict[str, Any] | None = None if isinstance(resp, dict): diff --git a/lib/instance/instance_live_push_lib.py b/lib/instance/instance_live_push_lib.py index 2baa2d8..ca93a67 100644 --- a/lib/instance/instance_live_push_lib.py +++ b/lib/instance/instance_live_push_lib.py @@ -100,6 +100,11 @@ class InstanceLivePush: instance_live_push = InstanceLivePush() +def notify_instance_balance_changed() -> int: + """划转/兑换后通知 embed 壳拉最新资金快照.""" + return instance_live_push.tick("balance") + + def register_instance_live_routes(app: Flask, login_required: Callable) -> None: instance_live_push.start() diff --git a/lib/instance/templates/embed_boot_scripts.html b/lib/instance/templates/embed_boot_scripts.html index 3d8ea40..990312b 100644 --- a/lib/instance/templates/embed_boot_scripts.html +++ b/lib/instance/templates/embed_boot_scripts.html @@ -1166,11 +1166,11 @@ function applyAccountSnapshot(data){ } if(data.options_funding_usdc != null || data.options_funding_usdt != null){ const optFunding = formatOptionsFundingLabel(data.options_funding_usdc, data.options_funding_usdt); - if(optFunding !== "—") setFundsFieldText("options-funding-usdc", optFunding); + setFundsFieldText("options-funding-usdc", optFunding); } if(data.options_trading_usdc != null || data.options_trading_usdt != null){ const optTrading = formatOptionsFundingLabel(data.options_trading_usdc, data.options_trading_usdt); - if(optTrading !== "—") setFundsFieldText("options-trading-usdc", optTrading); + setFundsFieldText("options-trading-usdc", optTrading); } if(typeof data.unrealized_pnl !== "undefined"){ updateRealtimePnl(data.unrealized_pnl); diff --git a/lib/instance/templates/embed_shell.html b/lib/instance/templates/embed_shell.html index ea23b29..c88b071 100644 --- a/lib/instance/templates/embed_shell.html +++ b/lib/instance/templates/embed_shell.html @@ -106,7 +106,7 @@ const ORDER_ENTRY_MODEL_CODE_TO_CATEGORY = {{ entry_model_code_to_category | toj window.__INSTANCE_DISPLAY__ = {{ display | tojson }}; - + diff --git a/lib/instance/templates/index.html b/lib/instance/templates/index.html index d379f1d..4868b66 100644 --- a/lib/instance/templates/index.html +++ b/lib/instance/templates/index.html @@ -1811,11 +1811,11 @@ function applyAccountSnapshot(data){ } if(data.options_funding_usdc != null || data.options_funding_usdt != null){ const optFunding = formatOptionsFundingLabel(data.options_funding_usdc, data.options_funding_usdt); - if(optFunding !== "—") setFundsFieldText("options-funding-usdc", optFunding); + setFundsFieldText("options-funding-usdc", optFunding); } if(data.options_trading_usdc != null || data.options_trading_usdt != null){ const optTrading = formatOptionsFundingLabel(data.options_trading_usdc, data.options_trading_usdt); - if(optTrading !== "—") setFundsFieldText("options-trading-usdc", optTrading); + setFundsFieldText("options-trading-usdc", optTrading); } if(typeof data.unrealized_pnl !== "undefined"){ updateRealtimePnl(data.unrealized_pnl); diff --git a/lib/options/options_register.py b/lib/options/options_register.py index 584f425..510af5d 100644 --- a/lib/options/options_register.py +++ b/lib/options/options_register.py @@ -123,9 +123,24 @@ def _build_cfg(app_module: Any) -> dict[str, Any]: "spot_market_swap_usdt_usdc": spot_market_swap_usdt_usdc, "transfer_main_sub_account": transfer_main_sub_account, "options_api_ready": options_api_ready, + "app_module": app_module, } +def _mark_balances_stale(cfg: dict[str, Any]) -> None: + from lib.exchange.okx_options_lib import invalidate_options_balance_cache + from lib.instance.instance_live_push_lib import notify_instance_balance_changed + + invalidate_options_balance_cache() + app_mod = cfg.get("app_module") + if app_mod is not None and hasattr(app_mod, "invalidate_account_balance_cache"): + app_mod.invalidate_account_balance_cache() + try: + notify_instance_balance_changed() + except Exception: + pass + + def _require_options_ex(cfg: dict[str, Any]): if not cfg.get("enabled"): return None, "期权模块未启用,请在 .env 设置 OKX_OPTIONS_ENABLED=true 并重启 PM2" @@ -791,6 +806,7 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None: conn.commit() finally: conn.close() + _mark_balances_stale(cfg) return jsonify(result) @app.route("/api/options/spot/swap", methods=["POST"]) @@ -805,7 +821,10 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None: amount = float(data.get("amount")) except (TypeError, ValueError): return jsonify({"ok": False, "msg": "数量无效"}) - return jsonify(cfg["spot_market_swap_usdt_usdc"](ex, direction=direction, amount=amount)) + result = cfg["spot_market_swap_usdt_usdc"](ex, direction=direction, amount=amount) + if result.get("ok"): + _mark_balances_stale(cfg) + return jsonify(result) @app.route("/api/options/cross-transfer", methods=["POST"]) @lr @@ -852,6 +871,7 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None: conn.commit() finally: conn.close() + _mark_balances_stale(cfg) return jsonify(result) @app.route("/api/options/history") diff --git a/lib/options/templates/options_settings_panel.html b/lib/options/templates/options_settings_panel.html index e00a4ad..ad39998 100644 --- a/lib/options/templates/options_settings_panel.html +++ b/lib/options/templates/options_settings_panel.html @@ -1,4 +1,4 @@ {# 期权设置脚本挂载点(卡片在 settings_panel 中拆分) #} - +