From 1c56858fbfd4b44a601255a5fc3e969d752fe246 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Fri, 14 Jun 2024 19:19:10 +0900 Subject: [PATCH 1/5] =?UTF-8?q?#68=20[refactor]=20:=20=EC=B0=A8=ED=8A=B8?= =?UTF-8?q?=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EA=B8=B0=EA=B0=84=202018?= =?UTF-8?q?=EB=85=84=EB=B6=80=ED=84=B0=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/dgu/backend/service/CandleInfoUpdater.java | 2 +- backend/src/main/java/org/dgu/backend/util/CandleUtil.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/org/dgu/backend/service/CandleInfoUpdater.java b/backend/src/main/java/org/dgu/backend/service/CandleInfoUpdater.java index 5a1a860..82f1430 100644 --- a/backend/src/main/java/org/dgu/backend/service/CandleInfoUpdater.java +++ b/backend/src/main/java/org/dgu/backend/service/CandleInfoUpdater.java @@ -45,7 +45,7 @@ public void ensureCandleInfoUpToDate(String koreanName, String candleName) { int candleInterval = candleUtil.calculateCandleInterval(candleName); LocalDateTime startDate; if (Objects.isNull(latestCandleInfo)) { - startDate = dateUtil.convertToLocalDateTime("2019-01-01T00:00:00"); + startDate = dateUtil.convertToLocalDateTime("2018-01-01T00:00:00"); } else { startDate = latestCandleInfo.getDateTime(); if (startDate.plusMinutes(candleInterval).isAfter(LocalDateTime.now())) { diff --git a/backend/src/main/java/org/dgu/backend/util/CandleUtil.java b/backend/src/main/java/org/dgu/backend/util/CandleUtil.java index cbbf33c..29096aa 100644 --- a/backend/src/main/java/org/dgu/backend/util/CandleUtil.java +++ b/backend/src/main/java/org/dgu/backend/util/CandleUtil.java @@ -39,7 +39,7 @@ public LocalDateTime getStartDateByCandleName(String candleName) { case "minutes10" -> now.minusMonths(10); case "minutes15" -> now.minusMonths(15); case "minutes30" -> now.minusMonths(30); - default -> LocalDateTime.of(2019, 1, 1, 0, 0); + default -> LocalDateTime.of(2018, 1, 1, 0, 0); }; } From 4fb6c10929156b835de9908ba5cae3b5a2db9b14 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Fri, 14 Jun 2024 20:59:40 +0900 Subject: [PATCH 2/5] =?UTF-8?q?#42=20[chore]=20:=20=EC=97=85=EB=B9=84?= =?UTF-8?q?=ED=8A=B8=20API=20URL=20=ED=99=98=EA=B2=BD=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/dgu/backend/service/CandleInfoServiceImpl.java | 10 +++++++--- .../org/dgu/backend/service/MarketServiceImpl.java | 8 ++++---- backend/src/main/resources/application.yml | 10 +++++++--- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/backend/src/main/java/org/dgu/backend/service/CandleInfoServiceImpl.java b/backend/src/main/java/org/dgu/backend/service/CandleInfoServiceImpl.java index a880dec..1cc38c0 100644 --- a/backend/src/main/java/org/dgu/backend/service/CandleInfoServiceImpl.java +++ b/backend/src/main/java/org/dgu/backend/service/CandleInfoServiceImpl.java @@ -11,6 +11,7 @@ import org.dgu.backend.repository.CandleInfoRepository; import org.dgu.backend.repository.CandleRepository; import org.dgu.backend.repository.MarketRepository; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.*; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; @@ -22,7 +23,10 @@ @Transactional @RequiredArgsConstructor public class CandleInfoServiceImpl implements CandleInfoService { - + @Value("${upbit.url.candle-minute}") + private String UPBIT_URL_CANDLE_MINUTE; + @Value("${upbit.url.candle-etc}") + private String UPBIT_URL_CANDLE_ETC; private final CandleInfoRepository candleInfoRepository; private final MarketRepository marketRepository; private final CandleRepository candleRepository; @@ -39,10 +43,10 @@ public void getCandleInfo(String koreanName, LocalDateTime to, int count, String if (candleName.startsWith("minutes")) { // 분봉인 경우 int unit = Integer.parseInt(candleName.substring(7)); - url = String.format("https://api.upbit.com/v1/candles/%s/%d?market=%s&count=%d", candleName.substring(0,7), unit, marketName, count); + url = String.format(UPBIT_URL_CANDLE_MINUTE, candleName.substring(0,7), unit, marketName, count); } else { // 그 외 (일봉, 주봉, 월봉) - url = String.format("https://api.upbit.com/v1/candles/%s?market=%s&count=%d", candleName, marketName, count); + url = String.format(UPBIT_URL_CANDLE_ETC, candleName, marketName, count); } if (to != null) { diff --git a/backend/src/main/java/org/dgu/backend/service/MarketServiceImpl.java b/backend/src/main/java/org/dgu/backend/service/MarketServiceImpl.java index cfe24af..cb40ee3 100644 --- a/backend/src/main/java/org/dgu/backend/service/MarketServiceImpl.java +++ b/backend/src/main/java/org/dgu/backend/service/MarketServiceImpl.java @@ -5,6 +5,7 @@ import lombok.extern.slf4j.Slf4j; import org.dgu.backend.dto.UpbitDto; import org.dgu.backend.repository.MarketRepository; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.*; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; @@ -14,20 +15,19 @@ @RequiredArgsConstructor @Slf4j public class MarketServiceImpl implements MarketService { - + @Value("${upbit.url.market}") + private String UPBIT_URL_MARKET; private final RestTemplate restTemplate; private final MarketRepository marketRepository; // 모든 가상화폐 데이터를 가져와 저장하는 메서드 @Override public void getAllMarkets() { - String url = "https://api.upbit.com/v1/market/all?isDetails=false"; - HttpHeaders headers = new HttpHeaders(); headers.set("accept", MediaType.APPLICATION_JSON_VALUE); ResponseEntity responseEntity = restTemplate.exchange( - url, + UPBIT_URL_MARKET, HttpMethod.GET, new HttpEntity<>(headers), UpbitDto.MarketResponse[].class diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 9a57ce9..5a1fe35 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -85,6 +85,10 @@ jwt: aes: secret: ${AES_SECRET} -upbit-open-api: - access-key: ${UPBIT_ACCESS_KEY} - secret-key: ${UPBIT_SECRET_KEY} \ No newline at end of file +upbit: + url: + market: ${UPBIT_URL_MARKET} + ticker: ${UPBIT_URL_TICKER} + account: ${UPBIT_URL_ACCOUNT} + candle-minute: ${UPBIT_URL_CANDLE_MINUTE} + candle-etc: ${UPBIT_URL_CANDLE_ETC} \ No newline at end of file From 7825a2508edebb464bbe6aa499237dafcc5969cd Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Fri, 14 Jun 2024 21:00:17 +0900 Subject: [PATCH 3/5] =?UTF-8?q?#42=20[refactor]=20:=20=EC=BD=94=EC=9D=B8?= =?UTF-8?q?=20=EA=B0=9C=EC=88=98,=20=ED=98=84=EC=9E=AC=20=EA=B0=80?= =?UTF-8?q?=EA=B2=A9,=20=EC=83=81=EC=8A=B9=EB=A5=A0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/dgu/backend/domain/UserCoin.java | 14 +++++++++++++- .../java/org/dgu/backend/dto/DashBoardDto.java | 8 +++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/org/dgu/backend/domain/UserCoin.java b/backend/src/main/java/org/dgu/backend/domain/UserCoin.java index 7405ed1..5e07d31 100644 --- a/backend/src/main/java/org/dgu/backend/domain/UserCoin.java +++ b/backend/src/main/java/org/dgu/backend/domain/UserCoin.java @@ -26,13 +26,25 @@ public class UserCoin extends BaseEntity { @Column(name = "coin_name", nullable = false, length = 50) private String coinName; + @Column(name = "coin_count", precision = 20, scale = 10, nullable = false) + private BigDecimal coinCount; + + @Column(name = "price", nullable = false) + private BigDecimal price; + @Column(name = "balance", nullable = false) private BigDecimal balance; + @Column(name = "rate", nullable = false) + private BigDecimal rate; + @Builder - public UserCoin(User user, String coinName, BigDecimal balance) { + public UserCoin(User user, String coinName, BigDecimal coinCount, BigDecimal price, BigDecimal balance, BigDecimal rate) { this.user = user; this.coinName = coinName; + this.coinCount = coinCount; + this.price = price; this.balance = balance; + this.rate = rate; } } \ No newline at end of file diff --git a/backend/src/main/java/org/dgu/backend/dto/DashBoardDto.java b/backend/src/main/java/org/dgu/backend/dto/DashBoardDto.java index 569b40c..10872f5 100644 --- a/backend/src/main/java/org/dgu/backend/dto/DashBoardDto.java +++ b/backend/src/main/java/org/dgu/backend/dto/DashBoardDto.java @@ -33,16 +33,22 @@ public static class UserAccountResponse { public static class UserCoinResponse { private String coinName; @JsonSerialize(using = BigDecimalSerializer.class) - private BigDecimal balance; + private BigDecimal coinCount; @JsonSerialize(using = BigDecimalSerializer.class) private BigDecimal price; + @JsonSerialize(using = BigDecimalSerializer.class) + private BigDecimal balance; private Boolean isIncrease; + private BigDecimal rate; public UserCoin to(User user) { return UserCoin.builder() .user(user) .coinName(coinName) + .coinCount(coinCount) + .price(price) .balance(balance) + .rate(rate) .build(); } } From 55386dac1c624f971a1b2f230081caee70175ea1 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Fri, 14 Jun 2024 21:00:41 +0900 Subject: [PATCH 4/5] =?UTF-8?q?#42=20[rename]=20:=20=EB=B3=80=EC=88=98?= =?UTF-8?q?=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/main/java/org/dgu/backend/dto/UpbitDto.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/org/dgu/backend/dto/UpbitDto.java b/backend/src/main/java/org/dgu/backend/dto/UpbitDto.java index d587105..06ff080 100644 --- a/backend/src/main/java/org/dgu/backend/dto/UpbitDto.java +++ b/backend/src/main/java/org/dgu/backend/dto/UpbitDto.java @@ -84,7 +84,8 @@ public static class CandleInfoResponse { @JsonInclude(JsonInclude.Include.NON_NULL) public static class Account { private String currency; - private BigDecimal balance; + @JsonProperty("balance") + private BigDecimal coinCount; private BigDecimal locked; private BigDecimal avgBuyPrice; private String unitCurrency; From 1e132115656d95287cfbbbcdb2db367ec33b0085 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Fri, 14 Jun 2024 21:01:39 +0900 Subject: [PATCH 5/5] =?UTF-8?q?#42=20[refactor]=20:=20=ED=98=84=EC=9E=AC?= =?UTF-8?q?=EA=B0=80=20=EA=B8=B0=EC=A4=80=EC=9C=BC=EB=A1=9C=20=EA=B3=84?= =?UTF-8?q?=EC=82=B0=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/service/DashBoardServiceImpl.java | 136 ++++++++++-------- 1 file changed, 79 insertions(+), 57 deletions(-) diff --git a/backend/src/main/java/org/dgu/backend/service/DashBoardServiceImpl.java b/backend/src/main/java/org/dgu/backend/service/DashBoardServiceImpl.java index ef5b7dc..04a8dd0 100644 --- a/backend/src/main/java/org/dgu/backend/service/DashBoardServiceImpl.java +++ b/backend/src/main/java/org/dgu/backend/service/DashBoardServiceImpl.java @@ -14,6 +14,7 @@ import org.dgu.backend.repository.UpbitKeyRepository; import org.dgu.backend.repository.UserCoinRepository; import org.dgu.backend.util.JwtUtil; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.*; import org.springframework.stereotype.Service; import org.springframework.web.client.HttpClientErrorException; @@ -30,6 +31,10 @@ @RequiredArgsConstructor @Slf4j public class DashBoardServiceImpl implements DashBoardService { + @Value("${upbit.url.account}") + private String UPBIT_URL_ACCOUNT; + @Value("${upbit.url.ticker}") + private String UPBIT_URL_TICKER; private final RestTemplate restTemplate; private final JwtUtil jwtUtil; private final UpbitKeyRepository upbitKeyRepository; @@ -39,18 +44,9 @@ public class DashBoardServiceImpl implements DashBoardService { @Override public DashBoardDto.UserAccountResponse getUserAccount(String authorizationHeader) { User user = jwtUtil.getUserFromHeader(authorizationHeader); - UpbitKey upbitKey = upbitKeyRepository.findByUser(user); - if (Objects.isNull(upbitKey)) { - throw new UpbitException(UpbitErrorResult.NOT_FOUND_UPBIT_KEY); - } + UpbitDto.Account[] accounts = getUpbitAccounts(user); - String token = jwtUtil.generateUpbitToken(upbitKey); - String url = "https://api.upbit.com/v1/accounts"; - UpbitDto.Account[] responseBody = getUserAccountsAtUpbit(url, token); - if (Objects.isNull(responseBody)) { - throw new UpbitException(UpbitErrorResult.FAIL_ACCESS_USER_ACCOUNT); - } - BigDecimal accountSum = getAccountSum(responseBody); + BigDecimal accountSum = getAccountSum(accounts); return DashBoardDto.UserAccountResponse.builder() .account(accountSum.setScale(3, RoundingMode.HALF_UP)) @@ -61,86 +57,109 @@ public DashBoardDto.UserAccountResponse getUserAccount(String authorizationHeade @Override public List getUserCoins(String authorizationHeader) { User user = jwtUtil.getUserFromHeader(authorizationHeader); - UpbitKey upbitKey = upbitKeyRepository.findByUser(user); - if (Objects.isNull(upbitKey)) { - throw new UpbitException(UpbitErrorResult.NOT_FOUND_UPBIT_KEY); - } + UpbitDto.Account[] accounts = getUpbitAccounts(user); - String token = jwtUtil.generateUpbitToken(upbitKey); - String url = "https://api.upbit.com/v1/accounts"; - UpbitDto.Account[] responseBody = getUserAccountsAtUpbit(url, token); - if (Objects.isNull(responseBody)) { - throw new UpbitException(UpbitErrorResult.FAIL_ACCESS_USER_ACCOUNT); - } + return processUserCoins(accounts, user); + } + // 유저 보유 코인을 처리하는 메서드 + private List processUserCoins(UpbitDto.Account[] accounts, User user) { List userCoinResponses = new ArrayList<>(); - for (UpbitDto.Account account : responseBody) { + for (UpbitDto.Account account : accounts) { String coinName = account.getCurrency(); - if (coinName.equals("KRW")) { - continue; - } - - UserCoin userCoin = userCoinRepository.findByCoinName(account.getCurrency()); - boolean isIncrease = false; - if (!Objects.isNull(userCoin)) { - isIncrease = isBalanceIncreased(account, userCoin); - userCoinRepository.delete(userCoin); + // 현금은 제외 + if (!coinName.equals("KRW")) { + userCoinResponses.add(processSingleCoin(account, user, coinName)); } + } + return userCoinResponses; + } - DashBoardDto.UserCoinResponse userCoinResponse = DashBoardDto.UserCoinResponse.builder() - .coinName(coinName) - .balance(account.getBalance()) - .price(account.getAvgBuyPrice()) - .isIncrease(isIncrease) - .build(); - userCoinResponses.add(userCoinResponse); - - userCoinRepository.save(userCoinResponse.to(user)); + // 단일 코인 정보를 처리하는 메서드 + private DashBoardDto.UserCoinResponse processSingleCoin(UpbitDto.Account account, User user, String coinName) { + coinName = "KRW-" + coinName; + UserCoin userCoin = userCoinRepository.findByCoinName(coinName); + UpbitDto.Ticker[] ticker = getTickerPriceAtUpbit(UPBIT_URL_TICKER + coinName); + BigDecimal curPrice = BigDecimal.valueOf(ticker[0].getPrice()); + BigDecimal curCoinCount = account.getCoinCount(); + boolean isIncrease = false; + BigDecimal rate = BigDecimal.ZERO; + if (!Objects.isNull(userCoin)) { + rate = getCoinPriceIncreaseRate(userCoin, curPrice, curCoinCount); + isIncrease = rate.compareTo(BigDecimal.ZERO) > 0; + userCoinRepository.delete(userCoin); } - return userCoinResponses; + DashBoardDto.UserCoinResponse userCoinResponse = DashBoardDto.UserCoinResponse.builder() + .coinName(coinName) + .coinCount(curCoinCount) + .price(curPrice) + .balance(curPrice.multiply(curCoinCount).setScale(4, RoundingMode.HALF_UP)) + .isIncrease(isIncrease) + .rate(rate) + .build(); + + userCoinRepository.save(userCoinResponse.to(user)); + return userCoinResponse; } // 대표 코인 5개 정보를 반환하는 메서드 @Override public List getRepresentativeCoins() { - String url = "https://api.upbit.com/v1/ticker?markets="; List representativeCoinResponses = new ArrayList<>(); for (Coin coin : Coin.values()) { - UpbitDto.Ticker[] responseBody = getTickerPriceAtUpbit(url + coin.getMarketName()); - if (Objects.isNull(responseBody[0])) { - throw new UpbitException(UpbitErrorResult.FAIL_ACCESS_COIN_INFO); - } - representativeCoinResponses.add(DashBoardDto.RepresentativeCoinResponse.of(responseBody[0], coin.getKoreanName())); + UpbitDto.Ticker[] ticker = getTickerPriceAtUpbit(UPBIT_URL_TICKER + coin.getMarketName()); + representativeCoinResponses.add(DashBoardDto.RepresentativeCoinResponse.of(ticker[0], coin.getKoreanName())); } return representativeCoinResponses; } // 현재 업비트 잔고를 계산하는 메서드 - private BigDecimal getAccountSum(UpbitDto.Account[] responseBody) { + private BigDecimal getAccountSum(UpbitDto.Account[] accounts) { BigDecimal accountSum = BigDecimal.ZERO; - for (UpbitDto.Account account : responseBody) { + for (UpbitDto.Account account : accounts) { if (account.getCurrency().equals("KRW")) { - accountSum = accountSum.add(account.getBalance()); + accountSum = accountSum.add(account.getCoinCount()); } else { - BigDecimal balance = account.getBalance(); - BigDecimal avgBuyPrice = account.getAvgBuyPrice(); - accountSum = accountSum.add(balance.multiply(avgBuyPrice)); + // 현재가를 가져옴 + String coinName = "KRW-" + account.getCurrency(); + UpbitDto.Ticker[] ticker = getTickerPriceAtUpbit(UPBIT_URL_TICKER + coinName); + BigDecimal curPrice = BigDecimal.valueOf(ticker[0].getPrice()); + BigDecimal userCoinCount = account.getCoinCount(); + accountSum = accountSum.add(curPrice.multiply(userCoinCount)); } } return accountSum; } - // 코인 가격 상승 여부를 판단하는 메서드 - private boolean isBalanceIncreased(UpbitDto.Account account, UserCoin userCoin) { + // 기존 대비 코인 가격 상승률을 계산하는 메서드 + private BigDecimal getCoinPriceIncreaseRate(UserCoin userCoin, BigDecimal curPrice, BigDecimal curCoinCount) { + BigDecimal pastValue = userCoin.getPrice().multiply(userCoin.getCoinCount()); + BigDecimal currentValue = curPrice.multiply(curCoinCount); - return account.getBalance().compareTo(userCoin.getBalance()) > 0; + return currentValue.subtract(pastValue) + .divide(pastValue, 6, RoundingMode.HALF_UP) + .multiply(new BigDecimal("100")); + } + + // 유저 업비트 계좌 정보를 조회하는 메서드 + private UpbitDto.Account[] getUpbitAccounts(User user) { + UpbitKey upbitKey = upbitKeyRepository.findByUser(user); + if (Objects.isNull(upbitKey)) { + throw new UpbitException(UpbitErrorResult.NOT_FOUND_UPBIT_KEY); + } + + String token = jwtUtil.generateUpbitToken(upbitKey); + UpbitDto.Account[] responseBody = getUserAccountsAtUpbit(UPBIT_URL_ACCOUNT, token); + if (Objects.isNull(responseBody)) { + throw new UpbitException(UpbitErrorResult.FAIL_ACCESS_USER_ACCOUNT); + } + return responseBody; } // 전체 계좌 조회 업비트 API와 통신하는 메서드 private UpbitDto.Account[] getUserAccountsAtUpbit(String url, String token) { - String authenticationToken = "Bearer " + token; HttpHeaders headers = new HttpHeaders(); headers.set("accept", MediaType.APPLICATION_JSON_VALUE); @@ -175,6 +194,9 @@ private UpbitDto.Ticker[] getTickerPriceAtUpbit(String url) { new HttpEntity<>(headers), UpbitDto.Ticker[].class ); + if (Objects.isNull(responseEntity.getBody()[0])) { + throw new UpbitException(UpbitErrorResult.FAIL_ACCESS_COIN_INFO); + } return responseEntity.getBody(); } catch (HttpClientErrorException e) { if (e.getStatusCode() == HttpStatus.UNAUTHORIZED && e.getResponseBodyAsString().contains("no_authorization_ip")) {