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;
|
}
|
}
|