ソースを参照

账号变更生成Html

hl 1 年間 前
コミット
087f7bf7c7

+ 110 - 3
Cargo.lock

@@ -109,6 +109,15 @@ dependencies = [
  "wyz",
 ]
 
+[[package]]
+name = "block-buffer"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
+dependencies = [
+ "generic-array",
+]
+
 [[package]]
 name = "block-buffer"
 version = "0.10.4"
@@ -265,6 +274,22 @@ dependencies = [
  "typenum",
 ]
 
+[[package]]
+name = "crypto-mac"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab"
+dependencies = [
+ "generic-array",
+ "subtle",
+]
+
+[[package]]
+name = "data-encoding"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
+
 [[package]]
 name = "deranged"
 version = "0.3.10"
@@ -274,13 +299,22 @@ dependencies = [
  "powerfmt",
 ]
 
+[[package]]
+name = "digest"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
+dependencies = [
+ "generic-array",
+]
+
 [[package]]
 name = "digest"
 version = "0.10.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
 dependencies = [
- "block-buffer",
+ "block-buffer 0.10.4",
  "crypto-common",
 ]
 
@@ -471,6 +505,22 @@ version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
 
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "hmac"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840"
+dependencies = [
+ "crypto-mac",
+ "digest 0.9.0",
+]
+
 [[package]]
 name = "http"
 version = "0.2.11"
@@ -733,6 +783,12 @@ version = "1.18.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
 
+[[package]]
+name = "opaque-debug"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
+
 [[package]]
 name = "openssl"
 version = "0.10.61"
@@ -831,7 +887,7 @@ checksum = "7c747191d4ad9e4a4ab9c8798f1e82a39affe7ef9648390b7e5548d18e099de6"
 dependencies = [
  "once_cell",
  "pest",
- "sha2",
+ "sha2 0.10.8",
 ]
 
 [[package]]
@@ -1070,6 +1126,21 @@ dependencies = [
  "winreg",
 ]
 
+[[package]]
+name = "ring"
+version = "0.16.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
+dependencies = [
+ "cc",
+ "libc",
+ "once_cell",
+ "spin",
+ "untrusted",
+ "web-sys",
+ "winapi",
+]
+
 [[package]]
 name = "rkyv"
 version = "0.7.42"
@@ -1239,6 +1310,19 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "sha2"
+version = "0.9.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
+dependencies = [
+ "block-buffer 0.9.0",
+ "cfg-if",
+ "cpufeatures",
+ "digest 0.9.0",
+ "opaque-debug",
+]
+
 [[package]]
 name = "sha2"
 version = "0.10.8"
@@ -1247,7 +1331,7 @@ checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
 dependencies = [
  "cfg-if",
  "cpufeatures",
- "digest",
+ "digest 0.10.7",
 ]
 
 [[package]]
@@ -1300,6 +1384,18 @@ dependencies = [
  "windows-sys 0.48.0",
 ]
 
+[[package]]
+name = "spin"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
+
+[[package]]
+name = "subtle"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
+
 [[package]]
 name = "syn"
 version = "1.0.109"
@@ -1409,12 +1505,17 @@ name = "ticker_viewer"
 version = "0.1.0"
 dependencies = [
  "chrono",
+ "data-encoding",
  "handlebars",
+ "hex",
+ "hmac",
  "reqwest",
+ "ring",
  "rust_decimal",
  "rust_decimal_macros",
  "serde",
  "serde_json",
+ "sha2 0.9.9",
  "tokio",
  "toml",
  "tracing",
@@ -1682,6 +1783,12 @@ dependencies = [
  "tinyvec",
 ]
 
+[[package]]
+name = "untrusted"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
+
 [[package]]
 name = "url"
 version = "2.5.0"

+ 10 - 1
Cargo.toml

@@ -18,4 +18,13 @@ chrono = "0.4"
 rust_decimal = { version = "1.32.0", features = ["maths"] }
 rust_decimal_macros = "1.32.0"
 toml = "0.8.8"
-uuid = { version = "1.6.1", features = ["v4"] }
+uuid = { version = "1.6.1", features = ["v4"] }
+
+
+
+
+ring = "0.16.20"
+data-encoding = "2.4.0"
+hex = "0.4"
+hmac = "0.8.1"
+sha2 = "0.9.8"

+ 156 - 0
src/export/htmlzz.rs

@@ -0,0 +1,156 @@
+use std::collections::BTreeMap;
+use std::fs::File;
+use std::io::Write;
+use std::str::FromStr;
+use chrono::NaiveDateTime;
+use handlebars::Handlebars;
+use rust_decimal::Decimal;
+use rust_decimal::prelude::{FromPrimitive, ToPrimitive};
+use uuid::Uuid;
+use rust_decimal_macros::dec;
+use serde::{Deserialize, Serialize};
+use serde_json::{json, Value};
+use tracing::info;
+use crate::export::html::{ExportExchangeTickerInfo, SeriesInfo};
+use crate::struct_standard::Trades;
+use crate::utils::utils::ConfigInfo;
+
+
+pub fn export_html(config: ConfigInfo, acc_name_all: &Vec<String>, x_time: &Vec<i64>, data_list: &Vec<Vec<String>>) {
+    info!("正在生成网页,请稍后!");
+    let export_path = if config.export_path == "" { "./" } else { config.export_path.as_str() };
+    let export_name = if config.export_name == "" { "acc_all" } else { config.export_name.as_str() };
+    let path = format!("{}/{}.html", export_path, export_name).replace("//", "/");
+    // 创建 Handlebars 实例
+    let mut handlebars = Handlebars::new();
+
+    // 数据组装
+    // info!("账号昵称数组:{:?}",acc_name_all);
+    // info!("X_时间片:{:?}",x_time);
+    // info!("X_时间片:{:?}",x_time);
+    // info!("组装数据原数组:{:?}",data_list);
+    //时间片数据组装
+    let mut time_str_x = vec![];
+    for time in x_time {
+        let time_str = NaiveDateTime::from_timestamp_millis((time + 8 * 3600) * 1000).unwrap().format("%Y-%m-%d %H:%M:%S%.3f").to_string();
+        time_str_x.push(time_str);
+    }
+    //每个账号的数据
+    let mut map: BTreeMap<String, Vec<f64>> = BTreeMap::new();
+    for acc_name in acc_name_all.clone() {
+        let mut pr_data: Vec<f64> = vec![];
+        let z = acc_name.clone();
+        let key = z.as_str();
+        if map.contains_key(key) {
+            pr_data = map.get(key).unwrap().clone();
+        }
+
+        for data in data_list.clone() {
+            if data[0].clone().as_str() == key {
+                let pr = data[4].clone().parse::<f64>().unwrap();
+                pr_data.push(pr);
+            }
+        }
+        map.insert(key.to_string(), pr_data);
+    }
+
+    let mut series_all=   Vec::new();
+    for (key,val) in map{
+        // info!("{:?}=---{:?}",key.to_string(),val.len());
+        let series = json!({"name": key.to_string(), "type": "line", "smooth": true, "data": val});
+        // info!("数据:{:?}",series);
+        series_all.push(series);
+    }
+    let json = json!({
+        "data":series_all
+    });
+
+    let strzzz =  format!("{}",json["data"]);
+    // info!("时间片:{:?}----{:?}",time_str_x.len(),time_str_x[time_str_x.len()-1]);
+
+    let data = serde_json::json!({
+        "chart_title": format!("账户合约余额变更历史"),
+        "acc_name_all": acc_name_all.clone(),
+    });
+    // HTML 模板
+    let template = r#"
+        <!DOCTYPE html>
+        <html>
+        <head>
+            <meta charset="UTF-8">
+            <title>{{chart_title}}</title>
+            <script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.4.3/echarts.min.js"></script>
+            <script src="https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.11.10/dayjs.min.js"></script>
+
+            <style>
+                * {
+                    margin: 0;
+                    padding: 0;
+                }
+                #main {
+                    margin: 50px auto 0;
+                    width: calc(100vw - 100px);
+                    height: calc(100vh - 100px);
+                }
+            </style>
+        </head>
+        <body>
+            <div id="main"></div>
+        </body>
+        <script>
+            var exchangeColor = {binance: '#F4BC0C', gate: '#0068FF', okx: '#171F30'};
+            var chartDom = document.getElementById('main');
+            var myChart = echarts.init(chartDom);
+            var option;
+
+            option = {
+                  title: {
+                    text: '{{chart_title}}'
+                  },
+                  tooltip: {
+                    trigger: 'axis'
+                  },
+                   toolbox: {
+                    feature: {
+                      dataZoom: {},
+                      brush: {
+                        type: ['rect', 'clear']
+                      }
+                    }
+                  },
+                  legend: {
+                       data: "#.to_owned() + format!("{:?}", acc_name_all).as_ref() + r#"
+                  },
+
+
+                  xAxis: {
+                    type: 'category',
+                    data: "# + format!("{:?}", time_str_x).as_ref() + r#"
+                  },
+                  yAxis: {
+                    type: 'value',
+                    scale: true
+                  },
+                  series: "# + format!("{}", strzzz).as_ref() + r#"
+                };
+
+            option && myChart.setOption(option);
+            </script>
+        </html>
+    "#;
+
+    // 编译模板
+    handlebars
+        .register_template_string("page", template)
+        .expect("编译模版失败!");
+
+    // 渲染模板
+    let output = handlebars
+        .render("page", &data)
+        .expect("渲染模版失败!");
+
+    // 将 HTML 写入文件
+    let mut file = File::create(&path).expect("创建文件失败!");
+    file.write_all(output.as_bytes()).expect("写入文件到本地失败!");
+    info!("网页生成成功!路径:{:?}", path);
+}

+ 2 - 1
src/export/mod.rs

@@ -1 +1,2 @@
-pub mod html;
+pub mod html;
+pub mod htmlzz;

+ 224 - 0
src/gate_swap/gate_export_blance.rs

@@ -0,0 +1,224 @@
+use std::collections::{BTreeMap, HashSet};
+use std::str::FromStr;
+use chrono::NaiveDateTime;
+
+use tracing::info;
+
+use crate::{export, utils};
+use crate::gate_swap::gate_swap_rest_utils::GateSwapRest;
+
+pub async fn creation_account_book_html(start_time: i64) {
+    let config = utils::utils::get_config_info("./config.toml");
+
+
+    //获取不同账号的数据
+    let mut acc_array: Vec<BTreeMap<String, String>> = vec![];
+    let mut acc_all_data: BTreeMap<String, Vec<Vec<String>>> = BTreeMap::new();
+    let mut acc_all_data_clone: BTreeMap<String, Vec<Vec<String>>> = BTreeMap::new();
+    let mut data_array_all: Vec<Vec<String>> = vec![];
+    let mut acc_name_all: Vec<String> = vec![];
+    let mut time_all: Vec<i64> = vec![];
+    loop {
+        let mut gate60: BTreeMap<String, String> = BTreeMap::new();
+        gate60.insert("acc_name".to_string(), String::from(""));
+        gate60.insert("access_key".to_string(), String::from(""));
+        gate60.insert("secret_key".to_string(), String::from(""));
+        acc_array.push(gate60);
+
+
+        if acc_array.len() == 0 {
+            info!("没有账号信息");
+            return;
+        }
+
+
+        for acc in acc_array {
+            let mut gate_exc = GateSwapRest::new(false, acc.clone());
+            let data = gate_exc.account_book("usdt".to_string(),start_time).await;
+            info!("请求完成{:?}",data.clone());
+            if data.code.as_str() == "200" {
+                let acc_name = acc.get("acc_name").unwrap().clone();
+                acc_name_all.push(acc_name.clone());
+
+                //账号
+                let json_value: serde_json::Value = serde_json::from_str(&data.data).unwrap();
+                if let serde_json::Value::Array(array) = json_value {
+                    let mut name_data_all: Vec<Vec<String>> = vec![];
+                    let mut name_data_all_clone: Vec<Vec<String>> = vec![];
+                    let mut times = 0;
+
+                    for item in array {
+                        let time = item["time"].as_i64().unwrap();//秒级
+                        if time == times{ continue;}
+                        times = time;
+
+                        time_all.push(time);
+                        // let time_str = NaiveDateTime::from_timestamp_millis((time + 8 * 3600) * 1000).unwrap().format("%Y-%m-%d %H:%M:%S%.3f").to_string();
+                        // info!("{:?}数据时间解析:{:?}",acc_name.clone(),time_str);
+
+
+                        let change = item["change"].as_str().unwrap();
+
+                        let balance = item["balance"].as_str().unwrap();
+                        let type_str = match item["type"].as_str().unwrap() {
+                            "dnw" => { "转入转出" ;continue; }
+                            "pnl" => { "减仓盈亏" }
+                            "fee" => { "交易手续费";continue; }
+                            "refr" => { "推荐人返佣" ;continue;}
+                            "fund" => { "资金费用";continue; }
+                            "point_dnw" => { "点卡转入转出" ;continue;}
+                            "point_fee" => { "点卡交易手续费" ;continue;}
+                            "point_refr" => { "点卡推荐人返佣";continue; }
+                            _ => {
+                                "未知-变更类型";continue;
+                            }
+                        };
+
+
+
+                        let text = item["text"].as_str().unwrap();
+                        let contract = item["contract"].as_str().unwrap();
+                        let trade_id = item["trade_id"].as_str().unwrap();
+
+                        let mut name_data_array: Vec<String> = vec![];
+                        name_data_array.push(time.to_string());
+                        name_data_array.push(trade_id.to_string());
+                        name_data_array.push(change.to_string());
+                        name_data_array.push(balance.to_string());
+                        name_data_array.push(type_str.to_string());
+                        name_data_array.push(contract.to_string());
+                        name_data_array.push(text.to_string());
+
+                        name_data_all.push(name_data_array.clone());
+                    }
+                    acc_all_data.insert(acc_name.clone(), name_data_all.clone());
+                } else {
+                    info!("不是数组 检查数据");
+                }
+            }
+            // break;
+        }
+        break;//这里是为了 代码收纳,用了loop来放置代码
+    }
+    // info!("数据如下:{:?}",acc_all_data);
+
+    //1. 生成时间片
+    let mut unique = HashSet::new();
+    time_all.retain(|e| unique.insert(*e));
+    time_all.sort_by(|a, b| a.cmp(&b));
+    // info!("数据如下1:{:?}",time_all);
+
+    //2. 数据根据时间片 补全数据
+    for time in &time_all {
+        // 根据时间片去拿数据 ,如果有添加没有,手动添加
+        let mut sum_pr: f64 = 0 as f64;
+        for (key, vale) in acc_all_data.clone() {
+            let mut value = vale.clone();
+            value.sort_by(|a, b| a[0].cmp(&b[0]));
+            let mut da: Option<Vec<String>> = None;
+            for v in value.clone() {
+                let acc_time = v[0].parse::<i64>().unwrap();
+                if acc_time == time.clone() {
+                    //时间相同,如果记录的 时间片不存在,则直接保存,如果存在则比较成交id,
+                    if da.is_none() {
+                        da = Option::from(v);//记录当前实际
+                    } else {
+                        let da_id = da.clone().unwrap()[1].clone();
+                        let v_id = v[1].clone();
+                        if da_id < v_id {
+                            da = Option::from(v);//记录当前实际
+                        }
+                    }
+                }
+                // info!("数据如下2:{:?}",time_all);
+            }
+
+            //如果匹配到时间片,直接使用,没有则创建
+            let new_d: Vec<String> = match da {
+                None => {
+                    let mut row: Vec<String> = vec![];
+                    let filter_arrya: Vec<Vec<String>> = value.clone().into_iter().filter(|s| {
+                        // info!("读取time:{:?}",s);
+                        let time_v = s[0].clone().parse::<i64>().unwrap();
+                        time_v < time.clone()
+                    }).collect();
+                    // info!("计算filter_arrya:---{:?}",filter_arrya.clone());
+                    let acc_name = key.clone().to_string();
+                    let acc_time = time.to_string();
+                    let acc_o_id = "填补数据类型".to_string();
+                    let mut acc_change = "0".to_string();
+                    let mut acc_balance = "0".to_string();
+                    let acc_type = "填补数据类型".to_string();
+                    let mut acc_contract = "填补数据类型".to_string();
+                    let acc_text = "填补数据类型".to_string();
+
+                    if filter_arrya.len() > 0 {
+                        // info!("{:?}--{}--{:?}",acc_name, time, filter_arrya[filter_arrya.len() - 1].clone());
+                        let zj_v = filter_arrya[filter_arrya.len() - 1].clone();
+
+                        acc_change = zj_v[2].clone();
+                        acc_balance = zj_v[3].clone();
+                        acc_contract = zj_v[5].clone();
+                    }
+
+
+                    row.push(acc_name);
+                    row.push(acc_time);
+                    row.push(acc_o_id);
+                    row.push(acc_change);
+                    row.push(acc_balance);
+                    row.push(acc_type);
+                    row.push(acc_contract);
+                    row
+                }
+                Some(d) => {
+                    let mut row: Vec<String> = vec![];
+                    row.push(key.clone());
+                    row.extend(d.clone());
+                    row
+                }
+            };
+            let time_str = NaiveDateTime::from_timestamp_millis((new_d[1].clone().parse::<i64>().unwrap() + 8 * 3600) * 1000).unwrap().format("%Y-%m-%d %H:%M:%S%.3f").to_string();
+            // info!("{:?}----时间片:{:?}",new_d[0].clone(),time_str);
+            // info!("计算3333:---{:?}",time_str);
+            // info!("{:?}=----pr:{:?}",key.clone().to_string(),new_d[4].clone());
+            sum_pr = sum_pr + new_d[4].clone().parse::<f64>().unwrap();
+            data_array_all.push(new_d);
+        }
+
+        let mut sum_row: Vec<String> = vec![];
+        sum_row.push("sum_acc".to_string());
+        sum_row.push(time.clone().to_string());
+        sum_row.push("".to_string());
+        sum_row.push("0".to_string());
+        sum_row.push(format!("{}", sum_pr));
+        sum_row.push("".to_string());
+        sum_row.push("".to_string());
+        data_array_all.push(sum_row.clone());
+        let time_str2 = NaiveDateTime::from_timestamp_millis((time + 8 * 3600) * 1000).unwrap().format("%Y-%m-%d %H:%M:%S%.3f").to_string();
+        // info!("------------------计算111:-----{:?}",time_str2);
+    }
+    // println!("数据如下:{:?}", data_array_all);
+
+    // info!("时间片:{:?}",time_all.clone());
+    // info!("时间片长度:{:?}---数据节点长度{:?}", time_all.clone().len(),data_array_all.clone().len());
+
+
+    //限制时间之外的数据过滤
+    let start_time_str = NaiveDateTime::from_timestamp_millis((start_time + 8 * 3600) * 1000).unwrap().format("%Y-%m-%d %H:%M:%S%.3f").to_string();
+    let time_all_array: Vec<i64> = time_all.clone().into_iter().filter(|s| {
+        s >= &start_time
+    }).collect();
+    time_all = time_all_array;
+
+    let time_all_array: Vec<Vec<String>> = data_array_all.clone().into_iter().filter(|s| {
+        let t = s[1].clone().parse::<i64>().unwrap();
+        t >= start_time
+    }).collect();
+    data_array_all = time_all_array;
+
+
+    //生成 html
+    acc_name_all.push("sum_acc".to_string());
+    export::htmlzz::export_html(config.clone(), &acc_name_all, &time_all, &data_array_all);
+}

+ 332 - 0
src/gate_swap/gate_swap_rest_utils.rs

@@ -0,0 +1,332 @@
+use std::collections::BTreeMap;
+
+use hex;
+use hmac::{Hmac, Mac, NewMac};
+use reqwest::Client;
+use reqwest::header::HeaderMap;
+use ring::digest;
+use rust_decimal::Decimal;
+use rust_decimal::prelude::FromPrimitive;
+use rust_decimal_macros::dec;
+use serde_json::Value;
+use sha2::Sha512;
+use tracing::{info, trace};
+use crate::http::request::Response;
+use crate::utils::utils::parse_params_to_str;
+
+
+#[derive(Clone)]
+pub struct GateSwapRest {
+    label: String,
+    base_url: String,
+    client: reqwest::Client,
+    /*******参数*/
+    //登陆所需参数
+    login_param: BTreeMap<String, String>,
+    delays: Vec<i64>,
+    max_delay: i64,
+    avg_delay: Decimal,
+}
+
+impl GateSwapRest {
+    /*******************************************************************************************************/
+    /*****************************************获取一个对象****************************************************/
+    /*******************************************************************************************************/
+    pub fn new(is_colo: bool, login_param: BTreeMap<String, String>) -> GateSwapRest
+    {
+        return GateSwapRest::new_label("default-GateSwapRest".to_string(), is_colo, login_param);
+    }
+    pub fn new_label(label: String, is_colo: bool, login_param: BTreeMap<String, String>) -> GateSwapRest
+    {
+        let base_url = if is_colo {
+            let url = "https://apiv4-private.gateapi.io".to_string();
+            info!("开启高速通道:{:?}",url);
+            url
+        } else {
+            let url = "https://api.gateio.ws".to_string();
+            info!("走普通通道:{}",url);
+            url
+        };
+
+
+        if is_colo {} else {}
+        /*****返回结构体*******/
+        GateSwapRest {
+            label,
+            base_url: base_url.to_string(),
+            client: Client::new(),
+            login_param,
+            delays: vec![],
+            max_delay: 0,
+            avg_delay: dec!(0.0),
+        }
+    }
+
+    /*******************************************************************************************************/
+    /*****************************************rest请求函数********************************************************/
+    //查询个人成交记录
+    pub async fn my_trades(&mut self, settle: String, contract: String, limit: i64) -> Response {
+        let mut params = serde_json::json!({
+            "contract":contract,
+            "limit":1000
+        });
+        if limit > 0 {
+            params["limit"] = serde_json::json!(limit);
+        }
+
+        let data = self.request("GET".to_string(),
+                                "/api/v4".to_string(),
+                                format!("/futures/{}/my_trades", settle),
+                                true,
+                                params.to_string(),
+        ).await;
+        data
+    }
+
+    //查询合约账户变更历史
+    pub async fn account_book(&mut self, settle: String,from:i64) -> Response {
+        let params = serde_json::json!({
+                "limit":100
+             });
+        let data = self.request("GET".to_string(),
+                                "/api/v4".to_string(),
+                                format!("/futures/{}/account_book", settle),
+                                true,
+                                params.to_string(),
+        ).await;
+        data
+    }
+
+
+    /*******************************************************************************************************/
+    /*****************************************工具函数********************************************************/
+    /*******************************************************************************************************/
+    pub fn get_delays(&self) -> Vec<i64> {
+        self.delays.clone()
+    }
+    pub fn get_avg_delay(&self) -> Decimal {
+        self.avg_delay.clone()
+    }
+    pub fn get_max_delay(&self) -> i64 {
+        self.max_delay.clone()
+    }
+    fn get_delay_info(&mut self) {
+        let last_100 = if self.delays.len() > 100 {
+            self.delays[self.delays.len() - 100..].to_vec()
+        } else {
+            self.delays.clone()
+        };
+
+        let max_value = last_100.iter().max().unwrap();
+        if max_value.clone().to_owned() > self.max_delay {
+            self.max_delay = max_value.clone().to_owned();
+        }
+
+        let sum: i64 = last_100.iter().sum();
+        let sum_v = Decimal::from_i64(sum).unwrap();
+        let len_v = Decimal::from_u64(last_100.len() as u64).unwrap();
+        self.avg_delay = (sum_v / len_v).round_dp(1);
+        self.delays = last_100.clone().into_iter().collect();
+    }
+
+    //调用请求
+    async fn request(&mut self,
+                     requesst_type: String,
+                     prefix_url: String,
+                     request_url: String,
+                     is_login: bool,
+                     params: String) -> Response
+    {
+        // trace!("login_param:{:?}", self.login_param);
+        //解析账号信息
+        let mut access_key = "".to_string();
+        let mut secret_key = "".to_string();
+        if self.login_param.contains_key("access_key") {
+            access_key = self.login_param.get("access_key").unwrap().to_string();
+        }
+        if self.login_param.contains_key("secret_key") {
+            secret_key = self.login_param.get("secret_key").unwrap().to_string();
+        }
+        let mut is_login_param = true;
+        if access_key == "" || secret_key == "" {
+            is_login_param = false
+        }
+
+        //请求头配置-如果需要登陆则存在额外配置
+        let mut body = "".to_string();
+        let timestamp = chrono::Utc::now().timestamp().to_string();
+
+        let mut headers = HeaderMap::new();
+        if requesst_type == "GET" {
+            headers.insert("Content-type", "application/x-www-form-urlencoded".parse().unwrap());
+            headers.insert("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) ".parse().unwrap());
+        } else {
+            headers.insert("Accept", "application/json".parse().unwrap());
+            headers.insert("Content-Type", "application/json".parse().unwrap());
+        }
+
+        if requesst_type == "POST" {
+            body = params.clone();
+        }
+
+        //是否需要登陆-- 组装sing
+        if is_login {
+            if !is_login_param {
+                let mut res = Response::new();
+                res.code = "-1".to_string();
+                res.msg = "登陆参数错误".to_string();
+                return res;
+            } else {//需要登陆-且登陆参数齐全
+                //组装sing
+                let sing = Self::sign(secret_key.clone(),
+                                      requesst_type.clone(),
+                                      prefix_url.clone(),
+                                      request_url.clone(),
+                                      params.clone(),
+                                      body.clone(),
+                                      timestamp.clone(),
+                );
+                // trace!("sing:{}", sing);
+                //组装header
+                headers.extend(Self::headers(access_key, timestamp, sing));
+            }
+        }
+
+        // trace!("headers:{:?}", headers);
+        let base_url = format!("{}{}", prefix_url.clone(), request_url.clone());
+        let start_time = chrono::Utc::now().timestamp_millis();
+        let get_response = self.http_toll(
+            base_url.clone(),
+            requesst_type.to_string(),
+            params.clone(),
+            headers,
+        ).await;
+        let res_data = Self::res_data_analysis(get_response, base_url, params);
+        res_data
+    }
+
+    pub fn headers(access_key: String, timestamp: String, sign: String) -> HeaderMap {
+        let mut headers = HeaderMap::new();
+        headers.insert("KEY", access_key.clone().parse().unwrap());
+        headers.insert("Timestamp", timestamp.clone().parse().unwrap());
+        headers.insert("SIGN", sign.clone().parse().unwrap());
+        headers
+    }
+    pub fn sign(secret_key: String,
+                requesst_type: String, prefix_url: String, request_url: String,
+                params: String, body_data: String, timestamp: String) -> String
+    {
+        let url = format!("{}{}", prefix_url, request_url);
+        let params_str = parse_params_to_str(params);
+        let body = Some(body_data);
+        let hashed_payload = if let Some(body) = body {
+            let mut m = digest::Context::new(&digest::SHA512);
+            m.update(body.as_bytes());
+            hex::encode(m.finish().as_ref())
+        } else {
+            String::new()
+        };
+        // trace!("hashed_payload:{}", hashed_payload);
+
+        let message = format!("{}\n{}\n{}\n{}\n{}",
+                              requesst_type,
+                              url,
+                              params_str,
+                              hashed_payload,
+                              timestamp);
+        // trace!("**********", );
+        // trace!("组装数据:{}", message);
+        // trace!("**********", );
+
+        let mut mac = Hmac::<Sha512>::new_varkey(secret_key.as_bytes()).expect("Failed to create HMAC");
+        mac.update(message.as_bytes());
+        let result = mac.finalize().into_bytes();
+        let sign = hex::encode(result);
+        sign
+    }
+
+
+    async fn http_toll(&mut self, request_path: String, request_type: String, params: String, headers: HeaderMap) -> Result<Response, reqwest::Error> {
+        /****请求接口与 地址*/
+        let url = format!("{}{}", self.base_url.to_string(), request_path);
+        let request_type = request_type.clone().to_uppercase();
+        let addrs_url = format!("{}?{}", url.clone(), parse_params_to_str(params.clone()));
+        // let params_json: serde_json::Value = serde_json::from_str(&params).unwrap();
+        // trace!("url:{}",url);
+        // trace!("addrs_url:{}",url);
+        // trace!("params_json:{}",params_json);
+        // trace!("headers:{:?}",headers);
+
+        let req = match request_type.as_str() {
+            "GET" => self.client.get(addrs_url.clone()).headers(headers),
+            "POST" => self.client.post(addrs_url.clone()).body(params).headers(headers),
+            "DELETE" => self.client.delete(addrs_url.clone()).headers(headers),
+            // "PUT" => self.client.put(url.clone()).json(&params),
+            _ => {
+                let mut res = Response::new();
+                res.code = "-1".to_string();
+                res.msg = "请求类型错误".to_string();
+                res.data = "".to_string();
+                return Ok((res));
+            }
+        };
+
+        let response = req.send().await?;
+        let mut res = Response::new();
+        if response.status().is_success() {
+            // 读取响应的内容
+            let body = response.text().await?;
+            res.code = "200".to_string();
+            res.msg = "success".to_string();
+            res.data = body;
+            // trace!("ok-----{}", body);
+        } else {
+            let body = response.text().await?;
+            res.code = "-1".to_string();
+            res.msg = body;
+            res.data = "".to_string();
+        }
+        Ok((res))
+    }
+
+    //res_data 解析
+    pub fn res_data_analysis(result: Result<Response, reqwest::Error>, base_url: String, params: String) -> Response {
+        let res = Response::new();
+        match result {
+            Ok(res_data) => {
+                // info!("原数据:{:?}",res_data);
+                if res_data.code != "200" {
+                    let message = res_data.msg;
+                    // let json_value: serde_json::Value = serde_json::from_str(&message).unwrap();//这种方式会触发 解析错误
+                    let json_value = serde_json::from_str::<Value>(&message);
+                    match json_value {
+                        Ok(data) => {
+                            let mut error = Response::new();
+                            error.code = "-1".to_string();
+                            error.msg = format!("请求错误{:?}", res.msg);
+                            error.data = format!("请求地址:{},请求参数:{}", base_url, params);
+                            error
+                        }
+                        Err(e) => {
+                            // error!("解析错误:{:?}",e);
+                            let mut error = Response::new();
+                            error.code = "-1".to_string();
+                            error.msg = format!("json 解析失败:{},相关参数:{}", e, message);
+                            error.data = "".to_string();
+                            error
+                        }
+                    }
+                } else {
+                    res_data
+                }
+            }
+            Err(err) => {
+                let mut error = Response::new();
+                error.code = "-1".to_string();
+                error.msg = format!("json 解析失败:{},相关参数:{}", err, params);
+                error.data = "".to_string();
+                error
+            }
+        }
+    }
+}

+ 3 - 1
src/gate_swap/mod.rs

@@ -1,2 +1,4 @@
+pub mod gate_swap_rest_utils;
+pub mod gate_swap_standard;
 pub mod gate_swap_rest;
-pub mod gate_swap_standard;
+pub mod gate_export_blance;

+ 80 - 78
src/main.rs

@@ -1,10 +1,8 @@
 use std::str::FromStr;
-use chrono::{NaiveDateTime};
-use rust_decimal::Decimal;
+
 use rust_decimal::prelude::ToPrimitive;
-use tracing::{error, info};
-use crate::export::html::ExportExchangeTickerInfo;
-use crate::struct_standard::Trades;
+
+use crate::gate_swap::gate_export_blance::{ creation_account_book_html};
 use crate::utils::logs;
 
 pub mod binance_swap;
@@ -21,79 +19,83 @@ pub mod handle_ticker;
 async fn main() {
     logs::init_log_with_info();
 
-    let config = utils::utils::get_config_info("./config.toml");
-    let config_clone = config.clone();
-    if http::proxy::ParsingDetail::http_enable_proxy(config_clone.proxy_address) {
-        info!("检测有代理配置,配置走代理");
-    }
-    let symbol = config_clone.symbol;
-    let start_at = NaiveDateTime::parse_from_str(&config.range_time[0].clone(), "%Y-%m-%d %H:%M:%S").unwrap().timestamp() - 8 * 3600;
-    let end_at = NaiveDateTime::parse_from_str(&config.range_time[1].clone(), "%Y-%m-%d %H:%M:%S").unwrap().timestamp() - 8 * 3600;
+    // let config = utils::utils::get_config_info("./config.toml");
+    // let config_clone = config.clone();
+    // if http::proxy::ParsingDetail::http_enable_proxy(config_clone.proxy_address) {
+    //     info!("检测有代理配置,配置走代理");
+    // }
+    // let symbol = config_clone.symbol;
+    // let start_at = NaiveDateTime::parse_from_str(&config.range_time[0].clone(), "%Y-%m-%d %H:%M:%S").unwrap().timestamp() - 8 * 3600;
+    // let end_at = NaiveDateTime::parse_from_str(&config.range_time[1].clone(), "%Y-%m-%d %H:%M:%S").unwrap().timestamp() - 8 * 3600;
+    //
+    // let recall_start_at = start_at - config.recall_time / 1000;
+    //
+    // let mut exchange_list = vec![];
+    // for exchange in config.exchanges.clone() {
+    //     let exchange_up = exchange.to_uppercase();
+    //     let exchange_result = match exchange_up.as_str() {
+    //         "BINANCE" => {
+    //             let recall_ticker_info = handle_ticker::get_binance_ticker_info(&symbol, &recall_start_at.to_string(), &end_at.to_string()).await;
+    //             let ticker_info: Vec<Trades> = recall_ticker_info.iter().filter(|item| item.create_time.parse::<i64>().unwrap() >= start_at * 1000).cloned().collect();
+    //             let mut max_price = Decimal::ZERO;
+    //             for trades in ticker_info.clone() {
+    //                 let trades_price = Decimal::from_str(&trades.price).unwrap();
+    //                 max_price = if trades_price > max_price { trades_price } else { max_price };
+    //             };
+    //             let start_index = recall_ticker_info.len().saturating_sub(ticker_info.len()).saturating_sub(config_clone.recall_max_count.to_usize().unwrap());
+    //             ExportExchangeTickerInfo {
+    //                 name: exchange.to_string(),
+    //                 ticker_info,
+    //                 recall_ticker_info: recall_ticker_info[start_index..].to_vec(),
+    //                 max_price: max_price.to_string(),
+    //             }
+    //         }
+    //         "GATE" => {
+    //             let recall_ticker_info = handle_ticker::get_gate_ticker_info(&symbol, &recall_start_at.to_string(), &end_at.to_string()).await;
+    //             let ticker_info: Vec<Trades> = recall_ticker_info.iter().filter(|item| item.create_time.parse::<i64>().unwrap() >= start_at * 1000).cloned().collect();
+    //             let mut max_price = Decimal::ZERO;
+    //             for trades in ticker_info.clone() {
+    //                 let trades_price = Decimal::from_str(&trades.price).unwrap();
+    //                 max_price = if trades_price > max_price { trades_price } else { max_price };
+    //             };
+    //             let start_index = recall_ticker_info.len().saturating_sub(ticker_info.len()).saturating_sub(config_clone.recall_max_count.to_usize().unwrap());
+    //             ExportExchangeTickerInfo {
+    //                 name: exchange.to_string(),
+    //                 ticker_info,
+    //                 recall_ticker_info: recall_ticker_info[start_index..].to_vec(),
+    //                 max_price: max_price.to_string(),
+    //             }
+    //         }
+    //         "OKX" => {
+    //             let recall_ticker_info = handle_ticker::get_okx_ticker_info(&symbol, &recall_start_at.to_string(), &end_at.to_string()).await;
+    //             let ticker_info: Vec<Trades> = recall_ticker_info.iter().filter(|item| item.create_time.parse::<i64>().unwrap() >= start_at * 1000).cloned().collect();
+    //             let mut max_price = Decimal::ZERO;
+    //             for trades in ticker_info.clone() {
+    //                 let trades_price = Decimal::from_str(&trades.price).unwrap();
+    //                 max_price = if trades_price > max_price { trades_price } else { max_price };
+    //             };
+    //             let start_index = recall_ticker_info.len().saturating_sub(ticker_info.len()).saturating_sub(config_clone.recall_max_count.to_usize().unwrap());
+    //             ExportExchangeTickerInfo {
+    //                 name: exchange.to_string(),
+    //                 ticker_info,
+    //                 recall_ticker_info: recall_ticker_info[start_index..].to_vec(),
+    //                 max_price: max_price.to_string(),
+    //             }
+    //         }
+    //         _ => {
+    //             error!("交易所输入错误!");
+    //             panic!("交易所输入错误!")
+    //         }
+    //     };
+    //     exchange_list.push(exchange_result);
+    // }
+    // let mut robot_info = vec![];
+    // if config_clone.robot_name != "" {
+    //     robot_info = handle_ticker::get_robot_info(&symbol, &config_clone.robot_name, &start_at.to_string(), &end_at.to_string()).await;
+    // }
+    // export::html::export_html(exchange_list, &start_at.to_string(), &end_at.to_string(), config.clone(), robot_info);
 
-    let recall_start_at = start_at - config.recall_time / 1000;
 
-    let mut exchange_list = vec![];
-    for exchange in config.exchanges.clone() {
-        let exchange_up = exchange.to_uppercase();
-        let exchange_result = match exchange_up.as_str() {
-            "BINANCE" => {
-                let recall_ticker_info = handle_ticker::get_binance_ticker_info(&symbol, &recall_start_at.to_string(), &end_at.to_string()).await;
-                let ticker_info: Vec<Trades> = recall_ticker_info.iter().filter(|item| item.create_time.parse::<i64>().unwrap() >= start_at * 1000).cloned().collect();
-                let mut max_price = Decimal::ZERO;
-                for trades in ticker_info.clone() {
-                    let trades_price = Decimal::from_str(&trades.price).unwrap();
-                    max_price = if trades_price > max_price { trades_price } else { max_price };
-                };
-                let start_index = recall_ticker_info.len().saturating_sub(ticker_info.len()).saturating_sub(config_clone.recall_max_count.to_usize().unwrap());
-                ExportExchangeTickerInfo {
-                    name: exchange.to_string(),
-                    ticker_info,
-                    recall_ticker_info: recall_ticker_info[start_index..].to_vec(),
-                    max_price: max_price.to_string(),
-                }
-            }
-            "GATE" => {
-                let recall_ticker_info = handle_ticker::get_gate_ticker_info(&symbol, &recall_start_at.to_string(), &end_at.to_string()).await;
-                let ticker_info: Vec<Trades> = recall_ticker_info.iter().filter(|item| item.create_time.parse::<i64>().unwrap() >= start_at * 1000).cloned().collect();
-                let mut max_price = Decimal::ZERO;
-                for trades in ticker_info.clone() {
-                    let trades_price = Decimal::from_str(&trades.price).unwrap();
-                    max_price = if trades_price > max_price { trades_price } else { max_price };
-                };
-                let start_index = recall_ticker_info.len().saturating_sub(ticker_info.len()).saturating_sub(config_clone.recall_max_count.to_usize().unwrap());
-                ExportExchangeTickerInfo {
-                    name: exchange.to_string(),
-                    ticker_info,
-                    recall_ticker_info: recall_ticker_info[start_index..].to_vec(),
-                    max_price: max_price.to_string(),
-                }
-            }
-            "OKX" => {
-                let recall_ticker_info = handle_ticker::get_okx_ticker_info(&symbol, &recall_start_at.to_string(), &end_at.to_string()).await;
-                let ticker_info: Vec<Trades> = recall_ticker_info.iter().filter(|item| item.create_time.parse::<i64>().unwrap() >= start_at * 1000).cloned().collect();
-                let mut max_price = Decimal::ZERO;
-                for trades in ticker_info.clone() {
-                    let trades_price = Decimal::from_str(&trades.price).unwrap();
-                    max_price = if trades_price > max_price { trades_price } else { max_price };
-                };
-                let start_index = recall_ticker_info.len().saturating_sub(ticker_info.len()).saturating_sub(config_clone.recall_max_count.to_usize().unwrap());
-                ExportExchangeTickerInfo {
-                    name: exchange.to_string(),
-                    ticker_info,
-                    recall_ticker_info: recall_ticker_info[start_index..].to_vec(),
-                    max_price: max_price.to_string(),
-                }
-            }
-            _ => {
-                error!("交易所输入错误!");
-                panic!("交易所输入错误!")
-            }
-        };
-        exchange_list.push(exchange_result);
-    }
-    let mut robot_info = vec![];
-    if config_clone.robot_name != "" {
-        robot_info = handle_ticker::get_robot_info(&symbol, &config_clone.robot_name, &start_at.to_string(), &end_at.to_string()).await;
-    }
-    export::html::export_html(exchange_list, &start_at.to_string(), &end_at.to_string(), config.clone(), robot_info);
+    //账号变更统计
+    creation_account_book_html(1702008000).await;
 }

+ 1 - 2
src/okx_swap/okx_swap_rest.rs

@@ -23,5 +23,4 @@ pub async fn get_symbol_details_all() -> Response {
         "instType":"SWAP"
     });
     get(url, params.to_string(), None).await
-}
-
+}

+ 27 - 0
src/utils/utils.rs

@@ -60,4 +60,31 @@ pub fn get_config_info(file_path: &str) -> ConfigInfo {
         }
     };
     result
+}
+
+//map 参数转换为 get请求?之后的参数
+//map数据转 get请求参数
+pub fn parse_params_to_str(parameters: String) -> String {
+    let mut params_str = String::from("");
+    let parsed_json: serde_json::Value = serde_json::from_str(&parameters).unwrap();
+
+    if let Some(json_obj) = parsed_json.as_object() {
+        for (key, value) in json_obj.iter() {
+            let formatted_value = match value {
+                serde_json::Value::String(s) => s.clone(),
+                _ => value.to_string()
+            };
+            // trace!("Key: {}", key);
+            // trace!("Value: {}", formatted_value);
+            // let formatted_value = match value {
+            //     Value::String(s) => s.clone(),
+            //     _ => value.to_string()
+            // };
+            let str = format!("{}={}", key, formatted_value);
+            let format_str = format!("{}{}{}", params_str, (if params_str.len() > 0 { "&" } else { "" }), str);
+            params_str = format_str;
+        }
+    }
+    // trace!("---json-转字符串拼接:{}",params_str);
+    params_str.to_string()
 }