|
|
@@ -0,0 +1,159 @@
|
|
|
+package top.lvzhiqiang.service.impl;
|
|
|
+
|
|
|
+import com.alibaba.fastjson.JSONArray;
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
+import com.fasterxml.jackson.databind.JsonNode;
|
|
|
+import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
+import com.xxl.job.core.context.XxlJobHelper;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+
|
|
|
+import org.apache.commons.codec.binary.Hex;
|
|
|
+import org.jsoup.Connection;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
+import org.springframework.http.ResponseEntity;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.http.HttpEntity;
|
|
|
+import org.springframework.http.HttpHeaders;
|
|
|
+import org.springframework.http.HttpMethod;
|
|
|
+import org.springframework.util.StopWatch;
|
|
|
+import org.springframework.web.client.RestTemplate;
|
|
|
+
|
|
|
+import top.lvzhiqiang.config.InitRunner;
|
|
|
+import top.lvzhiqiang.entity.CoinBitgetMixFundrate;
|
|
|
+import top.lvzhiqiang.entity.CoinGateSpotOrders;
|
|
|
+import top.lvzhiqiang.mapper.CoinBitgetMapper;
|
|
|
+import top.lvzhiqiang.mapper.CoinGateMapper;
|
|
|
+import top.lvzhiqiang.service.CoinBitgetService;
|
|
|
+import top.lvzhiqiang.service.CoinGateService;
|
|
|
+import top.lvzhiqiang.util.JsoupUtil;
|
|
|
+
|
|
|
+import javax.annotation.Resource;
|
|
|
+import javax.crypto.Mac;
|
|
|
+import javax.crypto.spec.SecretKeySpec;
|
|
|
+
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.net.URI;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+import java.security.MessageDigest;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.List;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+import java.util.stream.Stream;
|
|
|
+
|
|
|
+/**
|
|
|
+ * Coin Gate ServiceImpl
|
|
|
+ *
|
|
|
+ * @author lvzhiqiang
|
|
|
+ * 2026/3/13 15:33
|
|
|
+ */
|
|
|
+@Service
|
|
|
+@Slf4j
|
|
|
+public class CoinGateServiceImpl implements CoinGateService {
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private CoinGateMapper coinGateMapper;
|
|
|
+
|
|
|
+ private final RestTemplate restTemplate;
|
|
|
+ private final ObjectMapper objectMapper;
|
|
|
+
|
|
|
+ public CoinGateServiceImpl(RestTemplate restTemplate, ObjectMapper objectMapper) {
|
|
|
+ this.restTemplate = restTemplate;
|
|
|
+ this.objectMapper = objectMapper;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 同步现货订单列表
|
|
|
+ *
|
|
|
+ * @return com.xxl.job.core.biz.model.ReturnT<java.lang.String>
|
|
|
+ * @author lvzhiqiang
|
|
|
+ * 2026/3/13 15:33
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void syncSpotOrders() {
|
|
|
+ String apiKey = InitRunner.dicCodeMap.get("gateio_api_key").getCodeValue();
|
|
|
+ String apiSecret = InitRunner.dicCodeMap.get("gateio_api_secret").getCodeValue();
|
|
|
+ String host = InitRunner.dicCodeMap.get("gateio_host").getCodeValue();
|
|
|
+ String prefix = InitRunner.dicCodeMap.get("gateio_prefix").getCodeValue();
|
|
|
+
|
|
|
+ String path = "/spot/orders";
|
|
|
+ // 关键:API 要求必须带上状态参数查历史记录
|
|
|
+ String query = "currency_pair=" + "&status=finished";
|
|
|
+ String url = host + prefix + path + "?" + query;
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 1. 生成鉴权 Headers
|
|
|
+ HttpHeaders headers = generateAuthHeaders("GET", prefix + path, query, "", apiKey, apiSecret);
|
|
|
+
|
|
|
+ // 2. 发起 HTTP 请求
|
|
|
+ HttpEntity<String> entity = new HttpEntity<>(null, headers);
|
|
|
+ ResponseEntity<String> response = restTemplate.exchange(URI.create(url), HttpMethod.GET, entity, String.class);
|
|
|
+
|
|
|
+ // 3. 解析 JSON 响应
|
|
|
+ JsonNode rootNode = objectMapper.readTree(response.getBody());
|
|
|
+ List<CoinGateSpotOrders> orderList = new ArrayList<>();
|
|
|
+
|
|
|
+ for (JsonNode node : rootNode) {
|
|
|
+ CoinGateSpotOrders order = new CoinGateSpotOrders();
|
|
|
+ order.setId(node.get("id").asText());
|
|
|
+ order.setCurrencyPair(node.get("currency_pair").asText());
|
|
|
+ order.setSide(node.get("side").asText());
|
|
|
+ order.setType(node.get("type").asText());
|
|
|
+ order.setAmount(new BigDecimal(node.get("amount").asText()));
|
|
|
+ order.setPrice(new BigDecimal(node.get("price").asText()));
|
|
|
+ order.setFilledAmount(new BigDecimal(node.get("filled_amount").asText()));
|
|
|
+ order.setAvgDealPrice(new BigDecimal(node.get("avg_deal_price").asText()));
|
|
|
+ order.setFilledTotal(new BigDecimal(node.get("filled_total").asText()));
|
|
|
+ order.setFee(new BigDecimal(node.get("fee").asText()));
|
|
|
+ order.setFeeCurrency(node.get("fee_currency").asText());
|
|
|
+ order.setStatus(node.get("status").asText());
|
|
|
+ order.setFinishAs(node.get("finish_as").asText());
|
|
|
+ order.setCreateTimeMs(node.get("create_time_ms").asLong());
|
|
|
+ order.setUpdateTimeMs(node.get("update_time_ms").asLong());
|
|
|
+ orderList.add(order);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 批量保存到 MySQL (由于设置了 ID,JPA 会自动执行 Upsert 操作)
|
|
|
+ if (!orderList.isEmpty()) {
|
|
|
+ coinGateMapper.batchUpsert(orderList);
|
|
|
+ log.info("成功同步 {} 条 {} 订单记录到数据库。", orderList.size(), "Gate.io");
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("同步 Gate.io 订单失败: " + e.getMessage(), e);
|
|
|
+ // 生产环境建议使用 log.error() 并对接报警系统
|
|
|
+ throw new RuntimeException("订单同步异常", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 核心鉴权逻辑:生成 Gate.io APIv4 要求的签名 Header
|
|
|
+ */
|
|
|
+ private HttpHeaders generateAuthHeaders(String method, String url, String queryString, String bodyString, String apiKey, String apiSecret) throws Exception {
|
|
|
+ // 1. 生成 body 的 SHA512 哈希 (GET 请求通常为空字符串的哈希)
|
|
|
+ MessageDigest md = MessageDigest.getInstance("SHA-512");
|
|
|
+ md.update(bodyString.getBytes(StandardCharsets.UTF_8));
|
|
|
+ String hashedPayload = Hex.encodeHexString(md.digest());
|
|
|
+
|
|
|
+ // 2. 构造签名字符串
|
|
|
+ String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
|
|
|
+ String signatureString = method + "\n" +
|
|
|
+ url + "\n" +
|
|
|
+ (queryString == null ? "" : queryString) + "\n" +
|
|
|
+ hashedPayload + "\n" +
|
|
|
+ timestamp;
|
|
|
+
|
|
|
+ // 3. 使用 API Secret 进行 HMAC-SHA512 加密
|
|
|
+ Mac hmacSha512 = Mac.getInstance("HmacSHA512");
|
|
|
+ SecretKeySpec secretKeySpec = new SecretKeySpec(apiSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA512");
|
|
|
+ hmacSha512.init(secretKeySpec);
|
|
|
+ String signature = Hex.encodeHexString(hmacSha512.doFinal(signatureString.getBytes(StandardCharsets.UTF_8)));
|
|
|
+
|
|
|
+ // 4. 组装 Spring HttpHeaders
|
|
|
+ HttpHeaders headers = new HttpHeaders();
|
|
|
+ headers.set("KEY", apiKey);
|
|
|
+ headers.set("Timestamp", timestamp);
|
|
|
+ headers.set("SIGN", signature);
|
|
|
+ headers.set("Content-Type", "application/json");
|
|
|
+
|
|
|
+ return headers;
|
|
|
+ }
|
|
|
+}
|