goldenChat base source add

This commit is contained in:
aidev
2026-05-23 15:11:48 +09:00
commit a4ea7762b5
2081 changed files with 1155760 additions and 0 deletions
@@ -0,0 +1,49 @@
package com.goldenchart.upbit;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* 업비트 Open API JWT (query_hash + HS512).
*/
public final class UpbitJwtSigner {
private UpbitJwtSigner() {}
public static String sign(String accessKey, String secretKey, Map<String, String> params) {
String nonce = UUID.randomUUID().toString();
var builder = JWT.create()
.withClaim("access_key", accessKey)
.withClaim("nonce", nonce);
if (params != null && !params.isEmpty()) {
String query = params.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining("&"));
try {
MessageDigest md = MessageDigest.getInstance("SHA-512");
byte[] digest = md.digest(query.getBytes(java.nio.charset.StandardCharsets.UTF_8));
String queryHash = String.format("%0128x", new BigInteger(1, digest));
builder.withClaim("query_hash", queryHash);
builder.withClaim("query_hash_alg", "SHA512");
} catch (Exception e) {
throw new IllegalStateException("query_hash 생성 실패", e);
}
}
return builder.sign(Algorithm.HMAC512(secretKey));
}
/** GET 등 쿼리 파라미터 없는 요청 */
public static String signNoQuery(String accessKey, String secretKey) {
return sign(accessKey, secretKey, null);
}
}
@@ -0,0 +1,136 @@
package com.goldenchart.upbit;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
/**
* 업비트 주문·계좌 REST API (실거래).
*/
@Component
@RequiredArgsConstructor
@Slf4j
public class UpbitOrderApiClient {
private final WebClient.Builder webClientBuilder;
private final ObjectMapper objectMapper;
@Value("${upbit.api.base-url:https://api.upbit.com}")
private String baseUrl;
/**
* 시장가 매수 (KRW 금액 지정).
*/
public JsonNode placeMarketBuy(String accessKey, String secretKey,
String market, double krwAmount) {
Map<String, String> params = new TreeMap<>();
params.put("market", market);
params.put("side", "bid");
params.put("price", String.valueOf((long) Math.floor(krwAmount)));
params.put("ord_type", "price");
return postOrder(accessKey, secretKey, params);
}
/**
* 시장가 매도 (수량 지정).
*/
public JsonNode placeMarketSell(String accessKey, String secretKey,
String market, double volume) {
Map<String, String> params = new TreeMap<>();
params.put("market", market);
params.put("side", "ask");
params.put("volume", formatVolume(volume));
params.put("ord_type", "market");
return postOrder(accessKey, secretKey, params);
}
/** 보유 KRW 잔고 */
public double getKrwBalance(String accessKey, String secretKey) {
JsonNode accounts = getAccounts(accessKey, secretKey);
if (accounts == null || !accounts.isArray()) return 0;
for (JsonNode a : accounts) {
if ("KRW".equals(a.path("currency").asText())) {
return a.path("balance").asDouble(0);
}
}
return 0;
}
/** 종목 보유 수량 */
public double getCoinBalance(String accessKey, String secretKey, String market) {
String currency = market.startsWith("KRW-") ? market.substring(4) : market;
JsonNode accounts = getAccounts(accessKey, secretKey);
if (accounts == null || !accounts.isArray()) return 0;
for (JsonNode a : accounts) {
if (currency.equals(a.path("currency").asText())) {
return a.path("balance").asDouble(0);
}
}
return 0;
}
public JsonNode getAccounts(String accessKey, String secretKey) {
String token = UpbitJwtSigner.signNoQuery(accessKey, secretKey);
try {
String body = webClientBuilder.build()
.get()
.uri(baseUrl + "/v1/accounts")
.header(HttpHeaders.AUTHORIZATION, "Bearer " + token)
.retrieve()
.bodyToMono(String.class)
.block();
return objectMapper.readTree(body);
} catch (WebClientResponseException e) {
log.warn("[UpbitAPI] accounts {}: {}", e.getStatusCode(), e.getResponseBodyAsString());
throw new UpbitApiException(e.getStatusCode().value(), e.getResponseBodyAsString());
} catch (Exception e) {
throw new UpbitApiException(0, e.getMessage());
}
}
private JsonNode postOrder(String accessKey, String secretKey, Map<String, String> params) {
String token = UpbitJwtSigner.sign(accessKey, secretKey, params);
Map<String, Object> body = new LinkedHashMap<>(params);
try {
String response = webClientBuilder.build()
.post()
.uri(baseUrl + "/v1/orders")
.header(HttpHeaders.AUTHORIZATION, "Bearer " + token)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(body)
.retrieve()
.bodyToMono(String.class)
.block();
return objectMapper.readTree(response);
} catch (WebClientResponseException e) {
log.warn("[UpbitAPI] order {}: {}", e.getStatusCode(), e.getResponseBodyAsString());
throw new UpbitApiException(e.getStatusCode().value(), e.getResponseBodyAsString());
} catch (Exception e) {
throw new UpbitApiException(0, e.getMessage());
}
}
private static String formatVolume(double v) {
if (v >= 1) return String.format("%.8f", v).replaceAll("0+$", "").replaceAll("\\.$", "");
return String.format("%.8f", v);
}
public static class UpbitApiException extends RuntimeException {
public final int status;
public UpbitApiException(int status, String message) {
super("Upbit API " + status + ": " + message);
this.status = status;
}
}
}