1use crate::errors::{Result, TqError};
9use chrono::{DateTime, Utc};
10use futures::StreamExt;
11use serde_json::Value;
12use std::time::Duration;
13use tracing::{debug, info};
14
15pub async fn fetch_json(url: &str) -> Result<Value> {
37 info!("开始下载 JSON: {}", url);
38
39 let client = reqwest::Client::builder()
41 .gzip(true)
42 .brotli(true)
43 .timeout(Duration::from_secs(30))
44 .build()
45 .map_err(|e| TqError::NetworkError(format!("创建 HTTP 客户端失败: {}", e)))?;
46
47 let response = client
49 .get(url)
50 .send()
51 .await
52 .map_err(|e| TqError::NetworkError(format!("请求失败: {}", e)))?;
53
54 if !response.status().is_success() {
56 return Err(TqError::NetworkError(format!(
57 "HTTP 状态码错误: {}",
58 response.status()
59 )));
60 }
61
62 debug!("HTTP 状态: {}", response.status());
63
64 let mut stream = response.bytes_stream();
66 let mut buffer = Vec::new();
67
68 while let Some(chunk) = stream.next().await {
69 let chunk = chunk.map_err(|e| TqError::NetworkError(format!("下载数据失败: {}", e)))?;
70 buffer.extend_from_slice(&chunk);
71 debug!("已下载: {} 字节", buffer.len());
72 }
73
74 info!("下载完成,总大小: {} 字节", buffer.len());
75
76 let json: Value = serde_json::from_slice(&buffer)
78 .map_err(|e| TqError::ParseError(format!("JSON 解析失败: {}", e)))?;
79
80 Ok(json)
81}
82
83pub fn nanos_to_datetime(nanos: i64) -> DateTime<Utc> {
93 let secs = nanos / 1_000_000_000;
94 let nsecs = (nanos % 1_000_000_000) as u32;
95 DateTime::from_timestamp(secs, nsecs).unwrap_or_else(|| Utc::now())
96}
97
98pub fn datetime_to_nanos(dt: &DateTime<Utc>) -> i64 {
108 dt.timestamp() * 1_000_000_000 + dt.timestamp_subsec_nanos() as i64
109}
110
111pub fn split_symbol(symbol: &str) -> (&str, &str) {
130 if let Some(pos) = symbol.find('.') {
131 let exchange = &symbol[..pos];
132 let instrument = &symbol[pos + 1..];
133 (exchange, instrument)
134 } else {
135 ("", symbol)
136 }
137}
138
139pub fn get_exchange(symbol: &str) -> &str {
149 split_symbol(symbol).0
150}
151
152pub fn generate_chart_id(prefix: &str) -> String {
162 let uuid = uuid::Uuid::new_v4();
163 format!("{}_{}", prefix, uuid)
164}
165
166pub fn is_nan_string(s: &str) -> bool {
176 s == "NaN" || s == "-" || s.is_empty()
177}
178
179pub fn value_to_i64(value: &Value) -> i64 {
189 match value {
190 Value::Number(n) => n.as_i64().unwrap_or(0),
191 Value::String(s) => s.parse().unwrap_or(0),
192 _ => 0,
193 }
194}
195
196pub fn value_to_f64(value: &Value) -> f64 {
206 match value {
207 Value::Number(n) => n.as_f64().unwrap_or(0.0),
208 Value::String(s) => {
209 if is_nan_string(s) {
210 f64::NAN
211 } else {
212 s.parse().unwrap_or(0.0)
213 }
214 }
215 _ => 0.0,
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222
223 #[test]
224 fn test_split_symbol() {
225 let (exchange, instrument) = split_symbol("SHFE.au2602");
226 assert_eq!(exchange, "SHFE");
227 assert_eq!(instrument, "au2602");
228
229 let (exchange, instrument) = split_symbol("DCE.m2512");
230 assert_eq!(exchange, "DCE");
231 assert_eq!(instrument, "m2512");
232
233 let (exchange, instrument) = split_symbol("invalid");
234 assert_eq!(exchange, "");
235 assert_eq!(instrument, "invalid");
236 }
237
238 #[test]
239 fn test_get_exchange() {
240 assert_eq!(get_exchange("SHFE.au2602"), "SHFE");
241 assert_eq!(get_exchange("DCE.m2512"), "DCE");
242 assert_eq!(get_exchange("invalid"), "");
243 }
244
245 #[test]
246 fn test_is_nan_string() {
247 assert!(is_nan_string("NaN"));
248 assert!(is_nan_string("-"));
249 assert!(is_nan_string(""));
250 assert!(!is_nan_string("123"));
251 assert!(!is_nan_string("0"));
252 }
253
254 #[test]
255 fn test_datetime_conversion() {
256 let now = Utc::now();
257 let nanos = datetime_to_nanos(&now);
258 let dt = nanos_to_datetime(nanos);
259
260 let diff = (dt.timestamp() - now.timestamp()).abs();
262 assert!(diff <= 1);
263 }
264
265 #[test]
266 fn test_generate_chart_id() {
267 let id1 = generate_chart_id("TQGO_kline");
268 let id2 = generate_chart_id("TQGO_kline");
269
270 assert!(id1.starts_with("TQGO_kline_"));
271 assert!(id2.starts_with("TQGO_kline_"));
272 assert_ne!(id1, id2); }
274}
275