From 635199d881d45a8c92abea57e89b69634ab4b366 Mon Sep 17 00:00:00 2001
From: Administrator <15274802129@163.com>
Date: Mon, 22 Dec 2025 16:19:03 +0800
Subject: [PATCH] feat(newPrice): 新增OKX交易所相关功能模块
---
src/main/java/com/xcong/excoin/modules/newPrice/KlineVo.java | 23
src/main/java/com/xcong/excoin/modules/newPrice/ExchangeLoginService.java | 36 +
src/main/java/com/xcong/excoin/modules/newPrice/utils/JSONParser.java | 40 +
src/main/java/com/xcong/excoin/modules/newPrice/ExchangeInfoEnum.java | 34 +
src/main/java/com/xcong/excoin/modules/newPrice/utils/OkHttpUtils.java | 330 ++++++++++++
src/main/java/com/xcong/excoin/modules/newPrice/utils/ParameterChecker.java | 36 +
src/main/java/com/xcong/excoin/utils/CoinTypeConvert.java | 38 +
src/main/java/com/xcong/excoin/modules/newPrice/utils/DateUtils.java | 80 +++
src/main/java/com/xcong/excoin/modules/newPrice/enums/HttpMethod.java | 9
src/main/java/com/xcong/excoin/modules/newPrice/utils/SignUtils.java | 55 ++
src/main/java/com/xcong/excoin/modules/newPrice/utils/RequestBuilder.java | 150 +++++
src/main/java/com/xcong/excoin/modules/newPrice/RequestHandler.java | 125 ++++
src/main/java/com/xcong/excoin/modules/newPrice/ResponseHandler.java | 66 ++
src/main/java/com/xcong/excoin/modules/symbols/controller/SymbolsController.java | 10
src/main/java/com/xcong/excoin/modules/symbols/service/SymbolsService.java | 2
src/main/java/com/xcong/excoin/modules/newPrice/enums/RequestType.java | 7
src/main/java/com/xcong/excoin/modules/newPrice/impl/ExchangeLoginEventServiceImpl.java | 50 +
src/main/java/com/xcong/excoin/modules/newPrice/ExchangeLoginEventService.java | 27 +
src/main/java/com/xcong/excoin/modules/symbols/service/impl/SymbolsServiceImpl.java | 30 +
src/main/java/com/xcong/excoin/common/exception/FebsException.java | 15
src/main/java/com/xcong/excoin/modules/newPrice/utils/OKXContants.java | 185 +++++++
src/main/java/com/xcong/excoin/modules/newPrice/OKXAccount.java | 21
src/main/java/com/xcong/excoin/modules/newPrice/utils/UrlBuilder.java | 128 ++++
src/main/java/com/xcong/excoin/modules/newPrice/enums/DefaultUrls.java | 38 +
24 files changed, 1,535 insertions(+), 0 deletions(-)
diff --git a/src/main/java/com/xcong/excoin/common/exception/FebsException.java b/src/main/java/com/xcong/excoin/common/exception/FebsException.java
new file mode 100644
index 0000000..befbffc
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/common/exception/FebsException.java
@@ -0,0 +1,15 @@
+package com.xcong.excoin.common.exception;
+
+/**
+ * FEBS系统内部异常
+ *
+ * @author MrBird
+ */
+public class FebsException extends RuntimeException {
+
+ private static final long serialVersionUID = -994962710559017255L;
+
+ public FebsException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/com/xcong/excoin/modules/newPrice/ExchangeInfoEnum.java b/src/main/java/com/xcong/excoin/modules/newPrice/ExchangeInfoEnum.java
new file mode 100644
index 0000000..248208a
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/newPrice/ExchangeInfoEnum.java
@@ -0,0 +1,34 @@
+package com.xcong.excoin.modules.newPrice;
+
+import lombok.Getter;
+
+@Getter
+public enum ExchangeInfoEnum {
+
+ /**
+ * 模拟盘
+ * String apiKey = "16c882b0-2853-46c5-8af0-5269e3218d72";
+ * String passphrase = "Zh12345@@";
+ * String secretKey = "A13F7BC5721DB6D1F492B0FC333D4D9C";
+ */
+ OKX_UAT("0769b50c-2c36-4310-8bd9-cad6bc6c9d8f",
+ "7AF4A574BC44907CE76BBFF91F53852D",
+ "Aa123456@",
+ false);
+
+ private final String apiKey;
+
+ private final String secretKey;
+
+ private final String passphrase;
+
+// @Setting(label = "账户类型", order = 40, type = FieldType.SELECT, options = {"实盘账户", "模拟账户"}, optionsVal = {"true", "false"})
+ private final boolean accountType;
+
+ ExchangeInfoEnum(String apiKey, String secretKey, String passphrase, boolean accountType) {
+ this.apiKey = apiKey;
+ this.secretKey = secretKey;
+ this.passphrase = passphrase;
+ this.accountType = accountType;
+ }
+}
diff --git a/src/main/java/com/xcong/excoin/modules/newPrice/ExchangeLoginEventService.java b/src/main/java/com/xcong/excoin/modules/newPrice/ExchangeLoginEventService.java
new file mode 100644
index 0000000..69f2932
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/newPrice/ExchangeLoginEventService.java
@@ -0,0 +1,27 @@
+package com.xcong.excoin.modules.newPrice;
+
+import java.util.LinkedHashMap;
+
+public interface ExchangeLoginEventService {
+ /**
+ * 获取交易产品基础信息
+ * 获取所有可交易产品的信息列表。
+ * <br><br>
+ * GET /api/v5/public/instruments
+ * <br>
+ *
+ * @param parameters LinkedHashedMap of String,Object pair
+ * where String is the name of the parameter and Object is the value of the parameter
+ * <br><br>
+ * instType -- String 是 产品类型 SPOT:币币 MARGIN:币币杠杆 SWAP:永续合约 FUTURES:交割合约 OPTION:期权 <br>
+ * uly -- String 可选 标的指数,仅适用于交割/永续/期权,期权必填 <br>
+ * instFamily -- String 否 交易品种,仅适用于交割/永续/期权 <br>
+ * instId -- String 否 产品ID <br>
+ * @return String
+ * @see <a href="https://www.okx.com/docs-v5/zh/#rest-api-public-data-get-instruments">
+ * https://www.okx.com/docs-v5/zh/#rest-api-public-data-get-instruments</a>
+ */
+ String exchangeInfo(LinkedHashMap<String, Object> parameters);
+
+ String lineHistory(LinkedHashMap<String, Object> parameters);
+}
diff --git a/src/main/java/com/xcong/excoin/modules/newPrice/ExchangeLoginService.java b/src/main/java/com/xcong/excoin/modules/newPrice/ExchangeLoginService.java
new file mode 100644
index 0000000..d8f0844
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/newPrice/ExchangeLoginService.java
@@ -0,0 +1,36 @@
+package com.xcong.excoin.modules.newPrice;
+
+
+import com.xcong.excoin.common.exception.FebsException;
+import com.xcong.excoin.modules.newPrice.impl.ExchangeLoginEventServiceImpl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ExchangeLoginService {
+ private final static Map<String, ExchangeLoginEventService> eventMap = new HashMap<>();
+
+ static {
+ for (ExchangeInfoEnum infoEnum : ExchangeInfoEnum.values()) {
+ eventMap.put(infoEnum.name(), new ExchangeLoginEventServiceImpl(
+ infoEnum.getApiKey(),
+ infoEnum.getSecretKey(),
+ infoEnum.getPassphrase(),
+ infoEnum.isAccountType()));
+ }
+ }
+
+ private ExchangeLoginService() {
+ }
+
+ public final static ExchangeLoginService INSTANCE = new ExchangeLoginService();
+
+ public static ExchangeLoginEventService getInstance(String exchangeType) {
+ ExchangeLoginEventService exchange = eventMap.get(exchangeType);
+ if (exchange == null) {
+ throw new FebsException("参数错误");
+ }
+
+ return exchange;
+ }
+}
diff --git a/src/main/java/com/xcong/excoin/modules/newPrice/KlineVo.java b/src/main/java/com/xcong/excoin/modules/newPrice/KlineVo.java
new file mode 100644
index 0000000..3665b0d
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/newPrice/KlineVo.java
@@ -0,0 +1,23 @@
+package com.xcong.excoin.modules.newPrice;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+
+@Data
+@ApiModel(value = "KlineVo", description = "请求K线类")
+public class KlineVo implements Serializable {
+
+
+ @NotNull
+ @ApiModelProperty(value = "币种", example = "BTC/USDT")
+ private String instId;
+
+ @NotNull
+ @ApiModelProperty(value = "类型 1-币币2-合约", example = "1")
+ private Integer type;
+
+}
diff --git a/src/main/java/com/xcong/excoin/modules/newPrice/OKXAccount.java b/src/main/java/com/xcong/excoin/modules/newPrice/OKXAccount.java
new file mode 100644
index 0000000..6a08aef
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/newPrice/OKXAccount.java
@@ -0,0 +1,21 @@
+package com.xcong.excoin.modules.newPrice;
+
+import lombok.Data;
+
+@Data
+public class OKXAccount {
+
+ String passPhrase;
+ boolean isSimluate;
+ public String baseUrl;
+ public RequestHandler requestHandler;
+ public boolean showLimitUsage;
+
+ public OKXAccount(){}
+
+ public OKXAccount(String baseUrl, String apiKey, String secretKey, String passPhrase,boolean isSimluate) {
+ this.baseUrl = baseUrl;
+ this.requestHandler = new RequestHandler(apiKey, secretKey,passPhrase);
+ this.isSimluate = isSimluate;
+ }
+}
diff --git a/src/main/java/com/xcong/excoin/modules/newPrice/RequestHandler.java b/src/main/java/com/xcong/excoin/modules/newPrice/RequestHandler.java
new file mode 100644
index 0000000..72d5dd0
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/newPrice/RequestHandler.java
@@ -0,0 +1,125 @@
+package com.xcong.excoin.modules.newPrice;
+
+import com.alibaba.fastjson.JSON;
+import com.xcong.excoin.common.exception.FebsException;
+import com.xcong.excoin.modules.newPrice.enums.HttpMethod;
+import com.xcong.excoin.modules.newPrice.enums.RequestType;
+import com.xcong.excoin.modules.newPrice.utils.DateUtils;
+import com.xcong.excoin.modules.newPrice.utils.RequestBuilder;
+import com.xcong.excoin.modules.newPrice.utils.SignUtils;
+import com.xcong.excoin.modules.newPrice.utils.UrlBuilder;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.Request;
+
+import java.util.Date;
+import java.util.LinkedHashMap;
+
+@Slf4j
+public class RequestHandler {
+ private final String apiKey;
+ private final String secretKey;
+ private final String passphrase;
+
+ public RequestHandler(String apiKey) {
+ this.apiKey = apiKey;
+ this.secretKey = null;
+ this.passphrase = null;
+ }
+
+ public RequestHandler(String apiKey, String secretKey, String passphrase) {
+ this.apiKey = apiKey;
+ this.secretKey = secretKey;
+ this.passphrase = passphrase;
+ }
+
+ public static void main(String[] args) {
+ LinkedHashMap<String, Object> balanceParameters = new LinkedHashMap<>();
+ String queryString = UrlBuilder.joinQueryParameters(new StringBuilder("/api/v5/account/balance"), balanceParameters).toString();
+ String balanceParameters1 = UrlBuilder.buildFullUrl("/api/v5/account/balance","" , balanceParameters, null);
+ System.out.println(queryString);
+ System.out.println(balanceParameters1);
+ }
+
+ /**
+ * Build request based on request type and send the requests to server.
+ *
+ * @param baseUrl
+ * @param urlPath
+ * @param parameters
+ * @param httpMethod
+ * @param requestType
+ * @return String - response from server
+ */
+ private String sendApiRequest(String baseUrl, String urlPath, LinkedHashMap<String, Object> parameters,
+ HttpMethod httpMethod, RequestType requestType, boolean isSimluate) {
+ String fullUrl = UrlBuilder.buildFullUrl(baseUrl, urlPath, parameters, null);
+ log.debug("{} {}", httpMethod, fullUrl);
+ //System.out.println("sendApiRequest:fullUrl"+fullUrl);
+ Request request;
+ switch (requestType) {
+ case PUBLIC:
+ request = RequestBuilder.buildPublicRequest(fullUrl, httpMethod, isSimluate).build();
+ break;
+ case WITH_API_KEY:
+ case SIGNED:
+ // 获取签名
+ String timestamp = DateUtils.format(DateUtils.FORMAT_UTC_ISO8601, new Date(), 0);
+ String queryString = UrlBuilder.joinQueryParameters(new StringBuilder(urlPath), parameters).toString();
+ // String timestamp = System.currentTimeMillis()+"";
+// System.out.println("timestamp:"+timestamp);
+// System.out.println("timestamp:"+timestamp);
+// System.out.println("secretKey:"+secretKey);
+// System.out.println("httpMethod.toString():"+httpMethod.toString());
+// System.out.println("queryString:"+queryString);
+// System.out.println("passphrase:"+passphrase);
+ // 组装body
+ String body = "";
+ if (HttpMethod.POST.equals(httpMethod)) {
+ body = JSON.toJSONString(parameters);
+ queryString = UrlBuilder.joinQueryParameters(new StringBuilder(urlPath), null).toString();
+ fullUrl = UrlBuilder.buildFullUrl(baseUrl, urlPath, null, null);
+ }
+ if (HttpMethod.GET.equals(httpMethod)) {
+ queryString = UrlBuilder.buildFullUrl(urlPath,"" , parameters, null);
+// queryString = UrlBuilder.buildFullUrl(null, urlPath, parameters, null);
+ }
+ String sign = SignUtils.signRest(secretKey,
+ timestamp,
+ httpMethod.toString(),
+ queryString, body);
+
+
+ request = RequestBuilder.buildApiKeyRequest(fullUrl, body, passphrase, sign, timestamp, httpMethod, apiKey,isSimluate);
+
+
+ break;
+ default:
+ throw new FebsException("[RequestHandler] Invalid request type: " + requestType);
+ }
+ return ResponseHandler.handleResponse(request, isSimluate);
+ }
+
+ public String sendPublicRequest(String baseUrl, String urlPath, LinkedHashMap<String, Object> parameters,
+ HttpMethod httpMethod, boolean isSimluate) {
+ return sendApiRequest(baseUrl, urlPath, parameters, httpMethod, RequestType.PUBLIC, isSimluate);
+ }
+
+ public String sendWithApiKeyRequest(String baseUrl, String urlPath, LinkedHashMap<String, Object> parameters,
+ HttpMethod httpMethod, boolean isSimluate) {
+ if (null == apiKey || apiKey.isEmpty()) {
+ throw new FebsException("[RequestHandler] API key cannot be null or empty!");
+ }
+ return sendApiRequest(baseUrl, urlPath, parameters, httpMethod, RequestType.WITH_API_KEY, isSimluate);
+ }
+
+ public String sendSignedRequest(String baseUrl, String urlPath, LinkedHashMap<String, Object> parameters,
+ HttpMethod httpMethod, boolean isSimluate) {
+ if (null == secretKey || secretKey.isEmpty() || null == apiKey || apiKey.isEmpty()) {
+ throw new FebsException("[RequestHandler] Secret key/API key cannot be null or empty!");
+ }
+ //parameters.put("timestamp", UrlBuilder.buildTimestamp());
+ //String queryString = UrlBuilder.joinQueryParameters(parameters);
+ //String signature = SignatureGenerator.getSignature(queryString, secretKey);
+ return sendApiRequest(baseUrl, urlPath, parameters, httpMethod, RequestType.SIGNED, isSimluate);
+ }
+}
diff --git a/src/main/java/com/xcong/excoin/modules/newPrice/ResponseHandler.java b/src/main/java/com/xcong/excoin/modules/newPrice/ResponseHandler.java
new file mode 100644
index 0000000..53878e5
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/newPrice/ResponseHandler.java
@@ -0,0 +1,66 @@
+package com.xcong.excoin.modules.newPrice;
+
+
+import cn.hutool.json.JSONException;
+import com.xcong.excoin.common.exception.FebsException;
+import com.xcong.excoin.modules.newPrice.utils.JSONParser;
+import com.xcong.excoin.modules.newPrice.utils.OkHttpUtils;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+
+import java.io.IOException;
+
+public final class ResponseHandler {
+
+ private static final int HTTP_STATUS_CODE_400 = 400;
+ private static final int HTTP_STATUS_CODE_499 = 499;
+ private static final int HTTP_STATUS_CODE_500 = 500;
+
+ private ResponseHandler() {
+ }
+
+ public static String handleResponse(Request request, boolean showLimitUsage) {
+ try {
+ OkHttpUtils.builder();
+ try (Response response = OkHttpUtils.okHttpClient.newCall(request).execute()) {//OkHttpUtils.builder().okHttpClient
+ String responseAsString = getResponseBodyAsString(response.body());
+
+ if (response.code() >= HTTP_STATUS_CODE_400 && response.code() <= HTTP_STATUS_CODE_499) {
+ throw handleErrorResponse(responseAsString, response.code());
+ } else if (response.code() >= HTTP_STATUS_CODE_500) {
+ System.out.println("handleResponse:"+response.code());
+ throw new FebsException("responseAsString-"+responseAsString+";handleResponse-"+response.code());
+ }
+ return responseAsString;
+ // if (showLimitUsage) {
+ // return getlimitUsage(response, responseAsString);
+ // } else {
+ // return responseAsString;
+ // }
+ }
+ } catch (IOException | IllegalStateException e) {
+ e.printStackTrace();
+ throw new FebsException("[ResponseHandler] OKHTTP Error: " + e.getMessage());
+ }
+ }
+
+
+ private static FebsException handleErrorResponse(String responseBody, int responseCode) {
+ try {
+ String errorMsg = JSONParser.getJSONStringValue(responseBody, "msg");
+ int errorCode = JSONParser.getJSONIntValue(responseBody, "code");
+ return new FebsException("responseBody-"+responseBody+";errorMsg-"+errorMsg+";responseCode-"+responseCode+";errorCode-"+errorCode);
+ } catch (JSONException e) {
+ throw new FebsException("responseBody-"+responseBody+";responseCode-"+responseCode);
+ }
+ }
+
+ private static String getResponseBodyAsString(ResponseBody body) throws IOException {
+ if (null != body) {
+ return body.string();
+ } else {
+ return "";
+ }
+ }
+}
diff --git a/src/main/java/com/xcong/excoin/modules/newPrice/enums/DefaultUrls.java b/src/main/java/com/xcong/excoin/modules/newPrice/enums/DefaultUrls.java
new file mode 100644
index 0000000..845c706
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/newPrice/enums/DefaultUrls.java
@@ -0,0 +1,38 @@
+package com.xcong.excoin.modules.newPrice.enums;
+
+public final class DefaultUrls {
+ public static final String USDM_UAT_URL = "https://www.okx.com";
+ public static final String USDM_UAT_WSS_URL = "wss://wspap.okx.com:8443";
+ //public static final String USDM_UAT_WSS_URL = "wss://ws.okx.com:8443";
+ //USD-M Futures
+ public static final String USDM_PROD_URL = "https://aws.okx.com";
+ public static final String USDM_PROD_WS_URL = "wss://ws.okx.com:8443";
+ //比特币买入数量
+ public static final String BTC_BUYNUMBER = "0.001";
+ //以太坊买入数量
+ public static final String ETH_BUYNUMBER = "0.01";
+ //狗狗币买入数量
+ public static final String DOGE_BUYNUMBER = "100";
+ /**
+ * 全部卖出
+ */
+ public static final String OPERATION_ALLSOLD = "allsold";
+
+ /**
+ * 卖出
+ */
+ public static final String OPERATION_SOLD = "sell";
+
+ /**
+ * 买入
+ */
+ public static final String OPERATION_BUY = "buy";
+
+ /**
+ * 不买入
+ */
+ public static final String OPERATION_NOBUY = "nobuy";
+
+ private DefaultUrls() {
+ }
+}
diff --git a/src/main/java/com/xcong/excoin/modules/newPrice/enums/HttpMethod.java b/src/main/java/com/xcong/excoin/modules/newPrice/enums/HttpMethod.java
new file mode 100644
index 0000000..c32ed4d
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/newPrice/enums/HttpMethod.java
@@ -0,0 +1,9 @@
+package com.xcong.excoin.modules.newPrice.enums;
+
+public enum HttpMethod {
+ POST,
+ GET,
+ PUT,
+ DELETE,
+ INVALID
+}
diff --git a/src/main/java/com/xcong/excoin/modules/newPrice/enums/RequestType.java b/src/main/java/com/xcong/excoin/modules/newPrice/enums/RequestType.java
new file mode 100644
index 0000000..faf00e1
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/newPrice/enums/RequestType.java
@@ -0,0 +1,7 @@
+package com.xcong.excoin.modules.newPrice.enums;
+
+public enum RequestType {
+ PUBLIC,
+ WITH_API_KEY,
+ SIGNED
+}
diff --git a/src/main/java/com/xcong/excoin/modules/newPrice/impl/ExchangeLoginEventServiceImpl.java b/src/main/java/com/xcong/excoin/modules/newPrice/impl/ExchangeLoginEventServiceImpl.java
new file mode 100644
index 0000000..b9def41
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/newPrice/impl/ExchangeLoginEventServiceImpl.java
@@ -0,0 +1,50 @@
+package com.xcong.excoin.modules.newPrice.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.xcong.excoin.modules.newPrice.ExchangeLoginEventService;
+import com.xcong.excoin.modules.newPrice.OKXAccount;
+import com.xcong.excoin.modules.newPrice.enums.DefaultUrls;
+import com.xcong.excoin.modules.newPrice.enums.HttpMethod;
+import com.xcong.excoin.modules.newPrice.utils.OKXContants;
+import lombok.extern.slf4j.Slf4j;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.*;
+
+@Slf4j
+public class ExchangeLoginEventServiceImpl implements ExchangeLoginEventService {
+
+
+ private final com.xcong.excoin.modules.newPrice.OKXAccount OKXAccount;
+ private final String apiKey;
+ private final String secretKey;
+ private final String passphrase;
+ private final boolean accountType;
+
+ public ExchangeLoginEventServiceImpl(String apiKey, String secretKey, String passphrase, boolean accountType) {
+ this.apiKey = apiKey;
+ this.secretKey = secretKey;
+ this.passphrase = passphrase;
+ this.accountType = accountType;
+ OKXAccount = new OKXAccount(
+ accountType ? DefaultUrls.USDM_PROD_URL : DefaultUrls.USDM_UAT_URL,
+ apiKey,
+ secretKey,
+ passphrase,
+ !accountType);
+ }
+
+ @Override
+ public String exchangeInfo(LinkedHashMap<String, Object> parameters) {
+ return OKXAccount.requestHandler.sendPublicRequest(OKXAccount.baseUrl, OKXContants.INSTRUMENTS,parameters, HttpMethod.GET, OKXAccount.isSimluate());
+ }
+
+ @Override
+ public String lineHistory(LinkedHashMap<String, Object> parameters) {
+ return OKXAccount.requestHandler.sendPublicRequest(OKXAccount.baseUrl, OKXContants.K_LINE_HISTORY,parameters, HttpMethod.GET, OKXAccount.isSimluate());
+ }
+
+}
diff --git a/src/main/java/com/xcong/excoin/modules/newPrice/utils/DateUtils.java b/src/main/java/com/xcong/excoin/modules/newPrice/utils/DateUtils.java
new file mode 100644
index 0000000..df7ca20
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/newPrice/utils/DateUtils.java
@@ -0,0 +1,80 @@
+package com.xcong.excoin.modules.newPrice.utils;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.ZoneId;
+import java.util.Date;
+import java.util.TimeZone;
+
+public class DateUtils {
+
+ public static final String FORMAT_Y = "yyyy";
+ public static final String FORMAT_D_1 = "yyyy/MM/dd";
+ public static final String FORMAT_D_2 = "yyyy-MM-dd";
+ public static final String FORMAT_D_3 = "yyyyMMdd";
+ public static final String FORMAT_D_4 = "yyyy.MM.dd";
+ public static final String FORMAT_D = "dd";
+ public static final String FORMAT_DT_1 = "yyyy/MM/dd HH:mm:ss";
+ public static final String FORMAT_DT_2 = "yyyy-MM-dd HH:mm:ss";
+ public static final String FORMAT_DT_3 = "yyyyMMdd HH:mm:ss";
+ public static final String FORMAT_DT_4 = "yyyy-MM-dd HH:mm";
+ public static final String FORMAT_DT_5 = "yyyy.MM.dd HH:mm:ss";
+ public static final String FORMAT_DT_6 = "yyyyMMddHHmmss";
+ public static final String FORMAT_DT_7 = "yyyyMMddHH";
+ public static final String FORMAT_M_1 = "yyyy/MM";
+ public static final String FORMAT_M_2 = "yyyy-MM";
+ public static final String FORMAT_M_3 = "yyyyMM";
+ public static final String FORMAT_M = "MM";
+ public static final String FORMAT_MD_1 = "MM/dd";
+ public static final String FORMAT_MD_2 = "MM-dd";
+ public static final String FORMAT_MD_3 = "MMdd";
+ public static final String FORMAT_T_1 = "HH:mm:ss";
+ public static final String FORMAT_T_2 = "HH:mm";
+ public static final String FORMAT_TH = "HH";
+ public static final String FORMAT_TM = "mm";
+ public static final String FORMAT_TS = "ss";
+ public static final String FORMAT_UTC_ISO8601 = "yyyy-MM-dd'T'HH:mm:ss'Z'";
+
+ public static String format(String format, Date date) {
+ SimpleDateFormat sdf = new SimpleDateFormat(format);
+ return sdf.format(date);
+ }
+
+ /**
+ * @param format format
+ * @param date date
+ * @param timeZone 时区数字 -8, 0, 8 等
+ * @return date string
+ */
+ public static String format(String format, Date date, int timeZone) {
+ timeZone = timeZone % 13;
+ SimpleDateFormat sdf = new SimpleDateFormat(format);
+ ZoneId zoneId = ZoneId.of("GMT" + (timeZone >= 0 ? "+" : "") + timeZone);
+ TimeZone tz = TimeZone.getTimeZone(zoneId);
+ sdf.setTimeZone(tz);
+ return sdf.format(date);
+ }
+
+ public static Date parse(String dateString, String format) {
+ SimpleDateFormat sdf = new SimpleDateFormat(format);
+
+ try {
+ return sdf.parse(dateString);
+ } catch (ParseException var4) {
+ return null;
+ }
+ }
+
+ public static Date parse(String dateString, String format, int timeZone) {
+ SimpleDateFormat sdf = new SimpleDateFormat(format);
+ ZoneId zoneId = ZoneId.of("GMT" + (timeZone >= 0 ? "+" : "") + timeZone);
+ TimeZone tz = TimeZone.getTimeZone(zoneId);
+ sdf.setTimeZone(tz);
+ try {
+ return sdf.parse(dateString);
+ } catch (ParseException var4) {
+ return null;
+ }
+ }
+
+}
diff --git a/src/main/java/com/xcong/excoin/modules/newPrice/utils/JSONParser.java b/src/main/java/com/xcong/excoin/modules/newPrice/utils/JSONParser.java
new file mode 100644
index 0000000..92f6c5f
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/newPrice/utils/JSONParser.java
@@ -0,0 +1,40 @@
+package com.xcong.excoin.modules.newPrice.utils;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+
+public final class JSONParser {
+
+ private JSONParser() {
+ }
+
+ public static String getJSONStringValue(String json, String key) {
+ try {
+ JSONObject obj = new JSONObject(json);
+ return obj.getString(key);
+ } catch (JSONException e) {
+ throw new JSONException(String.format("[JSONParser] Failed to get \"%s\" from JSON object", key));
+ }
+ }
+
+ public static int getJSONIntValue(String json, String key) {
+ try {
+ JSONObject obj = new JSONObject(json);
+ return obj.getInt(key);
+ } catch (JSONException e) {
+ throw new JSONException(String.format("[JSONParser] Failed to get \"%s\" from JSON object", key));
+ }
+ }
+
+ public static String getJSONArray(ArrayList<?> symbols, String key) {
+ try {
+ JSONArray arr = new JSONArray(symbols);
+ return arr.toString();
+ } catch (JSONException e) {
+ throw new JSONException(String.format("[JSONParser] Failed to convert \"%s\" to JSON array", key));
+ }
+ }
+}
diff --git a/src/main/java/com/xcong/excoin/modules/newPrice/utils/OKXContants.java b/src/main/java/com/xcong/excoin/modules/newPrice/utils/OKXContants.java
new file mode 100644
index 0000000..6426533
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/newPrice/utils/OKXContants.java
@@ -0,0 +1,185 @@
+package com.xcong.excoin.modules.newPrice.utils;
+
+public class OKXContants {
+ /**
+ * 获取交易产品基础信息
+ * 获取所有可交易产品的信息列表。
+ * <br><br>
+ * GET /api/v5/public/instruments
+ * <br>
+ *
+ * @param parameters LinkedHashedMap of String,Object pair
+ * where String is the name of the parameter and Object is the value of the parameter
+ * <br><br>
+ * instType -- String 是 产品类型 SPOT:币币 MARGIN:币币杠杆 SWAP:永续合约 FUTURES:交割合约 OPTION:期权 <br>
+ * uly -- String 可选 标的指数,仅适用于交割/永续/期权,期权必填 <br>
+ * instFamily -- String 否 交易品种,仅适用于交割/永续/期权 <br>
+ * instId -- String 否 产品ID <br>
+ * @return String
+ * @see <a href="https://www.okx.com/docs-v5/zh/#rest-api-public-data-get-instruments">
+ * https://www.okx.com/docs-v5/zh/#rest-api-public-data-get-instruments</a>
+ */
+ public static final String INSTRUMENTS = "/api/v5/public/instruments";
+// public static final String K_LINE_HISTORY = "/api/v5/market/history-candles";
+ public static final String K_LINE_HISTORY = "/api/v5/market/history-mark-price-candles";
+ /**
+ * 查看账户余额
+ * 获取交易账户中资金余额信息。
+ * <br><br>
+ * GET /api/v5/account/balance
+ * <br>
+ * @param
+ * parameters LinkedHashedMap of String,Object pair
+ * where String is the name of the parameter and Object is the value of the parameter
+ * <br><br>
+ * ccy -- String 否 币种,如 BTC 支持多币种查询(不超过20个),币种之间半角逗号分隔 <br>
+ * @return String
+ * @see <a href="https://www.okx.com/docs-v5/zh/#rest-api-account-get-balance">
+ * https://www.okx.com/docs-v5/zh/#rest-api-account-get-balance</a>
+ */
+ public static final String BALANCE = "/api/v5/account/balance";
+ /**
+ * 查看持仓信息
+ * 获取该账户下拥有实际持仓的信息。账户为单向持仓模式会显示净持仓(net),账户为双向持仓模式下会分别返回多头(long)或空头(short)的仓位。按照仓位创建时间倒序排列。
+ * <br><br>
+ * GET /api/v5/account/positions
+ * <br>
+ * @param
+ * parameters LinkedHashedMap of String,Object pair
+ * where String is the name of the parameter and Object is the value of the parameter
+ * <br><br>
+ * instType -- String 否 产品类型
+ * MARGIN:币币杠杆
+ * SWAP:永续合约
+ * FUTURES:交割合约
+ * OPTION:期权
+ * instType和instId同时传入的时候会校验instId与instType是否一致。<br>
+ * instId -- String 否 交易产品ID,如:BTC-USD-190927-5000-C
+ * 支持多个instId查询(不超过10个),半角逗号分隔<br>
+ * posId -- String 否 持仓ID
+ * 支持多个posId查询(不超过20个),半角逗号分割<br>
+ * @return String <br>
+ * note: 如果该 instId 拥有过仓位且当前持仓量为0,传 instId 时,会返回仓位信息;不传 instId 时,仓位信息不返回。
+ * 逐仓交易设置中,如果设置为自主划转模式,逐仓转入保证金后,会生成一个持仓量为0的仓位 <br>
+ * @see <a href="https://www.okx.com/docs-v5/zh/#rest-api-account-get-positions">
+ * https://www.okx.com/docs-v5/zh/#rest-api-account-get-positions</a>
+ */
+ public static final String POSITIONS = "/api/v5/account/positions";
+ /**
+ * 查看历史持仓信息
+ * 获取最近3个月有更新的仓位信息,按照仓位更新时间倒序排列。
+ * <br><br>
+ * GET /api/v5/account/positions-history
+ * <br>
+ * @param
+ * parameters LinkedHashedMap of String,Object pair
+ * where String is the name of the parameter and Object is the value of the parameter
+ * <br><br>
+ * instType -- String 否 产品类型
+ * MARGIN:币币杠杆
+ * SWAP:永续合约
+ * FUTURES:交割合约
+ * OPTION:期权 <br>
+ * instId -- String 否 交易产品ID,如:BTC-USD-SWAP <br>
+ * mgnMode -- String 否 保证金模式
+ * cross:全仓,isolated:逐仓
+ * type -- String 否 平仓类型
+ * 1:部分平仓;2:完全平仓;3:强平;4:强减; 5:ADL自动减仓;
+ * 状态叠加时,以最新的平仓类型为准状态为准。 <br>
+ * posId -- String 否 持仓ID <br>
+ * after -- String 否 查询仓位更新 (uTime) 之前的内容,值为时间戳,Unix 时间戳为毫秒数格式,如 1597026383085 <br>
+ * before -- String 否 查询仓位更新 (uTime) 之后的内容,值为时间戳,Unix 时间戳为毫秒数格式,如 1597026383085 <br>
+ * limit -- String 否 分页返回结果的数量,最大为100,默认100条 <br>
+ * @return String
+ * @see <a href="https://www.okx.com/docs-v5/zh/#rest-api-account-get-positions-history">
+ * https://www.okx.com/docs-v5/zh/#rest-api-account-get-positions-history</a>
+ */
+ public static final String POSITIONS_HISTORY = "/api/v5/account/positions-history";
+ /**
+ * 撤单
+ * 撤销之前下的未完成订单。
+ *
+ * <br><br>
+ * GET /api/v5/trade/cancel-order
+ * <br>
+ *
+ * @param parameters LinkedHashedMap of String,Object pair
+ * where String is the name of the parameter and Object is the value of the parameter
+ * <br><br>
+ * instId -- String 是 产品ID,如 BTC-USD-190927 <br>
+ * ordId -- String 可选 订单ID, ordId和clOrdId必须传一个,若传两个,以ordId为主 <br>
+ * clOrdId -- String 可选 用户自定义ID <br>
+ * @return String
+ * @see <a href="https://www.okx.com/docs-v5/zh/#rest-api-trade-cancel-order">
+ * https://www.okx.com/docs-v5/zh/#rest-api-trade-cancel-order</a>
+ */
+ public static final String CANCEL_ORDER = "/api/v5/trade/cancel-order";
+ /**
+ * 下单
+ * 只有当您的账户有足够的资金才能下单。
+ *
+ * <br><br>
+ * GET /api/v5/trade/order
+ * <br>
+ *
+ * @param parameters LinkedHashedMap of String,Object pair
+ * where String is the name of the parameter and Object is the value of the parameter
+ * <br><br>
+ * instId -- String 是 产品ID,如 BTC-USD-190927-5000-C <br>
+ * tdMode -- String 是 交易模式
+ * 保证金模式:isolated:逐仓 ;cross:全仓
+ * 非保证金模式:cash:非保证金 <br>
+ * ccy -- String 否 保证金币种,仅适用于单币种保证金模式下的全仓杠杆订单 <br>
+ * clOrdId -- String 否 客户自定义订单ID
+ * 字母(区分大小写)与数字的组合,可以是纯字母、纯数字且长度要在1-32位之间。<br>
+ * tag -- String 否 订单标签
+ * 字母(区分大小写)与数字的组合,可以是纯字母、纯数字,且长度在1-16位之间。 <br>
+ * side -- String 是 订单方向
+ * buy:买, sell:卖 <br>
+ * posSide -- String 可选 持仓方向
+ * 在双向持仓模式下必填,且仅可选择 long 或 short。 仅适用交割、永续。 <br>
+ * ordType -- String 是 订单类型
+ * market:市价单
+ * limit:限价单
+ * post_only:只做maker单
+ * fok:全部成交或立即取消
+ * ioc:立即成交并取消剩余
+ * optimal_limit_ioc:市价委托立即成交并取消剩余(仅适用交割、永续) <br>
+ * sz -- String 是 委托数量 <br>
+ * px -- String 可选 委托价格,仅适用于limit、post_only、fok、ioc类型的订单 <br>
+ * reduceOnly -- Boolean 否 是否只减仓,true 或 false,默认false
+ * 仅适用于币币杠杆,以及买卖模式下的交割/永续
+ * 仅适用于单币种保证金模式和跨币种保证金模式 <br>
+ * tgtCcy -- String 否 市价单委托数量sz的单位,仅适用于币币市价订单
+ * base_ccy: 交易货币 ;quote_ccy:计价货币
+ * 买单默认quote_ccy, 卖单默认base_ccy <br>
+ * banAmend -- Boolean 否 是否禁止币币市价改单,true 或 false,默认false
+ * 为true时,余额不足时,系统不会改单,下单会失败,仅适用于币币市价单 <br>
+ * tpTriggerPx -- String 否 止盈触发价,如果填写此参数,必须填写 止盈委托价 <br>
+ * tpOrdPx -- String 否 止盈委托价,如果填写此参数,必须填写 止盈触发价
+ * 委托价格为-1时,执行市价止盈 <br>
+ * slTriggerPx -- String 否 止损触发价,如果填写此参数,必须填写 止损委托价 <br>
+ * slOrdPx -- String 否 止损委托价,如果填写此参数,必须填写 止损触发价
+ * 委托价格为-1时,执行市价止损 <br>
+ * tpTriggerPxType -- String 否 止盈触发价类型
+ * last:最新价格
+ * index:指数价格
+ * mark:标记价格
+ * 默认为last <br>
+ * slTriggerPxType -- String 否 止损触发价类型
+ * last:最新价格
+ * index:指数价格
+ * mark:标记价格
+ * 默认为last <br>
+ * @return String
+ * @see <a href="https://www.okx.com/docs-v5/zh/#rest-api-trade-place-order">
+ * https://www.okx.com/docs-v5/zh/#rest-api-trade-place-order</a>
+ */
+ public static final String ORDER = "/api/v5/trade/order";
+
+ /**
+ * 获取币种价格信息
+ */
+ public static final String TICKER = "/api/v5/market/ticker";
+
+}
diff --git a/src/main/java/com/xcong/excoin/modules/newPrice/utils/OkHttpUtils.java b/src/main/java/com/xcong/excoin/modules/newPrice/utils/OkHttpUtils.java
new file mode 100644
index 0000000..9e823e0
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/newPrice/utils/OkHttpUtils.java
@@ -0,0 +1,330 @@
+package com.xcong.excoin.modules.newPrice.utils;
+
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.*;
+import org.json.JSONObject;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.security.SecureRandom;
+import java.security.cert.X509Certificate;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * OkHttp请求工具封装
+ * 参考:https://blog.csdn.net/DwZ735660836/article/details/119976068
+ */
+@Slf4j
+public class OkHttpUtils {
+ public static volatile OkHttpClient okHttpClient = null;
+ private static volatile Semaphore semaphore = null;
+ private Map<String, String> headerMap;
+ private Map<String, String> paramMap;
+ private String url;
+ private Request.Builder request;
+ // 开发环境用的 ShadowsocksR-dotnet4.0 免费版本 正式环境得使用外网服务器
+ // 安易代理 http://127.0.0.1:10809/ http://127.0.0.1:10808/
+
+ /**
+ * 初始化okHttpClient,并且允许https访问
+ */
+ private OkHttpUtils() {
+ if (okHttpClient == null) {
+ synchronized (OkHttpUtils.class) {
+ if (okHttpClient == null) {
+ TrustManager[] trustManagers = buildTrustManagers();
+ okHttpClient = new OkHttpClient.Builder()
+ .connectTimeout(30, TimeUnit.SECONDS)
+ .writeTimeout(20, TimeUnit.SECONDS)
+ .readTimeout(60, TimeUnit.SECONDS)
+ .sslSocketFactory(createSSLSocketFactory(trustManagers), (X509TrustManager) trustManagers[0])
+ //.hostnameVerifier((hostName, session) -> true)
+ //配置自定义连接池参数
+ .connectionPool(new ConnectionPool(5, 60, TimeUnit.SECONDS))
+ .retryOnConnectionFailure(true)
+ .build();
+ addHeader("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36");
+ addHeader("Connection", "close");
+ addHeader("Accept-Encoding", "identity");
+ }
+ }
+ }
+ }
+
+ /**
+ * 用于异步请求时,控制访问线程数,返回结果
+ *
+ * @return
+ */
+ private static Semaphore getSemaphoreInstance() {
+ //只能1个线程同时访问
+ synchronized (OkHttpUtils.class) {
+ if (semaphore == null) {
+ semaphore = new Semaphore(0);
+ }
+ }
+ return semaphore;
+ }
+
+ /**
+ * 创建OkHttpUtils
+ *
+ * @return
+ */
+ public static OkHttpUtils builder() {
+ return new OkHttpUtils();
+ }
+
+ /**
+ * 添加url
+ *
+ * @param url
+ * @return
+ */
+ public OkHttpUtils url(String url) {
+ this.url = url;
+ return this;
+ }
+
+ /**
+ * 添加参数
+ *
+ * @param key 参数名
+ * @param value 参数值
+ * @return
+ */
+ public OkHttpUtils addParam(String key, String value) {
+ if (paramMap == null) {
+ paramMap = new LinkedHashMap<>(16);
+ }
+ paramMap.put(key, value);
+ return this;
+ }
+
+ /**
+ * 添加请求头
+ *
+ * @param key 参数名
+ * @param value 参数值
+ * @return
+ */
+ public OkHttpUtils addHeader(String key, String value) {
+ if (headerMap == null) {
+ headerMap = new LinkedHashMap<>(16);
+ }
+ headerMap.put(key, value);
+ return this;
+ }
+
+ /**
+ * 初始化get方法
+ *
+ * @return
+ */
+ public OkHttpUtils get() {
+ request = new Request.Builder().get();
+ StringBuilder urlBuilder = new StringBuilder(url);
+ if (paramMap != null) {
+ urlBuilder.append("?");
+ try {
+ for (Map.Entry<String, String> entry : paramMap.entrySet()) {
+ urlBuilder.append(URLEncoder.encode(entry.getKey(), "utf-8")).
+ append("=").
+ append(URLEncoder.encode(entry.getValue(), "utf-8")).
+ append("&");
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ urlBuilder.deleteCharAt(urlBuilder.length() - 1);
+ }
+ request.url(urlBuilder.toString());
+ return this;
+ }
+
+ /**
+ * 初始化post方法
+ *
+ * @param isJsonPost true等于json的方式提交数据,类似postman里post方法的raw
+ * false等于普通的表单提交
+ * @return
+ */
+ public OkHttpUtils post(boolean isJsonPost) {
+ RequestBody requestBody;
+ if (isJsonPost) {
+ String json = "";
+ if (paramMap != null) {
+ json = JSONObject.valueToString(paramMap);
+ }
+ requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json);
+ } else {
+ FormBody.Builder formBody = new FormBody.Builder();
+ if (paramMap != null) {
+ paramMap.forEach(formBody::add);
+ }
+ requestBody = formBody.build();
+ }
+ request = new Request.Builder().post(requestBody).url(url);
+ return this;
+ }
+
+ /**
+ * 同步请求
+ *
+ * @return
+ */
+ public Request.Builder sync() {
+ return setHeader(request);
+ }
+
+ /**
+ * 同步请求
+ *
+ * @return
+ */
+ public String syncStr() {
+ setHeader(request);
+ try {
+ Response response = okHttpClient.newCall(request.build()).execute();
+ assert response.body() != null;
+ return response.body().string();
+ } catch (IOException e) {
+ e.printStackTrace();
+ return "请求失败:" + e.getMessage();
+ }
+ }
+
+
+ /**
+ * 异步请求,有返回值
+ */
+ public String async() {
+ StringBuilder buffer = new StringBuilder();
+ setHeader(request);
+ okHttpClient.newCall(request.build()).enqueue(new Callback() {
+ @Override
+ public void onFailure(Call call, IOException e) {
+ buffer.append("请求出错:").append(e.getMessage());
+ }
+
+ @Override
+ public void onResponse(Call call, Response response) throws IOException {
+ assert response.body() != null;
+ buffer.append(response.body().string());
+ getSemaphoreInstance().release();
+ }
+ });
+ try {
+ getSemaphoreInstance().acquire();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ return buffer.toString();
+ }
+
+ /**
+ * 异步请求,带有接口回调
+ *
+ * @param callBack
+ */
+ public void async(ICallBack callBack) {
+ setHeader(request);
+ okHttpClient.newCall(request.build()).enqueue(new Callback() {
+ @Override
+ public void onFailure(Call call, IOException e) {
+ callBack.onFailure(call, e.getMessage());
+ }
+
+ @Override
+ public void onResponse(Call call, Response response) throws IOException {
+ assert response.body() != null;
+ callBack.onSuccessful(call, response.body().string());
+ }
+ });
+ }
+
+ /**
+ * 为request添加请求头
+ *
+ * @param request
+ */
+ private Request.Builder setHeader(Request.Builder request) {
+ if (headerMap != null) {
+ try {
+ for (Map.Entry<String, String> entry : headerMap.entrySet()) {
+ request.addHeader(entry.getKey(), entry.getValue());
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ return request;
+ }
+
+
+ /**
+ * 生成安全套接字工厂,用于https请求的证书跳过
+ *
+ * @return
+ */
+ private static SSLSocketFactory createSSLSocketFactory(TrustManager[] trustAllCerts) {
+ SSLSocketFactory ssfFactory = null;
+ try {
+ SSLContext sc = SSLContext.getInstance("SSL");
+ sc.init(null, trustAllCerts, new SecureRandom());
+ ssfFactory = sc.getSocketFactory();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return ssfFactory;
+ }
+
+ private static TrustManager[] buildTrustManagers() {
+ return new TrustManager[]{
+ new X509TrustManager() {
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType) {
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType) {
+ }
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return new X509Certificate[]{};
+ }
+ }
+ };
+ }
+
+ /**
+ * 自定义一个接口回调
+ */
+ public interface ICallBack {
+
+ void onSuccessful(Call call, String data);
+
+ void onFailure(Call call, String errorMsg);
+
+ }
+
+ public static void main(String[] args) {
+ String url = "https://api2.binance.com/api/v3/ticker/24hr?symbol=BNBUSDT&type=MINI";
+ String result = OkHttpUtils.builder()
+ .url(url)
+ .addHeader("Content-Type", "application/x-www-form-urlencoded")
+ .get()
+ .syncStr();
+ System.out.println(result);
+ }
+}
+
+
diff --git a/src/main/java/com/xcong/excoin/modules/newPrice/utils/ParameterChecker.java b/src/main/java/com/xcong/excoin/modules/newPrice/utils/ParameterChecker.java
new file mode 100644
index 0000000..ede6fdd
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/newPrice/utils/ParameterChecker.java
@@ -0,0 +1,36 @@
+package com.xcong.excoin.modules.newPrice.utils;
+
+import cc.mrbird.febs.common.exception.FebsException;
+
+import java.util.LinkedHashMap;
+
+public final class ParameterChecker {
+
+ private ParameterChecker() {
+ }
+
+ public static void checkParameter(LinkedHashMap<String, Object> parameters, String parameter, Class t) {
+ checkRequiredParameter(parameters, parameter);
+ checkParameterType(parameters.get(parameter), t, parameter);
+ }
+
+ public static void checkOrParameters(LinkedHashMap<String, Object> parameters, String parameter, String parameter2) {
+ if (!parameters.containsKey(parameter) && (!parameters.containsKey(parameter2))) {
+ throw new FebsException(String.format("Either \"%s\" or \"%s\" is required!", parameter, parameter2));
+ }
+ }
+
+ public static void checkRequiredParameter(LinkedHashMap<String, Object> parameters, String parameter) {
+ if (!parameters.containsKey(parameter)) {
+ throw new FebsException(String.format("\"%s\" is a mandatory parameter!", parameter));
+ }
+ }
+
+ public static void checkParameterType(Object parameter, Class t, String name) {
+ if (!t.isInstance(parameter)) {
+ throw new FebsException(String.format("\"%s\" must be of %s type.", name, t));
+ } else if (t == String.class && parameter.toString().trim().equals("")) {
+ throw new FebsException(String.format("\"%s\" must not be empty.", name));
+ }
+ }
+}
diff --git a/src/main/java/com/xcong/excoin/modules/newPrice/utils/RequestBuilder.java b/src/main/java/com/xcong/excoin/modules/newPrice/utils/RequestBuilder.java
new file mode 100644
index 0000000..a36cf50
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/newPrice/utils/RequestBuilder.java
@@ -0,0 +1,150 @@
+package com.xcong.excoin.modules.newPrice.utils;
+
+
+import com.xcong.excoin.common.exception.FebsException;
+import com.xcong.excoin.modules.newPrice.enums.HttpMethod;
+import okhttp3.MediaType;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+
+public final class RequestBuilder {
+ private static final MediaType JSON_TYPE = MediaType.parse("application/json; charset=utf-8");
+
+ private RequestBuilder() {
+ }
+
+ public static Request.Builder buildPublicRequest(String fullUrl, HttpMethod httpMethod, boolean issimulated) {
+ try {
+ final Request.Builder result;
+ switch (httpMethod) {
+ case POST:
+ OkHttpUtils builder = OkHttpUtils.builder();
+ if(issimulated){
+ builder.addHeader("x-simulated-trading","1");
+ }
+ result = builder
+ .url(fullUrl)
+ .post(true)
+ .sync();
+ break;
+ case GET:
+ OkHttpUtils builder1 = OkHttpUtils.builder();
+ if(issimulated){
+ builder1.addHeader("x-simulated-trading","1");
+ }
+ result = builder1
+ .url(fullUrl)
+ .addHeader("Content-Type", "application/x-www-form-urlencoded")
+ .get()
+ .sync();
+ break;
+ case PUT:
+ OkHttpUtils builder2 = OkHttpUtils.builder();
+ if(issimulated){
+ builder2.addHeader("x-simulated-trading","1");
+ }
+ result = builder2
+ .url(fullUrl)
+ .addHeader("Content-Type", "application/x-www-form-urlencoded")
+ .post(false)
+ .sync();
+ break;
+ case DELETE:
+ OkHttpUtils builder3 = OkHttpUtils.builder();
+ if(issimulated){
+ builder3.addHeader("x-simulated-trading","1");
+ }
+ result = builder3
+ .url(fullUrl)
+ .post(false)
+ .sync();
+ break;
+ default:
+ throw new FebsException("Invalid HTTP method: " + httpMethod);
+ }
+ return result;
+ } catch (IllegalArgumentException e) {
+ throw new FebsException("Invalid URL: " + e.getMessage());
+ }
+ }
+
+ public static Request buildApiKeyRequest(String fullUrl,String body,String passphrase,String sign,String timeStamp, HttpMethod httpMethod, String apiKey,boolean issimulate) {
+ try {
+ final Request request;
+ switch (httpMethod) {
+ case POST:
+ Request.Builder builder = new Request.Builder();
+ if(issimulate){
+ builder.addHeader("x-simulated-trading","1");
+ }
+ request = builder
+ .url(fullUrl)
+ .post(RequestBody.create(JSON_TYPE, body))
+ .addHeader("OK-ACCESS-KEY", apiKey)
+ .addHeader("OK-ACCESS-SIGN", sign)
+ .addHeader("OK-ACCESS-TIMESTAMP", timeStamp)
+ .addHeader("OK-ACCESS-PASSPHRASE", passphrase)
+ .build();
+ break;
+ case GET:
+ Request.Builder builder1 = new Request.Builder();
+ if(issimulate){
+ builder1.addHeader("x-simulated-trading","1");
+ }
+ request = builder1
+ .url(fullUrl)
+ .get()
+ .addHeader("Content-Type", "application/x-www-form-urlencoded")
+ .addHeader("OK-ACCESS-KEY", apiKey)
+ .addHeader("OK-ACCESS-KEY", apiKey)
+ .addHeader("OK-ACCESS-SIGN", sign)
+ .addHeader("OK-ACCESS-TIMESTAMP", timeStamp)
+ .addHeader("OK-ACCESS-PASSPHRASE", passphrase)
+ .build();
+ break;
+ case PUT:
+ Request.Builder builder2 = new Request.Builder();
+ if(issimulate){
+ builder2.addHeader("x-simulated-trading","1");
+ }
+ request = builder2
+ .url(fullUrl)
+ .put(RequestBody.create(JSON_TYPE, ""))
+ .addHeader("Content-Type", "application/x-www-form-urlencoded")
+ .addHeader("OK-ACCESS-KEY", apiKey)
+ .addHeader("OK-ACCESS-KEY", apiKey)
+ .addHeader("OK-ACCESS-SIGN", sign)
+ .addHeader("OK-ACCESS-TIMESTAMP", timeStamp)
+ .addHeader("OK-ACCESS-PASSPHRASE", passphrase)
+ .build();
+ break;
+ case DELETE:
+ Request.Builder builder3 = new Request.Builder();
+ if(issimulate){
+ builder3.addHeader("x-simulated-trading","1");
+ }
+ request = builder3
+ .url(fullUrl)
+ .delete()
+ .addHeader("Content-Type", "application/x-www-form-urlencoded")
+ .addHeader("OK-ACCESS-KEY", apiKey)
+ .addHeader("OK-ACCESS-KEY", apiKey)
+ .addHeader("OK-ACCESS-SIGN", sign)
+ .addHeader("OK-ACCESS-TIMESTAMP", timeStamp)
+ .addHeader("OK-ACCESS-PASSPHRASE", passphrase)
+ .build();
+ break;
+ default:
+ throw new FebsException("Invalid HTTP method: " + httpMethod);
+ }
+ return request;
+ } catch (IllegalArgumentException e) {
+ throw new FebsException("Invalid URL: " + e.getMessage());
+ }
+ }
+
+ public static Request buildWebsocketRequest(String fullUrl) {
+ return new Request.Builder().url(fullUrl).build();
+ }
+
+}
diff --git a/src/main/java/com/xcong/excoin/modules/newPrice/utils/SignUtils.java b/src/main/java/com/xcong/excoin/modules/newPrice/utils/SignUtils.java
new file mode 100644
index 0000000..9d5da67
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/newPrice/utils/SignUtils.java
@@ -0,0 +1,55 @@
+package com.xcong.excoin.modules.newPrice.utils;
+
+import lombok.extern.slf4j.Slf4j;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.util.Base64;
+
+
+@Slf4j
+public class SignUtils {
+
+ public static String signRest(String secretKey, String timestamp, String method, String path, String body) {
+ String str = String.format("%s%s%s%s",
+ timestamp, // timestamp
+ method, // method GET/POST
+ path, // requestPath
+ body // body
+ );
+ try {
+ return Base64.getEncoder().encodeToString(hmacSHA256(secretKey.getBytes(), str.getBytes()));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+
+ /**
+ * HmacSHA256算法,返回的结果始终是32位
+ *
+ * @param key 加密的键,可以是任何数据
+ * @param content 待加密的内容
+ * @return 加密后的内容
+ * @throws Exception ex
+ */
+ public static byte[] hmacSHA256(byte[] key, byte[] content) throws Exception {
+ Mac hmacSha256 = Mac.getInstance("HmacSHA256");
+ hmacSha256.init(new SecretKeySpec(key, 0, key.length, "HmacSHA256"));
+ return hmacSha256.doFinal(content);
+ }
+
+ public static String signWebsocket(String timestamp, String secretKey) {
+ String str = String.format("%s%s%s",
+ timestamp,
+ "GET",
+ "/users/self/verify");
+ try {
+ return Base64.getEncoder().encodeToString(hmacSHA256(secretKey.getBytes(), str.getBytes()));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/src/main/java/com/xcong/excoin/modules/newPrice/utils/UrlBuilder.java b/src/main/java/com/xcong/excoin/modules/newPrice/utils/UrlBuilder.java
new file mode 100644
index 0000000..5e28e12
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/newPrice/utils/UrlBuilder.java
@@ -0,0 +1,128 @@
+package com.xcong.excoin.modules.newPrice.utils;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+
+public final class UrlBuilder {
+ private static final int MAX_DECIMAL_DIGITS = 30;
+ private static DecimalFormat df;
+
+
+ private UrlBuilder() {
+ }
+
+ public static String buildFullUrl(String baseUrl, String urlPath, LinkedHashMap<String, Object> parameters, String signature) {
+ if (parameters != null && !parameters.isEmpty()) {
+ StringBuilder sb = new StringBuilder(baseUrl);
+ sb.append(urlPath).append('?');
+ joinQueryParameters(sb, parameters);
+ if (null != signature) {
+ sb.append("&signature=").append(signature);
+ }
+ return sb.toString();
+ } else {
+ return baseUrl + urlPath;
+ }
+ }
+
+ public static String buildStreamUrl(String baseUrl, ArrayList<String> streams) {
+ StringBuilder sb = new StringBuilder(baseUrl);
+ sb.append("?streams=");
+ return joinStreamUrls(sb, streams);
+ }
+
+ //concatenate query parameters
+ public static String joinQueryParameters(LinkedHashMap<String, Object> parameters) {
+ return joinQueryParameters(new StringBuilder(), parameters).toString();
+ }
+
+ public static StringBuilder joinQueryParameters(StringBuilder urlPath, LinkedHashMap<String, Object> parameters) {
+ if (parameters == null || parameters.isEmpty()) {
+ return urlPath;
+ }
+
+ boolean isFirst = true;
+ for (Map.Entry<String, Object> mapElement : parameters.entrySet()) {
+
+ if (mapElement.getValue() instanceof Double) {
+ parameters.replace(mapElement.getKey(), getFormatter().format(mapElement.getValue()));
+ } else if (mapElement.getValue() instanceof ArrayList) {
+ if (((ArrayList<?>) mapElement.getValue()).isEmpty()) {
+ continue;
+ }
+ String key = mapElement.getKey();
+ joinArrayListParameters(key, urlPath, (ArrayList<?>) mapElement.getValue(), isFirst);
+ isFirst = false;
+ continue;
+ }
+
+ if (isFirst) {
+ isFirst = false;
+ } else {
+ urlPath.append('&');
+ }
+
+ urlPath.append(mapElement.getKey())
+ .append('=')
+ .append(urlEncode(mapElement.getValue().toString()));
+ }
+ return urlPath;
+ }
+
+ private static void joinArrayListParameters(String key, StringBuilder urlPath, ArrayList<?> values, boolean isFirst) {
+ for (Object value: values) {
+ if (isFirst) {
+ isFirst = false;
+ } else {
+ urlPath.append('&');
+ }
+
+ urlPath.append(key)
+ .append('=')
+ .append(urlEncode(value.toString()));
+ }
+ }
+
+ private static String joinStreamUrls(StringBuilder urlPath, ArrayList<String> streams) {
+ boolean isFirst = true;
+ for (String stream: streams) {
+ if (isFirst) {
+ isFirst = false;
+ } else {
+ urlPath.append('/');
+ }
+ urlPath.append(stream);
+ }
+ return urlPath.toString();
+ }
+
+
+ public static String urlEncode(String s) {
+ try {
+ return URLEncoder.encode(s, StandardCharsets.UTF_8.name());
+ } catch (UnsupportedEncodingException e) {
+ // UTF-8 being unsuppored is unlikely
+ // Replace with a unchecked exception to tidy up exception handling
+ throw new RuntimeException(StandardCharsets.UTF_8.name() + " is unsupported", e);
+ }
+ }
+
+ private static DecimalFormat getFormatter() {
+ if (null == df) {
+ df = new DecimalFormat();
+ df.setMaximumFractionDigits(MAX_DECIMAL_DIGITS);
+ df.setGroupingUsed(false);
+ }
+ return df;
+ }
+
+ public static String buildTimestamp() {
+ return String.valueOf(System.currentTimeMillis());
+ }
+}
diff --git a/src/main/java/com/xcong/excoin/modules/symbols/controller/SymbolsController.java b/src/main/java/com/xcong/excoin/modules/symbols/controller/SymbolsController.java
index 64fe133..20342ef 100644
--- a/src/main/java/com/xcong/excoin/modules/symbols/controller/SymbolsController.java
+++ b/src/main/java/com/xcong/excoin/modules/symbols/controller/SymbolsController.java
@@ -2,6 +2,7 @@
import com.huobi.client.model.Candlestick;
import com.xcong.excoin.common.response.Result;
+import com.xcong.excoin.modules.newPrice.KlineVo;
import com.xcong.excoin.modules.symbols.parameter.dto.KlineDetailDto;
import com.xcong.excoin.modules.symbols.parameter.vo.HomeSymbolsVo;
import com.xcong.excoin.modules.symbols.parameter.vo.KlineDataVo;
@@ -61,6 +62,15 @@
return symbolsService.findKlineDetails(klineDetailDto);
}
+ @ApiOperation(value = "查询历史OKXK线数据", notes = "查询历史OKXK线数据")
+ @ApiResponses({
+ @ApiResponse(code = 0, message = "success", response = KlineDataVo.class)
+ })
+ @PostMapping(value = "/klineDetail")
+ public Result klineDetail(@RequestBody @Valid KlineVo klineDetailDto) {
+ return symbolsService.findKlineList(klineDetailDto);
+ }
+
@ApiOperation(value = "查询当日最高最低价")
@GetMapping(value = "/getDayHighAndLow")
public Result getDayHighAndLow(@ApiParam(name = "symbol", value = "币种", required = true, example = "BTC/USDT") @RequestParam(value = "symbol") String symbol) {
diff --git a/src/main/java/com/xcong/excoin/modules/symbols/service/SymbolsService.java b/src/main/java/com/xcong/excoin/modules/symbols/service/SymbolsService.java
index be91c71..2125165 100644
--- a/src/main/java/com/xcong/excoin/modules/symbols/service/SymbolsService.java
+++ b/src/main/java/com/xcong/excoin/modules/symbols/service/SymbolsService.java
@@ -2,6 +2,7 @@
import com.xcong.excoin.common.response.Result;
+import com.xcong.excoin.modules.newPrice.KlineVo;
import com.xcong.excoin.modules.symbols.parameter.dto.KlineDetailDto;
/**
@@ -18,4 +19,5 @@
public Result findKlineDetails(KlineDetailDto klineDetailDto);
+ public Result findKlineList(KlineVo klineDetailDto);
}
diff --git a/src/main/java/com/xcong/excoin/modules/symbols/service/impl/SymbolsServiceImpl.java b/src/main/java/com/xcong/excoin/modules/symbols/service/impl/SymbolsServiceImpl.java
index 98af864..cc882f8 100644
--- a/src/main/java/com/xcong/excoin/modules/symbols/service/impl/SymbolsServiceImpl.java
+++ b/src/main/java/com/xcong/excoin/modules/symbols/service/impl/SymbolsServiceImpl.java
@@ -1,12 +1,18 @@
package com.xcong.excoin.modules.symbols.service.impl;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
+import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.huobi.client.model.Candlestick;
import com.xcong.excoin.common.contants.AppContants;
import com.xcong.excoin.common.enumerates.SymbolEnum;
import com.xcong.excoin.common.response.Result;
import com.xcong.excoin.common.system.mapper.CandlestickMapper;
+import com.xcong.excoin.modules.newPrice.ExchangeInfoEnum;
+import com.xcong.excoin.modules.newPrice.ExchangeLoginService;
+import com.xcong.excoin.modules.newPrice.KlineVo;
import com.xcong.excoin.modules.platform.dao.PlatformCnyUsdtExchangeDao;
import com.xcong.excoin.modules.platform.entity.PlatformCnyUsdtExchangeEntity;
import com.xcong.excoin.modules.symbols.parameter.dto.KlineDetailDto;
@@ -14,6 +20,7 @@
import com.xcong.excoin.modules.symbols.parameter.vo.KlineDataVo;
import com.xcong.excoin.modules.symbols.service.SymbolsService;
import com.xcong.excoin.utils.CoinTypeConvert;
+import com.xcong.excoin.utils.MessageSourceUtils;
import com.xcong.excoin.utils.RedisUtils;
import com.xcong.excoin.utils.api.ApiClient;
import com.xcong.excoin.utils.api.response.Kline;
@@ -24,6 +31,8 @@
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
/**
@@ -179,4 +188,25 @@
}
return Result.fail("获取数据失败");
}
+
+ @Override
+ public Result findKlineList(KlineVo klineDetailDto) {
+ Integer type = klineDetailDto.getType();
+ String instId = null;
+ if (AppContants.HOME_SYMBOLS_COIN == type){
+ instId = CoinTypeConvert.convertToCoinKlineKey(klineDetailDto.getInstId());
+ }else if (AppContants.HOME_SYMBOLS_CONTRACT == type){
+ instId = CoinTypeConvert.convertToKlineKey(klineDetailDto.getInstId());
+ }else{
+ return Result.fail("参数错误");
+ }
+ LinkedHashMap<String, Object> requestParam = new LinkedHashMap<>();
+ requestParam.put("instId",instId);
+ String result = ExchangeLoginService.getInstance(ExchangeInfoEnum.OKX_UAT.name()).lineHistory(requestParam);
+ log.info("加载OKX-KLINE,{}", result);
+ JSONObject json = JSON.parseObject(result);
+ String data = json.getString("data");
+ List<String[]> klinesList = JSON.parseArray(data, String[].class);
+ return Result.ok(klinesList);
+ }
}
diff --git a/src/main/java/com/xcong/excoin/utils/CoinTypeConvert.java b/src/main/java/com/xcong/excoin/utils/CoinTypeConvert.java
index b60c94e..d5a5771 100644
--- a/src/main/java/com/xcong/excoin/utils/CoinTypeConvert.java
+++ b/src/main/java/com/xcong/excoin/utils/CoinTypeConvert.java
@@ -53,6 +53,44 @@
}
}
+ public static String convertToCoinKlineKey(String symbol) {
+ switch (symbol) {
+ case "BTC/USDT":
+ return "BTC-USDT";
+ case "ETH/USDT":
+ return "ETH-USDT";
+ case "XRP/USDT":
+ return "XRP-USDT";
+ case "LTC/USDT":
+ return "LTC-USDT";
+ case "BCH/USDT":
+ return "BCH-USDT";
+ case "ETC/USDT":
+ return "ETC-USDT";
+ default:
+ return null;
+ }
+ }
+
+ public static String convertToKlineKey(String symbol) {
+ switch (symbol) {
+ case "BTC/USDT":
+ return "BTC-USDT-SWAP";
+ case "ETH/USDT":
+ return "ETH-USDT-SWAP";
+ case "XRP/USDT":
+ return "XRP-USDT-SWAP";
+ case "LTC/USDT":
+ return "LTC-USDT-SWAP";
+ case "BCH/USDT":
+ return "BCH-USDT-SWAP";
+ case "ETC/USDT":
+ return "ETC-USDT-SWAP";
+ default:
+ return null;
+ }
+ }
+
public static String convertToOpenKey(String symbol) {
--
Gitblit v1.9.1