From 319517af6acf49a0ddd5d84db5e8d89c29d68a6f Mon Sep 17 00:00:00 2001
From: Administrator <15274802129@163.com>
Date: Wed, 08 Jul 2026 11:49:37 +0800
Subject: [PATCH] fix(gateApi): 统一返回成功状态的数据格式

---
 src/main/java/com/xcong/excoin/modules/gateApi/GateConfigPersistenceService.java |  135 +++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 135 insertions(+), 0 deletions(-)

diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/GateConfigPersistenceService.java b/src/main/java/com/xcong/excoin/modules/gateApi/GateConfigPersistenceService.java
new file mode 100644
index 0000000..480b87c
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/gateApi/GateConfigPersistenceService.java
@@ -0,0 +1,135 @@
+package com.xcong.excoin.modules.gateApi;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import java.io.File;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * Gate 配置持久化服务 — 按 apiKey 分文件存储。
+ * 文件路径: config/gate-config-{md5(apiKey)}.json
+ */
+@Slf4j
+@Service
+public class GateConfigPersistenceService {
+
+    private static final String CONFIG_DIR = "config";
+    private static final String CONFIG_PREFIX = "gate-config-";
+    private static final String JSON_SUFFIX = ".json";
+
+    private final ObjectMapper mapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
+
+    @PostConstruct
+    public void ensureConfigDir() {
+        File dir = new File(CONFIG_DIR);
+        if (!dir.exists()) {
+            dir.mkdirs();
+        }
+    }
+
+    /**
+     * 确保指定 apiKey 的配置文件存在,不存在则用默认值创建。
+     */
+    public GateConfigDTO ensureExists(String apiKey, GateConfigDTO defaults) {
+        GateConfigDTO existing = load(apiKey);
+        if (existing != null) {
+            return existing;
+        }
+        save(apiKey, defaults);
+        log.info("[GateConfig] 为 apiKey={} 创建默认配置文件", maskKey(apiKey));
+        return defaults;
+    }
+
+    /**
+     * 保存配置。
+     */
+    public void save(String apiKey, GateConfigDTO dto) {
+        try {
+            mapper.writeValue(configFile(apiKey), dto);
+            log.info("[GateConfig] 配置已保存, apiKey={}", maskKey(apiKey));
+        } catch (IOException e) {
+            log.error("[GateConfig] 保存配置失败, apiKey={}", maskKey(apiKey), e);
+            throw new RuntimeException("保存配置失败", e);
+        }
+    }
+
+    /**
+     * 加载配置,文件不存在返回 null。
+     */
+    public GateConfigDTO load(String apiKey) {
+        File file = configFile(apiKey);
+        if (!file.exists()) {
+            return null;
+        }
+        try {
+            return mapper.readValue(file, GateConfigDTO.class);
+        } catch (IOException e) {
+            log.error("[GateConfig] 读取配置失败, apiKey={}", maskKey(apiKey), e);
+            return null;
+        }
+    }
+
+    /**
+     * 从 DTO 构建 GateConfig(补齐 apiSecret)。
+     */
+    public GateConfig buildFromDTO(GateConfigDTO dto, String apiKey) {
+        return GateConfig.builder()
+                .apiKey(apiKey)
+                .apiSecret(GateWebSocketClientManager.DEFAULT_API_SECRET)
+                // 不可调参数 — 参考 GateWebSocketClientManager 顶部常量
+                .contract(GateWebSocketClientManager.DEFAULT_CONTRACT)
+                .leverage(GateWebSocketClientManager.DEFAULT_LEVERAGE)
+                .marginMode(GateWebSocketClientManager.DEFAULT_MARGIN_MODE)
+                .positionMode(GateWebSocketClientManager.DEFAULT_POSITION_MODE)
+                .restartGridSpan(GateWebSocketClientManager.DEFAULT_RESTART_GRID_SPAN)
+                .priceScale(GateWebSocketClientManager.DEFAULT_PRICE_SCALE)
+                .contractMultiplier(new BigDecimal(GateWebSocketClientManager.DEFAULT_CONTRACT_MULTIPLIER))
+                .unrealizedPnlPriceMode(GateConfig.PnLPriceMode.valueOf(GateWebSocketClientManager.DEFAULT_PNL_MODE))
+                .isProduction(true)
+                .reopenMaxRetries(GateWebSocketClientManager.DEFAULT_REOPEN_MAX_RETRIES)
+                // 可调参数 — 从 DTO 读取
+                .gridRate(nvl(dto.getGridRate(), new BigDecimal("0.005")))
+                .expectedProfit(nvl(dto.getExpectedProfit(), new BigDecimal("0.15")))
+                .maxLoss(nvl(dto.getMaxLoss(), new BigDecimal("1.5")))
+                .baseQuantity(nvl(dto.getBaseQuantity(), "2"))
+                .quantity(nvl(dto.getQuantity(), "2"))
+                .maxPositionSize(dto.getMaxPositionSize())
+                .stopLossCount(dto.getStopLossCount())
+                .build();
+    }
+
+    private File configFile(String apiKey) {
+        return new File(CONFIG_DIR, CONFIG_PREFIX + md5(apiKey) + JSON_SUFFIX);
+    }
+
+    private static String md5(String input) {
+        try {
+            MessageDigest md = MessageDigest.getInstance("MD5");
+            byte[] digest = md.digest(input.getBytes(StandardCharsets.UTF_8));
+            StringBuilder sb = new StringBuilder();
+            for (byte b : digest) {
+                sb.append(String.format("%02x", b));
+            }
+            return sb.toString();
+        } catch (NoSuchAlgorithmException e) {
+            return Integer.toHexString(input.hashCode());
+        }
+    }
+
+    private static String maskKey(String key) {
+        if (key == null || key.length() <= 8) return "***";
+        return key.substring(0, 4) + "****" + key.substring(key.length() - 4);
+    }
+
+    private static <T> T nvl(T value, T fallback) {
+        return value != null ? value : fallback;
+    }
+}

--
Gitblit v1.9.1