From f02b01f524ee3f48f460890881d8411dc5e7413a Mon Sep 17 00:00:00 2001
From: zyy <zyy@email.com>
Date: Tue, 04 Nov 2025 18:35:05 +0800
Subject: [PATCH] 1

---
 trading-order-huobi/src/main/java/com/yami/trading/huobi/jsws/WebsocketRunClient.java                |  120 +++++++++
 trading-order-bean/src/main/java/com/yami/trading/bean/model/StockRealTimeBean.java                  |   26 ++
 trading-order-bean/src/main/java/com/yami/trading/bean/data/domain/Realtime.java                     |    4 
 trading-order-huobi/src/main/java/com/yami/trading/huobi/data/job/StockGetDataJob.java               |    4 
 trading-order-huobi/src/main/java/com/yami/trading/huobi/websocket/constant/enums/EStockType.java    |  125 +++++++++
 trading-order-admin/src/main/java/com/yami/trading/admin/task/StockTask.java                         |  177 +++++++++++++
 trading-order-huobi/src/main/java/com/yami/trading/huobi/jsws/WebSocketClientBeanConfig.java         |   61 ++++
 trading-order-admin/src/main/java/com/yami/trading/admin/util/us/ReponseBase.java                    |   47 +++
 trading-order-common/src/main/java/com/yami/trading/common/serializer/redis/KryoRedisSerializer.java |    1 
 trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiItemController.java             |    2 
 trading-order-admin/src/main/resources/application-dev.yml                                           |    4 
 trading-order-admin/src/main/java/com/yami/trading/admin/util/us/HttpClientRequest.java              |  186 ++++++++++++++
 12 files changed, 752 insertions(+), 5 deletions(-)

diff --git a/trading-order-admin/src/main/java/com/yami/trading/admin/task/StockTask.java b/trading-order-admin/src/main/java/com/yami/trading/admin/task/StockTask.java
new file mode 100644
index 0000000..c975b65
--- /dev/null
+++ b/trading-order-admin/src/main/java/com/yami/trading/admin/task/StockTask.java
@@ -0,0 +1,177 @@
+package com.yami.trading.admin.task;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.google.gson.Gson;
+import com.yami.trading.admin.util.us.HttpClientRequest;
+import com.yami.trading.admin.util.us.ReponseBase;
+import com.yami.trading.bean.data.domain.Realtime;
+import com.yami.trading.bean.item.domain.Item;
+import com.yami.trading.bean.model.StockRealTimeBean;
+import com.yami.trading.common.constants.RedisKeys;
+import com.yami.trading.common.util.RedisUtil;
+import com.yami.trading.huobi.data.DataCache;
+import com.yami.trading.huobi.websocket.constant.enums.EStockType;
+import com.yami.trading.service.item.ItemService;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.stereotype.Component;
+
+import java.math.BigDecimal;
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.stream.Collectors;
+
+
+@Component
+public class StockTask {
+    private static final Logger log = LoggerFactory.getLogger(StockTask.class);
+
+    private final AtomicBoolean syncINStockData = new AtomicBoolean(false);
+
+    private final Lock syncINStockDataLock = new ReentrantLock();
+
+    @Autowired
+    private ThreadPoolTaskExecutor taskExecutor;
+
+    @Autowired
+    ItemService itemService;
+
+    /**
+     * 同步系统所需要的股票
+     */
+    @Scheduled(cron = "0 0/5 * * * ?")
+    public void syncINStockData() {
+
+        if (syncINStockData.get()) { // 判断任务是否在处理中
+            return;
+        }
+        if (syncINStockDataLock.tryLock()) {
+            try {
+                syncINStockData.set(true);
+
+                // 1. 定义需要处理的所有股票类型(集中管理,新增类型只需添加到列表)
+                List<EStockType> stockTypes = Arrays.asList(
+                        EStockType.US
+                );
+
+                // 2. 批量创建所有异步任务
+                List<CompletableFuture<Void>> futures = new ArrayList<>();
+                for (EStockType type : stockTypes) {
+                    // 添加loadAllStock任务
+                    futures.add(CompletableFuture.runAsync(() -> loadAllStock(type), taskExecutor));
+                }
+
+                // 3. 等待所有任务完成(将List转换为数组传入allOf)
+                CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
+            } catch (Exception e) {
+                log.error("同步股票数据出错", e);
+            } finally {
+                syncINStockDataLock.unlock();
+                syncINStockData.set(false);
+            }
+        }
+    }
+
+    /**
+     * 加载所有股票数据
+     */
+    public void loadAllStock(EStockType eStockType) {
+        log.info("同步股票 数据 {}", eStockType.getCode());
+        List<StockRealTimeBean> list = new ArrayList<>();
+        int totleStock = 1;
+        int page = 0;
+        try {
+            while (totleStock > list.size()) {
+                try {
+                    String result = HttpClientRequest.doGet(eStockType.stockUrl + "list?country_id=" + eStockType.getContryId() + "&size=100000&page=" + page + "&key=" + eStockType.stockKey);
+                    ReponseBase reponseBase = new Gson().fromJson(result, ReponseBase.class);
+                    list.addAll(reponseBase.getData());
+                    page++;
+                    totleStock = reponseBase.getTotal();
+                } catch (Exception e) {
+                    e.printStackTrace();
+                    break;
+                }
+            }
+            if (list.isEmpty()) {
+                return;
+            }
+            List<String> stockCodeList = list.stream().map(StockRealTimeBean::getSymbol).collect(Collectors.toList());
+            List<Item> stockList = itemService.list(new QueryWrapper<Item>().in("symbol", stockCodeList));
+            List<Item> updateStockList = new ArrayList<>();
+            for (StockRealTimeBean o : list) {
+                //System.out.println(o);
+                Item item = stockList.stream()
+                        .filter(x -> x.getSymbol().equals(o.getSymbol()))
+                        .findFirst()
+                        .orElse(null);
+                if (item == null) {
+                    item = new Item();
+                }
+                String name = StringUtils.trim(o.getName());
+                item.setEnName(name);
+                item.setName(name);
+                item.setSymbolFullName(name);
+                item.setSymbol(o.getSymbol());
+                item.setSymbolData(o.getSymbol());
+                item.setPips(BigDecimal.valueOf(0.01).doubleValue());
+                item.setPipsAmount(BigDecimal.valueOf(0.02).doubleValue());
+                item.setAdjustmentValue(BigDecimal.ZERO.doubleValue());
+                item.setUnitAmount(BigDecimal.valueOf(1000).doubleValue());
+                item.setUnitFee(BigDecimal.valueOf(30).doubleValue());
+                item.setMarket("FOREVER");
+                item.setDecimals(2);
+                item.setMultiple(BigDecimal.ZERO.doubleValue());
+                item.setBorrowingRate(BigDecimal.ZERO.doubleValue());
+                item.setDelFlag(0);
+                item.setType(Item.US_STOCKS);
+                item.setCategory(Item.US_STOCKS);
+                item.setSorted("100");
+                item.setOpenCloseType(Item.US_STOCKS);
+                item.setFake("0");
+                item.setShowStatus("1");
+                item.setTradeStatus("1");
+                item.setQuoteCurrency("USDT");
+
+                updateStockList.add(item);
+
+
+                Realtime realtime = new Realtime();
+                realtime.setUuid(o.getId());
+                realtime.setSymbol(o.getSymbol());
+                realtime.setName(o.getName());
+                realtime.setClose(new BigDecimal(o.getLast().trim()).doubleValue());
+                realtime.setLow(new BigDecimal(o.getLow().trim()).doubleValue());
+                realtime.setHigh(new BigDecimal(o.getHigh().trim()).doubleValue());
+                realtime.setOpen(new BigDecimal(o.getOpen().trim()).doubleValue());
+                realtime.setPrevClose(new BigDecimal(o.getPrevClose().trim()).doubleValue());
+                realtime.setTs(Long.valueOf(o.getTime() + "000"));
+                realtime.setVolume(new BigDecimal(o.getVolume().trim()).doubleValue());
+                realtime.setNetChange(new BigDecimal(o.getChg().trim()).doubleValue());
+                realtime.setChangeRatio(new BigDecimal(o.getChgPct()).doubleValue());
+                realtime.setType(o.getType());
+                realtime.setBid(new BigDecimal(o.getBid()).doubleValue());
+                realtime.setAsk(new BigDecimal(o.getAsk()).doubleValue());
+
+                DataCache.putRealtime(realtime.getSymbol(), realtime);
+            }
+            itemService.saveOrUpdateBatch(updateStockList);
+            log.info("同步股票 数据 成功 {}  总共同步数据 {}", eStockType.getCode(), list.size());
+        } catch (
+                Exception e) {
+            log.error("同步出错", e);
+        }
+    }
+
+    public static void main(String[] args) {
+        StockTask task = new StockTask();
+        task.loadAllStock(EStockType.US);
+    }
+}
diff --git a/trading-order-admin/src/main/java/com/yami/trading/admin/util/us/HttpClientRequest.java b/trading-order-admin/src/main/java/com/yami/trading/admin/util/us/HttpClientRequest.java
new file mode 100644
index 0000000..3b177ab
--- /dev/null
+++ b/trading-order-admin/src/main/java/com/yami/trading/admin/util/us/HttpClientRequest.java
@@ -0,0 +1,186 @@
+package com.yami.trading.admin.util.us;
+
+import com.google.gson.Gson;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.HttpEntity;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.*;
+
+@Slf4j
+public class HttpClientRequest {
+
+    public static String doGet(String url) {
+        CloseableHttpClient httpClient = null;
+        CloseableHttpResponse response = null;
+        String result = "";
+
+        try {
+            httpClient = HttpClients.createDefault();
+
+            HttpGet httpGet = new HttpGet(url);
+
+            httpGet.setHeader("Authorization", "Bearer da3efcbf-0845-4fe3-8aba-ee040be542c0");
+            httpGet.setHeader("Referer","https://quotes.sina.cn/hs/company/quotes/view/sz399001?vt=4&cid=76524&node_id=76524&autocallup=no&isfromsina=yes");
+            //cookie
+            httpGet.setHeader("Cookie", "xq_a_token=d269ad4aee7ece063038900846f9541a7d0ead07");
+
+
+            RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(35000).setConnectionRequestTimeout(35000).setSocketTimeout(60000).build();
+
+            httpGet.setConfig(requestConfig);
+
+            response = httpClient.execute(httpGet);
+
+            HttpEntity entity = response.getEntity();
+
+            result = EntityUtils.toString(entity);
+        } catch (ClientProtocolException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        finally {
+
+            if (null != response) {
+
+                try {
+                    response.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            if (null != httpClient) {
+                try {
+                    httpClient.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return result;
+    }
+
+    public static String doPost(String url, Map<String, Object> paramMap) {
+        CloseableHttpClient httpClient = null;
+        CloseableHttpResponse httpResponse = null;
+        String result = "";
+
+        httpClient = HttpClients.createDefault();
+
+        HttpPost httpPost = new HttpPost(url);
+
+
+        RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(35000).setConnectionRequestTimeout(35000).setSocketTimeout(60000).build();
+
+        httpPost.setConfig(requestConfig);
+
+        httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded");
+
+        if (null != paramMap && paramMap.size() > 0) {
+            List<NameValuePair> nvps = new ArrayList<NameValuePair>();
+
+            Set<Map.Entry<String, Object>> entrySet = paramMap.entrySet();
+
+            Iterator<Map.Entry<String, Object>> iterator = entrySet.iterator();
+            while (iterator.hasNext()) {
+                Map.Entry<String, Object> mapEntry = (Map.Entry) iterator.next();
+                nvps.add(new BasicNameValuePair((String) mapEntry.getKey(), mapEntry.getValue().toString()));
+            }
+
+
+            try {
+                httpPost.setEntity(new UrlEncodedFormEntity(nvps, "UTF-8"));
+            } catch (UnsupportedEncodingException e) {
+                e.printStackTrace();
+            }
+        }
+
+        try {
+            httpResponse = httpClient.execute(httpPost);
+
+            HttpEntity entity = httpResponse.getEntity();
+            result = EntityUtils.toString(entity);
+        } catch (ClientProtocolException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+
+            if (null != httpResponse) {
+                try {
+                    httpResponse.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            if (null != httpClient) {
+                try {
+                    httpClient.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return result;
+    }
+
+    public static String doPostJson(String url, Map<String, String> paramMap) {
+        CloseableHttpClient httpClient = null;
+        CloseableHttpResponse httpResponse = null;
+        String result = "";
+
+        httpClient = HttpClients.createDefault();
+
+        HttpPost httpPost = new HttpPost(url);
+
+
+        RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(35000).setConnectionRequestTimeout(35000).setSocketTimeout(60000).build();
+        httpPost.setConfig(requestConfig);
+        httpPost.addHeader("Content-Type", "application/json");
+        StringEntity reqEntity = new StringEntity(new Gson().toJson(paramMap),"utf-8");
+        httpPost.setEntity(reqEntity);
+        try {
+            httpResponse = httpClient.execute(httpPost);
+
+            HttpEntity entity = httpResponse.getEntity();
+            result = EntityUtils.toString(entity);
+        } catch (ClientProtocolException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+
+            if (null != httpResponse) {
+                try {
+                    httpResponse.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            if (null != httpClient) {
+                try {
+                    httpClient.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return result;
+    }
+
+
+}
diff --git a/trading-order-admin/src/main/java/com/yami/trading/admin/util/us/ReponseBase.java b/trading-order-admin/src/main/java/com/yami/trading/admin/util/us/ReponseBase.java
new file mode 100644
index 0000000..8e096fc
--- /dev/null
+++ b/trading-order-admin/src/main/java/com/yami/trading/admin/util/us/ReponseBase.java
@@ -0,0 +1,47 @@
+package com.yami.trading.admin.util.us;
+
+import com.yami.trading.bean.model.StockRealTimeBean;
+
+import java.util.List;
+
+public class ReponseBase {
+    private List<StockRealTimeBean> data;
+
+    private int page;
+
+    private int pageSize;
+
+    private  int  total;
+
+    public List<StockRealTimeBean> getData() {
+        return data;
+    }
+
+    public void setData(List<StockRealTimeBean> data) {
+        this.data = data;
+    }
+
+    public int getPage() {
+        return page;
+    }
+
+    public void setPage(int page) {
+        this.page = page;
+    }
+
+    public int getPageSize() {
+        return pageSize;
+    }
+
+    public void setPageSize(int pageSize) {
+        this.pageSize = pageSize;
+    }
+
+    public int getTotal() {
+        return total;
+    }
+
+    public void setTotal(int total) {
+        this.total = total;
+    }
+}
diff --git a/trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiItemController.java b/trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiItemController.java
index c1afbf3..67eeb33 100644
--- a/trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiItemController.java
+++ b/trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiItemController.java
@@ -272,7 +272,7 @@
         // 倒序吗? TODO
         queryWrapper.orderByDesc("sorted");
         long current = itemQuery.getCurrent() == 0 ? 1 : itemQuery.getCurrent();
-        long size = itemQuery.getSize() == 0 ? 1000 : itemQuery.getSize();
+        long size = itemQuery.getSize() == 0 ? 50 : itemQuery.getSize();
         Page<Item> page = new Page<>(current, size);
         IPage<Item> result = itemService.page(page, queryWrapper);
         List<Item> records = result.getRecords();
diff --git a/trading-order-admin/src/main/resources/application-dev.yml b/trading-order-admin/src/main/resources/application-dev.yml
index 8dee311..420cd9d 100644
--- a/trading-order-admin/src/main/resources/application-dev.yml
+++ b/trading-order-admin/src/main/resources/application-dev.yml
@@ -130,9 +130,9 @@
 config:
   timezone:
     # 配置当前盘口存储数据使用的时区
-    record: GMT+8
+    record: America/New_York
     # 配置当前盘口展示数据使用的时区
-    show: GMT+8
+    show: America/New_York
 
 sign:
   encryption-key: d78585e683ed11eaa13f0242ac110003
diff --git a/trading-order-bean/src/main/java/com/yami/trading/bean/data/domain/Realtime.java b/trading-order-bean/src/main/java/com/yami/trading/bean/data/domain/Realtime.java
index 885f601..061d1fa 100644
--- a/trading-order-bean/src/main/java/com/yami/trading/bean/data/domain/Realtime.java
+++ b/trading-order-bean/src/main/java/com/yami/trading/bean/data/domain/Realtime.java
@@ -92,6 +92,10 @@
      */
     private String type;
 
+    @TableField(exist = false)
+    @ApiModelProperty("昨日收盘价")
+    private double prevClose;
+
     /**
      * 涨跌幅
      */
diff --git a/trading-order-bean/src/main/java/com/yami/trading/bean/model/StockRealTimeBean.java b/trading-order-bean/src/main/java/com/yami/trading/bean/model/StockRealTimeBean.java
new file mode 100644
index 0000000..5eb0de9
--- /dev/null
+++ b/trading-order-bean/src/main/java/com/yami/trading/bean/model/StockRealTimeBean.java
@@ -0,0 +1,26 @@
+package com.yami.trading.bean.model;
+
+import lombok.Data;
+
+@Data
+public class StockRealTimeBean {
+
+    private String Id;          // 股票Id 也是股票的pid
+    private String Symbol;      // 股票编码
+    private String Name;        // 股票名称
+    private String Last;        // 股票最新价格
+    private String Low;         // 最低
+    private String High;        // 最高
+    private String Open;        // 今开
+    private String PrevClose;   // 昨收
+    private String Time;        // 价格更新时间
+    private String Volume;      // 交易量
+    private String Chg;         // 涨幅
+    private String ChgPct;      // 涨幅率
+    private String type;        // 股票所在的交易所
+    private String Ratio;       // 市盈率
+    private String MarketCap;   // 市值
+    private String Eps;         // 每股收益
+    private String Bid;         // 买进价
+    private String Ask;         // 卖出价
+}
diff --git a/trading-order-common/src/main/java/com/yami/trading/common/serializer/redis/KryoRedisSerializer.java b/trading-order-common/src/main/java/com/yami/trading/common/serializer/redis/KryoRedisSerializer.java
index ef7efaf..2e6deb3 100644
--- a/trading-order-common/src/main/java/com/yami/trading/common/serializer/redis/KryoRedisSerializer.java
+++ b/trading-order-common/src/main/java/com/yami/trading/common/serializer/redis/KryoRedisSerializer.java
@@ -67,6 +67,7 @@
         try (Input input = new Input(bytes)) {
             return (T) kryo.readClassAndObject(input);
         } catch (Exception e) {
+            e.printStackTrace();
             log.error(e.getMessage(), e);
         }
         return null;
diff --git a/trading-order-huobi/src/main/java/com/yami/trading/huobi/data/job/StockGetDataJob.java b/trading-order-huobi/src/main/java/com/yami/trading/huobi/data/job/StockGetDataJob.java
index db51644..0e9bc54 100644
--- a/trading-order-huobi/src/main/java/com/yami/trading/huobi/data/job/StockGetDataJob.java
+++ b/trading-order-huobi/src/main/java/com/yami/trading/huobi/data/job/StockGetDataJob.java
@@ -123,7 +123,7 @@
                 }
                 if(MarketOpenChecker.isMarketOpen(Item.US_STOCKS, 30)){
                     //美股
-                    this.realtimeHandleTradingViewUsStock();
+                    //this.realtimeHandleTradingViewUsStock();
 
                     //this.realtimeHandleXueQiu(usStockRemarks);
                     //美股ETF
@@ -133,7 +133,7 @@
                 this.realtimeHandleTradingViewUsEtf();
 
                 //美股
-                this.realtimeHandleTradingViewUsStock();
+                //this.realtimeHandleTradingViewUsStock();
 
                 //if(MarketOpenChecker.isMarketOpen(Item.TW_STOCKS, 30)){
                 //    this.realtimeHandleTW(twStockRemarks);
diff --git a/trading-order-huobi/src/main/java/com/yami/trading/huobi/jsws/WebSocketClientBeanConfig.java b/trading-order-huobi/src/main/java/com/yami/trading/huobi/jsws/WebSocketClientBeanConfig.java
new file mode 100644
index 0000000..c105d73
--- /dev/null
+++ b/trading-order-huobi/src/main/java/com/yami/trading/huobi/jsws/WebSocketClientBeanConfig.java
@@ -0,0 +1,61 @@
+package com.yami.trading.huobi.jsws;
+
+
+import com.yami.trading.huobi.websocket.constant.enums.EStockType;
+import lombok.extern.slf4j.Slf4j;
+import org.java_websocket.client.WebSocketClient;
+import org.springframework.context.annotation.Bean;
+import org.springframework.stereotype.Component;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+@Component
+public class WebSocketClientBeanConfig {
+
+
+    @Bean
+    public Map<String, WebSocketClient> websocketRunClientMap() {
+
+        Map<String, WebSocketClient> retMap = new HashMap<>(1);
+        try {
+            WebsocketRunClient websocketRunClient = new WebsocketRunClient(new URI(EStockType.US.getWsUrl()), EStockType.US);
+            websocketRunClient.connect();
+            websocketRunClient.setConnectionLostTimeout(0);
+            startHeartbeatThread(websocketRunClient);
+            retMap.put(EStockType.US.getStockKey(), websocketRunClient);
+        } catch (Exception e) {
+            log.error("WebsocketRunClient 异常: {}", e.getMessage());
+        }
+        return retMap;
+    }
+
+    private void startHeartbeatThread(WebSocketClient client) {
+        new Thread(() -> {
+            while (true) {
+                try {
+                    Thread.sleep(8000);
+                    if (client.isOpen()) { // 先检查连接状态
+                        client.send("heartbeat".getBytes());
+                    } else {
+                        client.reconnect();
+                        client.setConnectionLostTimeout(0);
+                    }
+                } catch (Exception e) {
+                    log.error("心跳线程异常, 尝试重连: {}", e.getMessage());
+                    try {
+                        client.reconnect();
+                        client.setConnectionLostTimeout(0);
+                    } catch (Exception re) {
+                        log.error("重连失败: {}", re.getMessage());
+                    }
+                }
+            }
+        }).start();
+    }
+
+
+}
+
diff --git a/trading-order-huobi/src/main/java/com/yami/trading/huobi/jsws/WebsocketRunClient.java b/trading-order-huobi/src/main/java/com/yami/trading/huobi/jsws/WebsocketRunClient.java
new file mode 100644
index 0000000..93a6a2e
--- /dev/null
+++ b/trading-order-huobi/src/main/java/com/yami/trading/huobi/jsws/WebsocketRunClient.java
@@ -0,0 +1,120 @@
+package com.yami.trading.huobi.jsws;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+
+import com.yami.trading.bean.data.domain.Realtime;
+import com.yami.trading.huobi.data.DataCache;
+import com.yami.trading.huobi.websocket.constant.enums.EStockType;
+import lombok.extern.slf4j.Slf4j;
+import org.java_websocket.client.WebSocketClient;
+import org.java_websocket.handshake.ServerHandshake;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.net.URI;
+import java.util.*;
+
+@Slf4j
+public class WebsocketRunClient extends WebSocketClient {
+
+    private EStockType eStockType;
+
+    public WebsocketRunClient(URI serverUri, EStockType eStockType) {
+        // 修改为新的WebSocket服务器地址
+        super(URI.create("wss://usws.yanshiz.com/websocket-server"));
+        this.eStockType = eStockType;
+    }
+
+
+    @Override
+    public void onOpen(ServerHandshake serverHandshake) {
+        log.info("WebSocket连接已建立,连接到: wss://usws.yanshiz.com/websocket-server");
+        // 发送身份验证消息
+        send(("key:"+ eStockType.getStockKey()+":"+eStockType.getContryId()).getBytes());
+        Timer heartbeatTimer;
+        // 启动心跳定时器
+        heartbeatTimer = new Timer();
+        heartbeatTimer.schedule(new TimerTask() {
+            @Override
+            public void run() {
+                if (isOpen()) {
+                    //send(("key:"+ eStockType.getStockKey()+":"+eStockType.getContryId()).getBytes());
+                    send("heartbeat");
+                }
+            }
+        }, 0, 3000); // 每3秒发送一次心跳消息
+    }
+
+    @Override
+    public void onMessage(String s) {
+        if(!s.equals("pong") && !s.equals("身份验证成功")){
+            try {
+                Map<String, String> stringObjectMap = jsonToMap(s);
+                Realtime realtime = new Realtime();
+                realtime.setUuid(stringObjectMap.get("pid"));
+                realtime.setSymbol(stringObjectMap.get("symbol"));
+                realtime.setClose(new BigDecimal(stringObjectMap.get("last")).doubleValue());
+                realtime.setLow(new BigDecimal(stringObjectMap.get("low")).doubleValue());
+                realtime.setHigh(new BigDecimal(stringObjectMap.get("high")).doubleValue());
+                realtime.setOpen(new BigDecimal(stringObjectMap.get("open")).doubleValue());
+                realtime.setPrevClose(new BigDecimal(stringObjectMap.get("prevClose")).doubleValue());
+                realtime.setTs(Long.valueOf(stringObjectMap.get("time") + "000"));
+                realtime.setNetChange(new BigDecimal(stringObjectMap.get("pc")).doubleValue());
+                realtime.setChangeRatio(parsePercent(stringObjectMap.get("pcp")));
+                realtime.setBid(new BigDecimal(stringObjectMap.get("bid")).doubleValue());
+                realtime.setAsk(new BigDecimal(stringObjectMap.get("ask")).doubleValue());
+                DataCache.putRealtime(realtime.getSymbol(), realtime);
+            } catch (Exception e) {
+                log.error("处理WebSocket消息时发生错误: {}", e.getMessage(), e);
+            }
+        } else {
+            log.info("WebSocket心跳或认证响应: {}", s);
+        }
+    }
+
+    public static Map<String, String> jsonToMap(String json) {
+        Gson gson = new Gson();
+        Type type = new TypeToken<Map<String, String>>(){}.getType();
+        return gson.fromJson(json, type);
+    }
+
+    @Override
+    public void onClose(int i, String s, boolean b) {
+        log.info("WebSocket连接已关闭,代码: {}, 原因: {}, 远程关闭: {}", i, s, b);
+    }
+
+    @Override
+    public void onError(Exception e) {
+        log.error("WebSocket连接发生错误: {}", e.getMessage(), e);
+    }
+
+    /**
+     * 将带正负号的百分比字符串转换为double(返回小数形式,如+37.69% → 0.3769)
+     * @param percentStr 百分比字符串(如"+37.69%"、"-5.2%")
+     * @return 转换后的double值
+     * @throws IllegalArgumentException 若输入格式无效
+     */
+    public static double parsePercent(String percentStr) {
+        if (percentStr == null || percentStr.trim().isEmpty()) {
+            throw new IllegalArgumentException("输入字符串不能为空");
+        }
+
+        // 1. 去除百分号并清理空格
+        String numStr = percentStr.replace("%", "").trim();
+
+        try {
+            // 2. 转换为double(支持正负号)
+            double value = Double.parseDouble(numStr);
+            // 3. 转换为小数(百分比 → 小数)
+            double decimal = value / 100.0;
+            // 4. 四舍五入保留4位小数(使用BigDecimal确保精度)
+            return new BigDecimal(decimal)
+                    .setScale(4, RoundingMode.HALF_UP) // HALF_UP:四舍五入模式
+                    .doubleValue();
+        } catch (NumberFormatException e) {
+            throw new IllegalArgumentException("无效的百分比格式:" + percentStr, e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/trading-order-huobi/src/main/java/com/yami/trading/huobi/websocket/constant/enums/EStockType.java b/trading-order-huobi/src/main/java/com/yami/trading/huobi/websocket/constant/enums/EStockType.java
new file mode 100644
index 0000000..9f59499
--- /dev/null
+++ b/trading-order-huobi/src/main/java/com/yami/trading/huobi/websocket/constant/enums/EStockType.java
@@ -0,0 +1,125 @@
+package com.yami.trading.huobi.websocket.constant.enums;
+
+public enum EStockType {
+
+
+    US("US","美国股票","5","http://api-us-v2.js-stock.top/","ws://api-us-v2-ws.js-stock.top","gAFCeL8vaTo7slHJFogb","USD","$"),
+    ;
+
+    private String code;
+    private String typeDesc;
+    public String contryId;
+
+    public String stockUrl;
+    public String wsUrl;
+    public  String stockKey;
+
+    private String symbol;
+
+    private String symbol1;
+
+    public static EStockType getDefault() {
+        return US; // 指定默认
+    }
+
+    EStockType(String code, String typeDesc, String contryId, String stockUrl, String wsUrl,String stockKey,String  symbol,String symbol1) {
+        this.code = code;
+        this.typeDesc = typeDesc;
+        this.contryId = contryId;
+        this.stockUrl = stockUrl;
+        this.wsUrl = wsUrl;
+        this.stockKey = stockKey;
+        this.symbol = symbol;
+        this.symbol1 = symbol1;
+    }
+
+    public static EStockType getEStockTypeByCode(String code){
+        if(EStockType.US.getCode().equals(code)){
+            return US;
+        /*}else if(EStockType.HK.getCode().equals(code)){
+            return HK;
+        }else if(EStockType.IN.getCode().equals(code)){
+            return IN;
+        }else if(EStockType.TW.getCode().equals(code)){
+            return TW;*/
+        }else{
+            return US;
+        }
+    }
+
+    public static boolean isExistByCode(String code){
+        /*if(EStockType.US.getCode().equals(code)){
+            return true;
+        }else if(EStockType.HK.getCode().equals(code)){
+            return true;
+        }else if(EStockType.IN.getCode().equals(code)){
+            return true;
+        }else if(EStockType.TW.getCode().equals(code)){
+            return true;
+        }else{
+            return false;
+        }*/
+        return false;
+    }
+
+    //根据货币获取类型
+    public static EStockType getEStockTypeBySymbol(String symbol){
+        /*if(EStockType.US.getSymbol().equals(symbol)){
+            return US;
+        }else if(EStockType.HK.getSymbol().equals(symbol)){
+            return HK;
+        }else if(EStockType.IN.getSymbol().equals(symbol)){
+            return IN;
+        }else if(EStockType.TW.getSymbol().equals(symbol)){
+            return TW;
+        }else{
+            return null;
+        }*/
+        return null;
+    }
+
+    public String getContryId() {
+        return contryId;
+    }
+
+    public String getStockUrl() {
+        return stockUrl;
+    }
+
+    public String getStockKey() {
+        return stockKey;
+    }
+
+    public String getWsUrl() {
+        return wsUrl;
+    }
+
+    public void setWsUrl(String wsUrl) {
+        this.wsUrl = wsUrl;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public String getSymbol() {
+        return symbol;
+    }
+
+    public void setSymbol(String symbol) {
+        this.symbol = symbol;
+    }
+
+    public String getSymbol1() {
+        return symbol1;
+    }
+
+    public void setSymbol1(String symbol1) {
+        this.symbol1 = symbol1;
+    }
+
+    public String getTypeDesc() {
+        return typeDesc;
+    }
+
+}

--
Gitblit v1.9.3