Skip to main content

futu_gateway_core/bridge/
mod.rs

1// 业务桥接层:直连富途后端的完整网关
2//
3// 启动流程:HTTP 认证 → TCP 连接后端 → 登录 → 获取 session_key → 拉取账户列表 → 启动 API 服务
4//
5// # GatewayBridge boundary map
6//
7// `GatewayBridge` keeps facade/ownership state: shared backend, broker runtime,
8// subscription runtime, client identity, idempotency/replay guards, and small
9// control state.
10// Cache-owned state lives under `CacheBundle`; push runtime state lives under
11// `PushRuntime`; startup/reload/push/trade/QOT helpers are split into focused
12// bridge submodules.
13//
14// Current boundary rules:
15//
16// 1. Handlers receive grouped dependency structs instead of cloning bridge
17//    internals directly.
18// 2. Cache access crosses the bridge boundary through `bridge.caches()` or
19//    handler-owned `CacheBundle`.
20// 3. Metrics, push health, and QOT login health cross the bridge boundary
21//    through `bridge.push_runtime()`.
22// 4. New bridge state must document its owner boundary before being exposed
23//    outside this module.
24
25use std::sync::Arc;
26
27use arc_swap::ArcSwap;
28use futu_backend::auth;
29use futu_backend::conn::BackendConn;
30use futu_core::error::Result;
31
32// v1.4.110 P0-5 T15 wire: removed (handlers facade no longer exists)
33
34// v1.4.60 Phase E1: bridge 模块化 split. 工具函数抽到子模块, 保持 pub(crate)
35// 可见性让 sub-module + GatewayBridge 都能用.
36mod account;
37mod auth_runtime;
38mod background_runtime;
39mod broker_conn;
40mod broker_runtime;
41pub mod cache_bundle;
42mod channel_runtime_shadow;
43mod client_identity_runtime;
44mod client_sig_reactive;
45mod client_sig_timer;
46pub mod clock;
47mod dispatcher;
48mod gateway_parts;
49// v1.4.110 Layer 3 B: bridge::initialize() 拆 4 phases (zero behavior change).
50mod init_phase1;
51mod init_phase2;
52mod init_phase3;
53mod init_phase4_reconnect;
54mod market_startup;
55mod mkt_id_refresh;
56mod platform_pool;
57mod push_basic_qot;
58mod push_broker_queue;
59mod push_builder;
60mod push_callback;
61mod push_crypto_notify;
62mod push_dedup;
63pub mod push_delivery;
64mod push_health;
65mod push_kline;
66mod push_msg_center;
67mod push_orderbook;
68pub mod push_parser;
69mod push_rt;
70mod push_runtime;
71mod push_ticker;
72mod push_trade_notify;
73mod qot_login_health;
74mod qot_right_push;
75mod qot_right_refresh;
76mod qot_runtime;
77mod reference_data_download;
78mod reference_runtime;
79mod reload;
80mod stock_list_sync;
81mod subscription_runtime;
82mod trade_dispatch;
83mod trade_write_runtime;
84mod types;
85pub mod utils;
86pub use types::*;
87
88#[cfg(test)]
89mod runtime_construction_tests;
90
91/// 从 SharedBackend 加载当前后端连接(原子读取,支持重连后自动更新)
92///
93/// v1.4.110 P0-5 prep: 从 `handlers/mod.rs::load_backend` 提到 `bridge` 顶层,
94/// 因 dispatcher.rs 3 处用 + handlers 子模块 48 处用, 是 bridge / handlers 共
95/// 用 helper. 拆 3 crate 后位于 gateway-core (bridge 与 dispatcher 同 crate).
96/// 原 `handlers::load_backend` 保留 `pub use` re-export 作 facade.
97pub fn load_backend(
98    shared: &SharedBackend,
99) -> Option<std::sync::Arc<futu_backend::conn::BackendConn>> {
100    let guard = shared.load();
101    guard.as_ref().clone()
102}
103
104// v1.4.83 §9 Phase 2: Push channel health tracking (F2-F6)
105pub use auth_runtime::AuthRuntime;
106pub use background_runtime::{BackgroundRuntime, BackgroundTaskTracker};
107use broker_conn::{
108    BrokerChannelSetup, broker_tcp_login, establish_single_broker, keep_subscribe_quotes,
109    resubscribe_quotes, sync_current_quote_desired_set,
110};
111pub use broker_runtime::{BROKER_RECONNECT_MAX_CONSECUTIVE_FAILURES, BrokerRuntime};
112pub use cache_bundle::CacheBundle;
113pub use client_identity_runtime::ClientIdentityRuntime;
114pub use clock::ServerClockRuntime;
115use gateway_parts::GatewayBridgeParts;
116use push_builder::{build_order_fill_update_push, build_order_update_push, resolve_push_header};
117pub use push_health::{
118    PushHealth, PushHealthSnapshot, QotLoginHealth, QotLoginHealthSnapshot, SharedPushHealth,
119    SharedQotLoginHealth,
120};
121pub use push_runtime::PushRuntime;
122pub use qot_runtime::QotRuntime;
123pub use reference_runtime::ReferenceRuntime;
124pub use subscription_runtime::SubscriptionRuntime;
125use trade_dispatch::{dispatch_trade_data_queries, retry_with_exp_backoff};
126pub use trade_write_runtime::TradeWriteRuntime;
127use utils::split_host_port;
128// v1.4.93 P1-3 (BUG-5318-003): cross-module re-export 让 handlers/qot/mod.rs
129// 能调 derive_exch_type_with_fallback. utils 模块自身保持 private.
130pub use utils::{
131    derive_exch_type_with_fallback, listing_date_to_str, market_code_to_exch_type_with_security,
132    market_code_to_qot_market_with_security,
133};
134
135/// 共享的后端连接引用,支持重连后原子替换。
136/// 所有 handler 持有同一个 Arc,重连时通过 ArcSwap::store 替换内部值,
137/// handler 下次 load() 即可获取新连接。
138pub type SharedBackend = Arc<ArcSwap<Option<Arc<BackendConn>>>>;
139
140/// 网关桥接器
141pub struct GatewayBridge {
142    /// Cache ownership boundary for quote, trade, login, profile, and related
143    /// shared cache state.
144    caches: CacheBundle,
145    /// Push-runtime boundary for metrics and push health state.
146    push_runtime: PushRuntime,
147    /// Broker/backend connection boundary for Platform and broker channels.
148    broker_runtime: BrokerRuntime,
149    /// QOT-specific mutable runtime state boundary.
150    qot_runtime: QotRuntime,
151    /// Subscription runtime boundary for QOT/TRD subscriptions and server push.
152    subscription_runtime: SubscriptionRuntime,
153    /// Auth/reload lifecycle runtime boundary.
154    auth_runtime: AuthRuntime,
155    /// Background task lifecycle boundary. Owns startup worker JoinHandles and
156    /// aborts them when the bridge/runtime is dropped.
157    background_runtime: BackgroundRuntime,
158    /// Server-clock runtime boundary. Owns login-derived offset and injectable
159    /// `ServerClock` provider.
160    server_clock: ServerClockRuntime,
161    /// Reference-data runtime boundary for HTTP-backed suspend/code-change caches.
162    reference_runtime: ReferenceRuntime,
163    /// Client identity boundary: app language + HTTP/API client type.
164    client_identity: ClientIdentityRuntime,
165    /// Trade-write runtime boundary for idempotency and PacketID replay guard.
166    trade_write_runtime: TradeWriteRuntime,
167}
168
169impl GatewayBridge {
170    pub fn new() -> Self {
171        let parts = GatewayBridgeParts::new_default();
172        Self {
173            caches: parts.caches,
174            push_runtime: parts.push_runtime,
175            broker_runtime: parts.broker_runtime,
176            qot_runtime: parts.qot_runtime,
177            subscription_runtime: parts.subscription_runtime,
178            auth_runtime: parts.auth_runtime,
179            background_runtime: parts.background_runtime,
180            server_clock: parts.server_clock,
181            reference_runtime: parts.reference_runtime,
182            client_identity: parts.client_identity,
183            trade_write_runtime: parts.trade_write_runtime,
184        }
185    }
186
187    /// Cache ownership boundary for quote, trade, login, profile, and related
188    /// shared cache state.
189    pub fn caches(&self) -> &CacheBundle {
190        &self.caches
191    }
192
193    /// Trade-write runtime boundary for idempotency and PacketID replay guard.
194    pub fn trade_write_runtime(&self) -> &TradeWriteRuntime {
195        &self.trade_write_runtime
196    }
197
198    /// Push-runtime boundary for metrics, push health, qot-login health, and
199    /// push event sender state.
200    pub fn push_runtime(&self) -> &PushRuntime {
201        &self.push_runtime
202    }
203
204    /// Auth/reload lifecycle runtime boundary.
205    pub fn auth_runtime(&self) -> &AuthRuntime {
206        &self.auth_runtime
207    }
208
209    /// Subscription runtime boundary for QOT/TRD subscriptions and server push.
210    pub fn subscription_runtime(&self) -> &SubscriptionRuntime {
211        &self.subscription_runtime
212    }
213
214    /// Broker/backend connection boundary for Platform and broker channels.
215    pub fn broker_runtime(&self) -> &BrokerRuntime {
216        &self.broker_runtime
217    }
218
219    pub(super) fn auth_runtime_mut(&mut self) -> &mut AuthRuntime {
220        &mut self.auth_runtime
221    }
222
223    /// Client identity boundary: app language + HTTP/API client type.
224    pub fn client_identity(&self) -> &ClientIdentityRuntime {
225        &self.client_identity
226    }
227
228    pub(super) fn client_identity_mut(&mut self) -> &mut ClientIdentityRuntime {
229        &mut self.client_identity
230    }
231
232    /// Reference-data runtime boundary for HTTP-backed suspend/code-change caches.
233    pub fn reference_runtime(&self) -> &ReferenceRuntime {
234        &self.reference_runtime
235    }
236
237    /// QOT-specific mutable runtime state boundary.
238    pub fn qot_runtime(&self) -> &QotRuntime {
239        &self.qot_runtime
240    }
241
242    /// Server-clock runtime boundary. Owns login-derived offset and injectable
243    /// `ServerClock` provider.
244    pub fn server_clock(&self) -> &ServerClockRuntime {
245        &self.server_clock
246    }
247
248    /// Background task lifecycle boundary. Owns startup worker JoinHandles and
249    /// aborts them when the bridge/runtime is dropped.
250    pub fn background_runtime(&self) -> &BackgroundRuntime {
251        &self.background_runtime
252    }
253
254    /// 取所有**已建立**的 broker 连接。顺序按 broker_id 升序,
255    /// `fetch_real_accounts` 会遍历这个 vec 对每个 broker 各发一次 CMD 2282,
256    /// 对齐 C++ `GTWCmdAndPushReply::OMEvProc_RePullAccList`(`GTWCmdAndPushReply.cpp:937-945`)
257    /// —— 每个 broker conn 独立发 `INNProto_Trd_AccList::QueryAccList(uid, ConnTypeToBrokerID(m_enConnType))`。
258    pub fn all_broker_backends(&self) -> Vec<(u32, Arc<BackendConn>)> {
259        self.broker_runtime().all_broker_backends()
260    }
261
262    /// v1.4.110 codex audit P1 #3: 用户唯一已开户 crypto account 的 broker_id.
263    ///
264    /// 对齐 C++ `INNData_Trd_MainBrokerage::GetCryptoSupportedDefaultMainBroker`
265    /// (line 70-123) 优先级 1:
266    /// > 如果当前只开了一个 crypto account, 直接取该 account 的 broker.
267    ///
268    /// 返:
269    /// - `Some(broker_id)`: trd_cache 恰好 1 个 `is_crypto_account()`, 取其 broker
270    /// - `None`: 0 个或 ≥ 2 个 crypto account, 走 9419 crypto_brokers / main_brokers / fallback 路径
271    ///
272    /// 注入到 `resolve_qot_broker_for_request` / `resolve_or_reject_broker`
273    /// 第 5 参数. QOT handler `securityFirm=Unknown(0)` 时此值决定 default broker.
274    pub fn single_crypto_account_broker(&self) -> Option<u32> {
275        futu_backend::crypto_trade::single_crypto_account_broker(&self.caches().trd_cache)
276    }
277
278    /// 完整的启动流程: 认证 → 连接后端 → 登录 → 拉取数据
279    ///
280    /// `verify_cb`: 短信验证码回调(GUI 模式传入, CLI 模式传 None 使用 stdin)
281    ///
282    /// v1.4.110 Layer 3 B refactor: 拆 4 phase 编排器, 每 phase 抽到独立子模块
283    /// (zero behavior change):
284    /// - [`init_phase1`]: HTTP 认证 + `--setup-only` 早退出
285    /// - [`init_phase2`]: TCP 连接后端 + 登录 + ConnIP + broker 通道
286    /// - [`init_phase3`]: 拉取账户列表 + 启动后台 worker (CMD 3020 / 6024 /
287    ///   mkt_id refresh / qot_login_health / G1 client_sig timer /
288    ///   stock_list_sync / reference_data_download)
289    /// - [`init_phase4_reconnect`]: 心跳 + 断连监控 tokio task
290    pub async fn initialize(
291        &mut self,
292        config: &GatewayConfig,
293        verify_cb: Option<auth::VerifyCodeCallback>,
294    ) -> Result<tokio::sync::mpsc::Receiver<PushEvent>> {
295        let proactive_env = std::env::var("FUTU_CLIENT_SIG_PROACTIVE_REFRESH").ok();
296        let reactive_env = std::env::var("FUTU_CLIENT_SIG_REACTIVE_REFRESH").ok();
297        self.auth_runtime_mut()
298            .set_client_sig_refresh_status(ClientSigRefreshStatus {
299                client_sig_proactive_refresh_enabled:
300                    client_sig_timer::effective_proactive_refresh_enabled(
301                        config.client_sig_proactive_refresh,
302                        proactive_env.as_deref(),
303                    ),
304                client_sig_reactive_refresh_enabled:
305                    client_sig_reactive::effective_reactive_refresh_enabled(
306                        config.client_sig_reactive_refresh,
307                        reactive_env.as_deref(),
308                    ),
309            });
310
311        // Phase 1: HTTP 认证 (+ `--setup-only` 早退出)
312        let phase1 = match self.init_phase1_http_auth(config, verify_cb).await? {
313            init_phase1::Phase1Outcome::SetupOnlyDone(rx) => return Ok(rx),
314            init_phase1::Phase1Outcome::Continue(p1) => p1,
315        };
316
317        // Phase 2: TCP 连接后端 + 登录 + ConnIP + broker 通道
318        let phase2 = self
319            .init_phase2_tcp_connect(config, phase1.auth_result)
320            .await?;
321        let init_phase2::Phase2Result {
322            backend,
323            login_result,
324            push_rx,
325            push_cb,
326            auth_result_for_reconnect,
327        } = phase2;
328
329        // Phase 3: 启动后台 worker
330        self.init_phase3_workers(&backend).await?;
331
332        // Phase 4: 心跳 + 断连监控
333        let heartbeat_interval =
334            std::time::Duration::from_secs(login_result.keep_alive_interval as u64);
335        tracing::info!(interval_secs = ?heartbeat_interval, "starting backend heartbeat");
336        let heartbeat_handle =
337            futu_backend::heartbeat::start_heartbeat(Arc::clone(&backend), heartbeat_interval);
338
339        init_phase4_reconnect::spawn_heartbeat_and_reconnect_monitor(
340            init_phase4_reconnect::ReconnectMonitorDeps {
341                config: config.clone(),
342                auth: auth_result_for_reconnect,
343                caches: self.caches().clone(),
344                broker_runtime: self.broker_runtime().clone(),
345                subscriptions: self.subscription_runtime().manager(),
346                push_runtime: self.push_runtime().clone(),
347                main_broker_cache: self.qot_runtime().main_broker_cache(),
348                push_cb,
349                commconfig: self.auth_runtime().commconfig(),
350                auth_refresher: self.auth_runtime().auth_refresher(),
351                account: config.account.clone(),
352                initial_heartbeat: heartbeat_handle,
353                background_runtime: self.background_runtime().clone(),
354            },
355        );
356
357        // v1.4.106 codex 1140 F7: broker_dict refresher (CMD 18008 8h 周期).
358        //
359        // v1.4.110 P0-5 T15 P4 wire: 原本 bridge::initialize() 直接调
360        // `handlers::qot::spawn_broker_dict_refresher(...)` 是 bridge → qot 反向
361        // 引用 (cross-crate cycle 后违法). 现 spawn 移到
362        // `futu_gateway_qot::register_handlers` 内部 — register 时 broker_runtime.backend
363        // 已 ready (initialize 在它之前已完成), 同一 daemon 启动节奏不变.
364
365        tracing::info!("gateway initialization complete");
366        Ok(push_rx)
367    }
368    // v1.4.110 P0-5 prep (T12 P1): `register_handlers(&self, server)` 已删 —
369    // 原本反向调用 `handlers::{qot,trd,sys}::register_handlers(router, self)`
370    // 是 bridge → handlers cyclic dep (3 crate split 后 bridge 在 gateway-core,
371    // handlers 在 gateway-trd/-qot, cross-crate cycle 不可). 改由 futu-opend
372    // `startup/mod.rs` 在 bridge 建好后**显式**调 3 个域的 register fn.
373}
374
375impl Default for GatewayBridge {
376    fn default() -> Self {
377        Self::new()
378    }
379}