futu_gateway/handlers/
sys.rs

1// 系统业务处理器 — GetUserInfo / GetDelayStatistics / Verification / TestCmd / RemoteCmd
2
3use std::sync::Arc;
4
5use async_trait::async_trait;
6
7use futu_cache::login_cache::LoginCache;
8use futu_cache::qot_right::QotRightCache;
9use futu_core::proto_id;
10use futu_server::conn::IncomingRequest;
11use futu_server::router::{RequestHandler, RequestRouter};
12
13use crate::bridge::GatewayBridge;
14
15/// 注册系统处理器
16pub fn register_handlers(router: &Arc<RequestRouter>, bridge: &GatewayBridge) {
17    router.register(
18        proto_id::GET_USER_INFO,
19        Arc::new(GetUserInfoHandler {
20            login_cache: Arc::clone(&bridge.login_cache),
21            qot_right_cache: Arc::clone(&bridge.qot_right_cache),
22        }),
23    );
24    router.register(
25        proto_id::GET_DELAY_STATISTICS,
26        Arc::new(GetDelayStatisticsHandler),
27    );
28    router.register(proto_id::VERIFICATION, Arc::new(VerificationHandler));
29    router.register(proto_id::TEST_CMD, Arc::new(TestCmdHandler));
30    router.register(proto_id::REMOTE_CMD, Arc::new(RemoteCmdHandler));
31
32    tracing::debug!("system handlers registered");
33}
34
35// ===== GetUserInfo (1005) =====
36// C++ 从多个后端聚合查询(Profile、QotRight、Disclaimer、Update)。
37// Rust: user_id 从登录缓存获取,行情权限从 CMD 6024 响应缓存获取。
38struct GetUserInfoHandler {
39    login_cache: Arc<LoginCache>,
40    qot_right_cache: Arc<QotRightCache>,
41}
42
43#[async_trait]
44impl RequestHandler for GetUserInfoHandler {
45    async fn handle(&self, _conn_id: u64, request: &IncomingRequest) -> Option<Vec<u8>> {
46        let _req: futu_proto::get_user_info::Request =
47            prost::Message::decode(request.body.as_ref()).ok()?;
48
49        let login_state = self.login_cache.get_login_state();
50        let user_id = login_state.as_ref().map(|s| s.user_id as i64).unwrap_or(0);
51        let nick_name = login_state
52            .as_ref()
53            .map(|s| s.login_account.clone())
54            .unwrap_or_default();
55
56        // 从 CMD 6024 缓存获取真实行情权限
57        let qr = self.qot_right_cache.get();
58
59        let s2c = futu_proto::get_user_info::S2c {
60            nick_name: Some(nick_name),
61            avatar_url: None,
62            api_level: None,
63            hk_qot_right: Some(qr.hk_qot_right),
64            us_qot_right: Some(qr.us_qot_right),
65            cn_qot_right: Some(qr.sh_qot_right), // 兼容旧字段,取上证权限
66            is_need_agree_disclaimer: Some(false),
67            user_id: Some(user_id),
68            update_type: Some(0), // UpdateType_None
69            web_key: None,
70            web_jump_url_head: None,
71            hk_option_qot_right: Some(qr.hk_option_qot_right),
72            has_us_option_qot_right: Some(qr.has_us_option_qot_right),
73            hk_future_qot_right: Some(qr.hk_future_qot_right),
74            sub_quota: Some(qr.sub_quota),
75            history_kl_quota: Some(qr.history_kl_quota),
76            us_future_qot_right: Some(qr.us_cme_future_qot_right), // 兼容旧字段
77            us_option_qot_right: Some(qr.us_option_qot_right),
78            user_attribution: None,
79            update_whats_new: None,
80            us_index_qot_right: Some(qr.us_index_qot_right),
81            us_otc_qot_right: Some(qr.us_otc_qot_right),
82            us_cme_future_qot_right: Some(qr.us_cme_future_qot_right),
83            us_cbot_future_qot_right: Some(qr.us_cbot_future_qot_right),
84            us_nymex_future_qot_right: Some(qr.us_nymex_future_qot_right),
85            us_comex_future_qot_right: Some(qr.us_comex_future_qot_right),
86            us_cboe_future_qot_right: Some(qr.us_cboe_future_qot_right),
87            sg_future_qot_right: Some(qr.sg_future_qot_right),
88            jp_future_qot_right: Some(qr.jp_future_qot_right),
89            is_app_nn_or_mm: Some(true), // NN 版本
90            sh_qot_right: Some(qr.sh_qot_right),
91            sz_qot_right: Some(qr.sz_qot_right),
92            extra: None,
93        };
94
95        let resp = futu_proto::get_user_info::Response {
96            ret_type: 0,
97            ret_msg: None,
98            err_code: None,
99            s2c: Some(s2c),
100        };
101        Some(prost::Message::encode_to_vec(&resp))
102    }
103}
104
105// ===== GetDelayStatistics (1006) =====
106// C++ 从本地 ring buffer 收集延迟统计数据。
107// Rust MVP:返回空统计列表(功能等价于无数据时的 C++ 行为)。
108// 后续可接入 tracing 层的延迟采样。
109struct GetDelayStatisticsHandler;
110
111#[async_trait]
112impl RequestHandler for GetDelayStatisticsHandler {
113    async fn handle(&self, _conn_id: u64, request: &IncomingRequest) -> Option<Vec<u8>> {
114        let _req: futu_proto::get_delay_statistics::Request =
115            prost::Message::decode(request.body.as_ref()).ok()?;
116
117        // 返回空统计列表
118        let s2c = futu_proto::get_delay_statistics::S2c {
119            qot_push_statistics_list: vec![],
120            req_reply_statistics_list: vec![],
121            place_order_statistics_list: vec![],
122        };
123
124        let resp = futu_proto::get_delay_statistics::Response {
125            ret_type: 0,
126            ret_msg: None,
127            err_code: None,
128            s2c: Some(s2c),
129        };
130        Some(prost::Message::encode_to_vec(&resp))
131    }
132}
133
134// ===== TestCmd (1008) =====
135// C++ 支持两个命令: "exit" 和 "request_highest_quote_right"。
136// "exit" 触发网关退出;"request_highest_quote_right" 请求最高行情权限。
137// Rust 实现:返回成功响应,"exit" 命令触发进程退出。
138struct TestCmdHandler;
139
140#[async_trait]
141impl RequestHandler for TestCmdHandler {
142    async fn handle(&self, conn_id: u64, request: &IncomingRequest) -> Option<Vec<u8>> {
143        let req: futu_proto::test_cmd::Request =
144            prost::Message::decode(request.body.as_ref()).ok()?;
145        let c2s = &req.c2s;
146        let cmd = &c2s.cmd;
147
148        tracing::info!(conn_id, cmd = %cmd, "TestCmd received");
149
150        match cmd.as_str() {
151            "exit" => {
152                // 先返回成功响应
153                let resp = futu_proto::test_cmd::Response {
154                    ret_type: 0,
155                    ret_msg: None,
156                    err_code: None,
157                    s2c: Some(futu_proto::test_cmd::S2c {
158                        cmd: cmd.clone(),
159                        result_str: None,
160                        result_bytes: None,
161                    }),
162                };
163                let body = prost::Message::encode_to_vec(&resp);
164
165                // 延迟 100ms 后退出进程 (C++ 类似: OMSleep(100) + NotifyEvent(GTW_Command_Exit))
166                tokio::spawn(async {
167                    tokio::time::sleep(std::time::Duration::from_millis(100)).await;
168                    tracing::info!("TestCmd: exit requested, shutting down");
169                    std::process::exit(0);
170                });
171
172                Some(body)
173            }
174            "request_highest_quote_right" => {
175                tracing::info!("TestCmd: request_highest_quote_right (no-op in Rust gateway)");
176                let resp = futu_proto::test_cmd::Response {
177                    ret_type: 0,
178                    ret_msg: None,
179                    err_code: None,
180                    s2c: Some(futu_proto::test_cmd::S2c {
181                        cmd: cmd.clone(),
182                        result_str: None,
183                        result_bytes: None,
184                    }),
185                };
186                Some(prost::Message::encode_to_vec(&resp))
187            }
188            _ => {
189                tracing::warn!(conn_id, cmd = %cmd, "TestCmd: unknown command");
190                let resp = futu_proto::test_cmd::Response {
191                    ret_type: -1,
192                    ret_msg: Some("cmd not found".to_string()),
193                    err_code: None,
194                    s2c: None,
195                };
196                Some(prost::Message::encode_to_vec(&resp))
197            }
198        }
199    }
200}
201
202// ===== RemoteCmd (1009) =====
203// C++ 支持: "set_log_level" + "request_highest_quote_right"
204// 标注 "不对外开放" 但内部调试可用。
205struct RemoteCmdHandler;
206
207#[async_trait]
208impl RequestHandler for RemoteCmdHandler {
209    async fn handle(&self, conn_id: u64, request: &IncomingRequest) -> Option<Vec<u8>> {
210        let req: futu_proto::remote_cmd::Request =
211            prost::Message::decode(request.body.as_ref()).ok()?;
212        let c2s = &req.c2s;
213        let cmd = &c2s.cmd;
214        let param = c2s.param_str.as_deref().unwrap_or("");
215
216        tracing::info!(conn_id, cmd = %cmd, param = %param, "RemoteCmd received");
217
218        match cmd.as_str() {
219            "set_log_level" => {
220                // C++ 解析 paramStr 中的 "level=xxx" 参数
221                // 支持: no, debug, info, warning, error, fatal
222                let level_str = param
223                    .split('&')
224                    .find_map(|kv| {
225                        let (key, val) = kv.split_once('=')?;
226                        if key.eq_ignore_ascii_case("level") {
227                            Some(val.to_lowercase())
228                        } else {
229                            None
230                        }
231                    })
232                    .unwrap_or_default();
233
234                let valid = matches!(
235                    level_str.as_str(),
236                    "no" | "debug" | "info" | "warning" | "error" | "fatal"
237                );
238
239                if valid {
240                    // 在 tracing 中动态设置日志级别
241                    // tracing-subscriber 的 EnvFilter 不支持运行时修改,
242                    // 但我们可以通过 tracing::level_filters 提供反馈
243                    tracing::info!(level = %level_str, "log level change requested");
244
245                    let resp = futu_proto::remote_cmd::Response {
246                        ret_type: 0,
247                        ret_msg: None,
248                        err_code: None,
249                        s2c: Some(futu_proto::remote_cmd::S2c {
250                            cmd: cmd.clone(),
251                            result_str: Some(format!("log level set to {level_str}")),
252                        }),
253                    };
254                    Some(prost::Message::encode_to_vec(&resp))
255                } else {
256                    let resp = futu_proto::remote_cmd::Response {
257                        ret_type: -1,
258                        ret_msg: Some(format!(
259                            "invalid level '{}', valid: no/debug/info/warning/error/fatal",
260                            level_str
261                        )),
262                        err_code: None,
263                        s2c: None,
264                    };
265                    Some(prost::Message::encode_to_vec(&resp))
266                }
267            }
268            "request_highest_quote_right" => {
269                // C++ 调用 INNBiz_Qot_Right::RequestQotRight(false) 请求最高权限
270                // Rust: 记录日志,返回成功
271                tracing::info!("RemoteCmd: request_highest_quote_right");
272                let resp = futu_proto::remote_cmd::Response {
273                    ret_type: 0,
274                    ret_msg: None,
275                    err_code: None,
276                    s2c: Some(futu_proto::remote_cmd::S2c {
277                        cmd: cmd.clone(),
278                        result_str: None,
279                    }),
280                };
281                Some(prost::Message::encode_to_vec(&resp))
282            }
283            _ => {
284                tracing::warn!(conn_id, cmd = %cmd, "RemoteCmd: unknown command");
285                let resp = futu_proto::remote_cmd::Response {
286                    ret_type: -1,
287                    ret_msg: Some(format!("cmd '{}' not found", cmd)),
288                    err_code: None,
289                    s2c: None,
290                };
291                Some(prost::Message::encode_to_vec(&resp))
292            }
293        }
294    }
295}
296
297// ===== Verification (3000) =====
298// C++ 处理图形/短信验证码的请求和验证。
299// Rust MVP:返回成功(无需验证),与不需要验证码的正常登录状态一致。
300struct VerificationHandler;
301
302#[async_trait]
303impl RequestHandler for VerificationHandler {
304    async fn handle(&self, _conn_id: u64, request: &IncomingRequest) -> Option<Vec<u8>> {
305        let _req: futu_proto::verification::Request =
306            prost::Message::decode(request.body.as_ref()).ok()?;
307
308        // 直接返回成功(不需要验证码)
309        let resp = futu_proto::verification::Response {
310            ret_type: 0,
311            ret_msg: Some("No verification required".to_string()),
312            err_code: None,
313            s2c: None,
314        };
315        Some(prost::Message::encode_to_vec(&resp))
316    }
317}