From a86b5d2a0c5d0c635a23eb44b1edbc8294227f99 Mon Sep 17 00:00:00 2001 From: yonghwankim-dev Date: Mon, 4 Dec 2023 15:47:32 +0900 Subject: [PATCH 01/12] =?UTF-8?q?#33=20fix:=20=EB=A1=9C=EA=B9=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/codesquad/fineants/spring/aop/SimpleLogAop.java | 3 +-- .../api/portfolio_stock/PortfolioStockRestController.java | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/codesquad/fineants/spring/aop/SimpleLogAop.java b/src/main/java/codesquad/fineants/spring/aop/SimpleLogAop.java index e2a62b3a0..e5629f100 100644 --- a/src/main/java/codesquad/fineants/spring/aop/SimpleLogAop.java +++ b/src/main/java/codesquad/fineants/spring/aop/SimpleLogAop.java @@ -10,13 +10,12 @@ import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; -import org.springframework.stereotype.Component; import lombok.extern.slf4j.Slf4j; @Slf4j @Aspect -@Component +// @Component public class SimpleLogAop { @Pointcut("execution(* codesquad.fineants..*.*(..))") diff --git a/src/main/java/codesquad/fineants/spring/api/portfolio_stock/PortfolioStockRestController.java b/src/main/java/codesquad/fineants/spring/api/portfolio_stock/PortfolioStockRestController.java index 8f62b3377..ec28a8f6d 100644 --- a/src/main/java/codesquad/fineants/spring/api/portfolio_stock/PortfolioStockRestController.java +++ b/src/main/java/codesquad/fineants/spring/api/portfolio_stock/PortfolioStockRestController.java @@ -65,7 +65,9 @@ public SseEmitter readMyPortfolioStocks(@PathVariable Long portfolioId) { emitter.onTimeout(emitter::complete); // 장시간 동안에는 스케줄러를 이용하여 지속적 응답 - if (stockMarketChecker.isMarketOpen(LocalDateTime.now())) { + LocalDateTime now = LocalDateTime.now(); + log.info("now : {}", now); + if (stockMarketChecker.isMarketOpen(now)) { scheduleSseEventTask(portfolioId, emitter, false); } else { scheduleSseEventTask(portfolioId, emitter, true); From 0c185c282c2b3cdb2e2836cc008c418651cdefb5 Mon Sep 17 00:00:00 2001 From: yonghwankim-dev Date: Mon, 4 Dec 2023 16:01:02 +0900 Subject: [PATCH 02/12] =?UTF-8?q?#33=20fix:=20=EB=A1=9C=EA=B9=85=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20SSL=20=EC=9E=84=EC=8B=9C=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/portfolio_stock/PortfolioStockRestController.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/codesquad/fineants/spring/api/portfolio_stock/PortfolioStockRestController.java b/src/main/java/codesquad/fineants/spring/api/portfolio_stock/PortfolioStockRestController.java index ec28a8f6d..8f62b3377 100644 --- a/src/main/java/codesquad/fineants/spring/api/portfolio_stock/PortfolioStockRestController.java +++ b/src/main/java/codesquad/fineants/spring/api/portfolio_stock/PortfolioStockRestController.java @@ -65,9 +65,7 @@ public SseEmitter readMyPortfolioStocks(@PathVariable Long portfolioId) { emitter.onTimeout(emitter::complete); // 장시간 동안에는 스케줄러를 이용하여 지속적 응답 - LocalDateTime now = LocalDateTime.now(); - log.info("now : {}", now); - if (stockMarketChecker.isMarketOpen(now)) { + if (stockMarketChecker.isMarketOpen(LocalDateTime.now())) { scheduleSseEventTask(portfolioId, emitter, false); } else { scheduleSseEventTask(portfolioId, emitter, true); From ebeb4041b6a4fa79229df7a34b2a62cb24c38b2b Mon Sep 17 00:00:00 2001 From: yonghwankim-dev Date: Mon, 4 Dec 2023 16:02:29 +0900 Subject: [PATCH 03/12] =?UTF-8?q?#33=20fix:=20=EB=A1=9C=EA=B9=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- secret | 2 +- .../api/portfolio_stock/PortfolioStockRestController.java | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/secret b/secret index 7dfc30eaa..4220334f1 160000 --- a/secret +++ b/secret @@ -1 +1 @@ -Subproject commit 7dfc30eaa00a4efb5d41841f241dc356c3cfa37a +Subproject commit 4220334f1eeda580f5488e7f3cd07ad572392b25 diff --git a/src/main/java/codesquad/fineants/spring/api/portfolio_stock/PortfolioStockRestController.java b/src/main/java/codesquad/fineants/spring/api/portfolio_stock/PortfolioStockRestController.java index 8f62b3377..199151086 100644 --- a/src/main/java/codesquad/fineants/spring/api/portfolio_stock/PortfolioStockRestController.java +++ b/src/main/java/codesquad/fineants/spring/api/portfolio_stock/PortfolioStockRestController.java @@ -85,6 +85,7 @@ private void scheduleSseEventTask(Long portfolioId, SseEmitter emitter, boolean .data("sse complete") .name("complete")); emitter.complete(); + log.info("emitter complete"); } } catch (IOException | FineAntsException e) { log.error(e.getMessage()); @@ -92,8 +93,10 @@ private void scheduleSseEventTask(Long portfolioId, SseEmitter emitter, boolean } }; if (isComplete) { + log.info("sseExecutor.schedule"); sseExecutor.schedule(task, 0, TimeUnit.SECONDS); } else { + log.info("sseExecutor.scheduleAtFixedRate"); sseExecutor.scheduleAtFixedRate(task, 0, 5L, TimeUnit.SECONDS); } } From 8d1f12882c852e427efc3ed416e37044e3f28827 Mon Sep 17 00:00:00 2001 From: yonghwankim-dev Date: Mon, 4 Dec 2023 16:23:55 +0900 Subject: [PATCH 04/12] =?UTF-8?q?#33=20fix:=20SSL=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=ED=95=B4=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- secret | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/secret b/secret index 4220334f1..969fc18dc 160000 --- a/secret +++ b/secret @@ -1 +1 @@ -Subproject commit 4220334f1eeda580f5488e7f3cd07ad572392b25 +Subproject commit 969fc18dc4c7c5a806ec813d3fca0a7787ef1ea1 From bb2effc72a6020df7b7cb8c8278e66e7ecab9f87 Mon Sep 17 00:00:00 2001 From: yonghwankim-dev Date: Mon, 4 Dec 2023 16:32:11 +0900 Subject: [PATCH 05/12] =?UTF-8?q?#33=20fix:=20SSL=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=ED=95=B4=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- secret | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/secret b/secret index 969fc18dc..95d7c60c8 160000 --- a/secret +++ b/secret @@ -1 +1 @@ -Subproject commit 969fc18dc4c7c5a806ec813d3fca0a7787ef1ea1 +Subproject commit 95d7c60c82ea31e280dc7eb9e917dfda5b4e3f02 From 4a25c7ece5058c5cd6aa96c97c2e9cd525fc32d0 Mon Sep 17 00:00:00 2001 From: yonghwankim-dev Date: Mon, 4 Dec 2023 17:16:38 +0900 Subject: [PATCH 06/12] =?UTF-8?q?#33=20fix:=20emitter=20=EC=8B=9C=EA=B0=84?= =?UTF-8?q?=2030=EC=B4=88=EB=A1=9C=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/portfolio_stock/PortfolioStockRestController.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/codesquad/fineants/spring/api/portfolio_stock/PortfolioStockRestController.java b/src/main/java/codesquad/fineants/spring/api/portfolio_stock/PortfolioStockRestController.java index 199151086..c9c25474f 100644 --- a/src/main/java/codesquad/fineants/spring/api/portfolio_stock/PortfolioStockRestController.java +++ b/src/main/java/codesquad/fineants/spring/api/portfolio_stock/PortfolioStockRestController.java @@ -1,7 +1,6 @@ package codesquad.fineants.spring.api.portfolio_stock; import java.io.IOException; -import java.time.Duration; import java.time.LocalDateTime; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -61,7 +60,7 @@ public ApiResponse deletePortfolioStock(@PathVariable Long portfolioId, @GetMapping public SseEmitter readMyPortfolioStocks(@PathVariable Long portfolioId) { - SseEmitter emitter = new SseEmitter(Duration.ofHours(10L).toMillis()); + SseEmitter emitter = new SseEmitter(1000L * 30); emitter.onTimeout(emitter::complete); // 장시간 동안에는 스케줄러를 이용하여 지속적 응답 @@ -81,6 +80,7 @@ private void scheduleSseEventTask(Long portfolioId, SseEmitter emitter, boolean .name("sse event - myPortfolioStocks")); log.info("send message"); if (isComplete) { + Thread.sleep(3000L); emitter.send(SseEmitter.event() .data("sse complete") .name("complete")); @@ -90,6 +90,8 @@ private void scheduleSseEventTask(Long portfolioId, SseEmitter emitter, boolean } catch (IOException | FineAntsException e) { log.error(e.getMessage()); emitter.completeWithError(e); + } catch (InterruptedException e) { + emitter.completeWithError(e); } }; if (isComplete) { From edcff8496fa6e816a7690c0a42495084f97b0f1a Mon Sep 17 00:00:00 2001 From: yonghwankim-dev Date: Mon, 4 Dec 2023 17:33:39 +0900 Subject: [PATCH 07/12] =?UTF-8?q?#33=20fix:=20=EC=93=B0=EB=A0=88=EB=93=9C?= =?UTF-8?q?=ED=92=80=EC=9D=84=20=EC=82=AC=EC=9A=A9=ED=95=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EA=B3=A0=20=EB=B0=94=EB=A1=9C=20complete=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PortfolioStockRestController.java | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/main/java/codesquad/fineants/spring/api/portfolio_stock/PortfolioStockRestController.java b/src/main/java/codesquad/fineants/spring/api/portfolio_stock/PortfolioStockRestController.java index c9c25474f..499e63bc6 100644 --- a/src/main/java/codesquad/fineants/spring/api/portfolio_stock/PortfolioStockRestController.java +++ b/src/main/java/codesquad/fineants/spring/api/portfolio_stock/PortfolioStockRestController.java @@ -1,7 +1,6 @@ package codesquad.fineants.spring.api.portfolio_stock; import java.io.IOException; -import java.time.LocalDateTime; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -63,12 +62,22 @@ public SseEmitter readMyPortfolioStocks(@PathVariable Long portfolioId) { SseEmitter emitter = new SseEmitter(1000L * 30); emitter.onTimeout(emitter::complete); - // 장시간 동안에는 스케줄러를 이용하여 지속적 응답 - if (stockMarketChecker.isMarketOpen(LocalDateTime.now())) { - scheduleSseEventTask(portfolioId, emitter, false); - } else { - scheduleSseEventTask(portfolioId, emitter, true); + try { + emitter.send(SseEmitter.event() + .data(portfolioStockService.readMyPortfolioStocks(portfolioId, lastDayClosingPriceManager)) + .name("sse event - myPortfolioStocks")); + emitter.complete(); + } catch (IOException e) { + log.error(e.getMessage(), e); + emitter.completeWithError(e); } + + // 장시간 동안에는 스케줄러를 이용하여 지속적 응답 + // if (stockMarketChecker.isMarketOpen(LocalDateTime.now())) { + // scheduleSseEventTask(portfolioId, emitter, false); + // } else { + // scheduleSseEventTask(portfolioId, emitter, true); + // } return emitter; } From 680c76c359a8e9b6ae52d313523172ed11b12180 Mon Sep 17 00:00:00 2001 From: yonghwankim-dev Date: Mon, 4 Dec 2023 23:44:52 +0900 Subject: [PATCH 08/12] =?UTF-8?q?#33=20fix:=20=EC=A2=85=EA=B0=80=20?= =?UTF-8?q?=EA=B0=B1=EC=8B=A0=ED=95=98=EB=8A=94=20=EB=B6=80=EB=B6=84=20?= =?UTF-8?q?=EB=A1=9C=EA=B9=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/codesquad/fineants/spring/api/kis/KisService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/codesquad/fineants/spring/api/kis/KisService.java b/src/main/java/codesquad/fineants/spring/api/kis/KisService.java index 2508a1da2..e9e759aa3 100644 --- a/src/main/java/codesquad/fineants/spring/api/kis/KisService.java +++ b/src/main/java/codesquad/fineants/spring/api/kis/KisService.java @@ -141,6 +141,7 @@ private void refreshLastDayClosingPrice(List tickerSymbols) { .collect(Collectors.toList()); futures.parallelStream() .map(CompletableFuture::join) + .peek(response -> log.info("종가 갱신 응답 : {}", response)) .filter(Objects::nonNull) .forEach(response -> lastDayClosingPriceManager.addPrice(response.getTickerSymbol(), response.getPrice())); } From 146b63622510164a6bf09de337f4bf9dd11046ed Mon Sep 17 00:00:00 2001 From: yonghwankim-dev Date: Tue, 5 Dec 2023 12:46:11 +0900 Subject: [PATCH 09/12] =?UTF-8?q?#33=20fix:=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=ED=95=B4=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PortfolioStockRestController.java | 34 ++++++------------- 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/src/main/java/codesquad/fineants/spring/api/portfolio_stock/PortfolioStockRestController.java b/src/main/java/codesquad/fineants/spring/api/portfolio_stock/PortfolioStockRestController.java index 499e63bc6..cafa5b64d 100644 --- a/src/main/java/codesquad/fineants/spring/api/portfolio_stock/PortfolioStockRestController.java +++ b/src/main/java/codesquad/fineants/spring/api/portfolio_stock/PortfolioStockRestController.java @@ -1,6 +1,7 @@ package codesquad.fineants.spring.api.portfolio_stock; -import java.io.IOException; +import java.time.Duration; +import java.time.LocalDateTime; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -20,7 +21,6 @@ import codesquad.fineants.domain.oauth.support.AuthMember; import codesquad.fineants.domain.oauth.support.AuthPrincipalMember; -import codesquad.fineants.spring.api.errors.exception.FineAntsException; import codesquad.fineants.spring.api.kis.manager.LastDayClosingPriceManager; import codesquad.fineants.spring.api.portfolio_stock.request.PortfolioStockCreateRequest; import codesquad.fineants.spring.api.response.ApiResponse; @@ -59,25 +59,15 @@ public ApiResponse deletePortfolioStock(@PathVariable Long portfolioId, @GetMapping public SseEmitter readMyPortfolioStocks(@PathVariable Long portfolioId) { - SseEmitter emitter = new SseEmitter(1000L * 30); + SseEmitter emitter = new SseEmitter(Duration.ofHours(10).toMillis()); emitter.onTimeout(emitter::complete); - try { - emitter.send(SseEmitter.event() - .data(portfolioStockService.readMyPortfolioStocks(portfolioId, lastDayClosingPriceManager)) - .name("sse event - myPortfolioStocks")); - emitter.complete(); - } catch (IOException e) { - log.error(e.getMessage(), e); - emitter.completeWithError(e); - } - // 장시간 동안에는 스케줄러를 이용하여 지속적 응답 - // if (stockMarketChecker.isMarketOpen(LocalDateTime.now())) { - // scheduleSseEventTask(portfolioId, emitter, false); - // } else { - // scheduleSseEventTask(portfolioId, emitter, true); - // } + if (stockMarketChecker.isMarketOpen(LocalDateTime.now())) { + scheduleSseEventTask(portfolioId, emitter, false); + } else { + scheduleSseEventTask(portfolioId, emitter, true); + } return emitter; } @@ -89,25 +79,21 @@ private void scheduleSseEventTask(Long portfolioId, SseEmitter emitter, boolean .name("sse event - myPortfolioStocks")); log.info("send message"); if (isComplete) { - Thread.sleep(3000L); + Thread.sleep(2000L); // sse event - myPortfolioStocks 메시지와 전송 간격 emitter.send(SseEmitter.event() .data("sse complete") .name("complete")); emitter.complete(); log.info("emitter complete"); } - } catch (IOException | FineAntsException e) { + } catch (Exception e) { log.error(e.getMessage()); emitter.completeWithError(e); - } catch (InterruptedException e) { - emitter.completeWithError(e); } }; if (isComplete) { - log.info("sseExecutor.schedule"); sseExecutor.schedule(task, 0, TimeUnit.SECONDS); } else { - log.info("sseExecutor.scheduleAtFixedRate"); sseExecutor.scheduleAtFixedRate(task, 0, 5L, TimeUnit.SECONDS); } } From 89783f1c0134ae5fd7528a397034620afc6c6766 Mon Sep 17 00:00:00 2001 From: yonghwankim-dev Date: Tue, 5 Dec 2023 12:46:38 +0900 Subject: [PATCH 10/12] =?UTF-8?q?#33=20fix:=20=EC=93=B0=EB=A0=88=EB=93=9C?= =?UTF-8?q?=ED=92=80=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PortFolioHoldingRepository.java | 3 + .../fineants/spring/api/kis/KisService.java | 108 ++++++++---------- .../spring/api/kis/aop/AccessTokenAspect.java | 10 +- .../spring/api/kis/client/KisClient.java | 1 - .../manager/LastDayClosingPriceManager.java | 2 +- .../api/portfolio/PortFolioService.java | 2 +- 6 files changed, 53 insertions(+), 73 deletions(-) diff --git a/src/main/java/codesquad/fineants/domain/portfolio_holding/PortFolioHoldingRepository.java b/src/main/java/codesquad/fineants/domain/portfolio_holding/PortFolioHoldingRepository.java index df9f8da0f..7b8e5a59e 100644 --- a/src/main/java/codesquad/fineants/domain/portfolio_holding/PortFolioHoldingRepository.java +++ b/src/main/java/codesquad/fineants/domain/portfolio_holding/PortFolioHoldingRepository.java @@ -3,6 +3,7 @@ import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import codesquad.fineants.domain.portfolio.Portfolio; @@ -12,4 +13,6 @@ public interface PortFolioHoldingRepository extends JpaRepository findAllByPortfolio(Portfolio portfolio); + @Query("select distinct s.tickerSymbol from PortfolioHolding p inner join Stock s on p.stock.tickerSymbol = s.tickerSymbol") + List findAllTickerSymbol(); } diff --git a/src/main/java/codesquad/fineants/spring/api/kis/KisService.java b/src/main/java/codesquad/fineants/spring/api/kis/KisService.java index e9e759aa3..26b9b8d1d 100644 --- a/src/main/java/codesquad/fineants/spring/api/kis/KisService.java +++ b/src/main/java/codesquad/fineants/spring/api/kis/KisService.java @@ -1,7 +1,6 @@ package codesquad.fineants.spring.api.kis; import java.time.LocalDateTime; -import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.concurrent.CompletableFuture; @@ -14,12 +13,8 @@ import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import codesquad.fineants.domain.portfolio.Portfolio; -import codesquad.fineants.domain.portfolio.PortfolioRepository; -import codesquad.fineants.domain.portfolio_holding.PortfolioHolding; -import codesquad.fineants.domain.stock.Stock; +import codesquad.fineants.domain.portfolio_holding.PortFolioHoldingRepository; import codesquad.fineants.spring.api.kis.client.KisClient; import codesquad.fineants.spring.api.kis.manager.CurrentPriceManager; import codesquad.fineants.spring.api.kis.manager.KisAccessTokenManager; @@ -37,10 +32,10 @@ @Service public class KisService { private static final String SUBSCRIBE_PORTFOLIO_HOLDING_FORMAT = "/sub/portfolio/%d"; - private static final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5); + private static final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1); private final KisClient kisClient; - private final PortfolioRepository portfolioRepository; + private final PortFolioHoldingRepository portFolioHoldingRepository; private final SimpMessagingTemplate messagingTemplate; private final PortfolioStockService portfolioStockService; private final KisAccessTokenManager manager; @@ -53,22 +48,11 @@ public class KisService { return thread; }); - // 제약조건 : kis 서버에 1초당 최대 5건, TR간격 0.1초 이하면 안됨 - public CurrentPriceResponse readRealTimeCurrentPrice(String tickerSymbol) { - long currentPrice = kisClient.readRealTimeCurrentPrice(tickerSymbol, manager.createAuthorization()); - log.info("tickerSymbol={}, currentPrice={}, time={}", tickerSymbol, currentPrice, LocalDateTime.now()); - return new CurrentPriceResponse(tickerSymbol, currentPrice); - } - public void addPortfolioSubscription(String sessionId, PortfolioSubscription subscription) { - addTickerSymbols(subscription.getTickerSymbols()); - portfolioSubscriptionManager.addPortfolioSubscription(sessionId, subscription); - } - - public void addTickerSymbols(List tickerSymbols) { - tickerSymbols.stream() + subscription.getTickerSymbols().stream() .filter(tickerSymbol -> !currentPriceManager.hasKey(tickerSymbol)) .forEach(currentPriceManager::addKey); + portfolioSubscriptionManager.addPortfolioSubscription(sessionId, subscription); } public void removePortfolioSubscription(String sessionId) { @@ -76,7 +60,7 @@ public void removePortfolioSubscription(String sessionId) { } @Scheduled(fixedRate = 5, timeUnit = TimeUnit.SECONDS) - public void publishPortfolioDetail() { + protected void publishPortfolioDetail() { List> futures = portfolioSubscriptionManager.values() .parallelStream() .filter(this::hasAllCurrentPrice) @@ -110,26 +94,50 @@ private boolean hasAllCurrentPrice(PortfolioSubscription subscription) { .allMatch(currentPriceManager::hasCurrentPrice); } + // 종목 가격정보 갱신 @Scheduled(fixedRate = 5, timeUnit = TimeUnit.SECONDS) - @Transactional(readOnly = true) - public void refreshCurrentPrice() { - List tickerSymbols = portfolioRepository.findAll().parallelStream() - .map(Portfolio::getPortfolioHoldings) - .flatMap(Collection::stream) - .map(PortfolioHolding::getStock) - .map(Stock::getTickerSymbol) - .collect(Collectors.toList()); + public void refreshStockPrice() { + List tickerSymbols = portFolioHoldingRepository.findAllTickerSymbol(); + refreshStockCurrentPrice(tickerSymbols); + refreshLastDayClosingPrice(tickerSymbols); + } - // 종목 현재가 갱신 - refreshCurrentPrice(tickerSymbols); - log.info("{}개의 종목 현재가 갱신 완료", tickerSymbols.size()); + // 주식 현재가 갱신 + public void refreshStockCurrentPrice(List tickerSymbols) { + List> futures = tickerSymbols.parallelStream() + .map(tickerSymbol -> { + CompletableFuture future = new CompletableFuture<>(); + executorService.schedule(createCurrentPriceRequest(tickerSymbol, future), 200, TimeUnit.MILLISECONDS); + return future; + }).collect(Collectors.toList()); - // 종가 갱신 - refreshLastDayClosingPrice(tickerSymbols); + futures.parallelStream() + .map(CompletableFuture::join) + .filter(Objects::nonNull) + .forEach(currentPriceManager::addCurrentPrice); } - // 종가 매니저가 가격을 갖지 않은 종목들의 직전거래일의 종가를 요청하여 저장한다 - private void refreshLastDayClosingPrice(List tickerSymbols) { + private Runnable createCurrentPriceRequest(final String tickerSymbol, + CompletableFuture future) { + return () -> { + CurrentPriceResponse response = readRealTimeCurrentPrice(tickerSymbol); + future.completeOnTimeout(response, 10, TimeUnit.SECONDS); + future.exceptionally(e -> { + log.info(e.getMessage(), e); + return null; + }); + }; + } + + // 제약조건 : kis 서버에 1초당 최대 5건, TR간격 0.1초 이하면 안됨 + public CurrentPriceResponse readRealTimeCurrentPrice(String tickerSymbol) { + long currentPrice = kisClient.readRealTimeCurrentPrice(tickerSymbol, manager.createAuthorization()); + log.info("tickerSymbol={}, currentPrice={}, time={}", tickerSymbol, currentPrice, LocalDateTime.now()); + return new CurrentPriceResponse(tickerSymbol, currentPrice); + } + + // 종가 갱신 (매일 0시 1분 0초에 시작) + public void refreshLastDayClosingPrice(List tickerSymbols) { List> futures = tickerSymbols.parallelStream() .filter(tickerSymbol -> !lastDayClosingPriceManager.hasPrice(tickerSymbol)) .map(tickerSymbol -> { @@ -161,30 +169,4 @@ private Runnable createLastDayClosingPriceRequest(final String tickerSymbol, }); }; } - - public void refreshCurrentPrice(List tickerSymbols) { - List> futures = tickerSymbols.parallelStream() - .map(tickerSymbol -> { - CompletableFuture future = new CompletableFuture<>(); - executorService.schedule(createCurrentPriceRequest(tickerSymbol, future), 200, TimeUnit.MILLISECONDS); - return future; - }).collect(Collectors.toList()); - - futures.parallelStream() - .map(CompletableFuture::join) - .filter(Objects::nonNull) - .forEach(currentPriceManager::addCurrentPrice); - } - - public Runnable createCurrentPriceRequest(final String tickerSymbol, - CompletableFuture future) { - return () -> { - CurrentPriceResponse response = readRealTimeCurrentPrice(tickerSymbol); - future.completeOnTimeout(response, 10, TimeUnit.SECONDS); - future.exceptionally(e -> { - log.info(e.getMessage(), e); - return null; - }); - }; - } } diff --git a/src/main/java/codesquad/fineants/spring/api/kis/aop/AccessTokenAspect.java b/src/main/java/codesquad/fineants/spring/api/kis/aop/AccessTokenAspect.java index 390878796..c06e51524 100644 --- a/src/main/java/codesquad/fineants/spring/api/kis/aop/AccessTokenAspect.java +++ b/src/main/java/codesquad/fineants/spring/api/kis/aop/AccessTokenAspect.java @@ -25,15 +25,11 @@ public class AccessTokenAspect { private final KisClient client; private final KisRedisService redisService; - @Pointcut("execution(* codesquad.fineants.spring.api.kis.KisService.refreshCurrentPrice())") - public void refreshCurrentPrice() { + @Pointcut("execution(* codesquad.fineants.spring.api.kis.KisService.refreshStockPrice())") + public void refreshStockPrice() { } - @Pointcut("execution(* codesquad.fineants.spring.api.kis.KisService.readRealTimeCurrentPrice())") - public void readRealTimeCurrentPrice() { - } - - @Before(value = "refreshCurrentPrice(), readRealTimeCurrentPrice()") + @Before(value = "refreshStockPrice()") public void checkAccessTokenExpiration() { if (manager.isAccessTokenExpired(LocalDateTime.now())) { final Optional> optionalMap = redisService.getAccessTokenMap(); diff --git a/src/main/java/codesquad/fineants/spring/api/kis/client/KisClient.java b/src/main/java/codesquad/fineants/spring/api/kis/client/KisClient.java index a20718561..2cd356ea5 100644 --- a/src/main/java/codesquad/fineants/spring/api/kis/client/KisClient.java +++ b/src/main/java/codesquad/fineants/spring/api/kis/client/KisClient.java @@ -78,7 +78,6 @@ public Map accessToken() { public long readRealTimeCurrentPrice(String tickerSymbol, String authorization) { MultiValueMap headerMap = new LinkedMultiValueMap<>(); - log.info("authorization : {}", authorization); headerMap.add("authorization", authorization); headerMap.add("appkey", appkey); headerMap.add("appsecret", secretkey); diff --git a/src/main/java/codesquad/fineants/spring/api/kis/manager/LastDayClosingPriceManager.java b/src/main/java/codesquad/fineants/spring/api/kis/manager/LastDayClosingPriceManager.java index f0edd0b89..4c1707df0 100644 --- a/src/main/java/codesquad/fineants/spring/api/kis/manager/LastDayClosingPriceManager.java +++ b/src/main/java/codesquad/fineants/spring/api/kis/manager/LastDayClosingPriceManager.java @@ -15,7 +15,7 @@ public class LastDayClosingPriceManager { private final RedisTemplate redisTemplate; public void addPrice(String tickerSymbol, long price) { - redisTemplate.opsForValue().set(String.format(format, tickerSymbol), String.valueOf(price), Duration.ofDays(1)); + redisTemplate.opsForValue().set(String.format(format, tickerSymbol), String.valueOf(price), Duration.ofDays(2)); } public Long getPrice(String tickerSymbol) { diff --git a/src/main/java/codesquad/fineants/spring/api/portfolio/PortFolioService.java b/src/main/java/codesquad/fineants/spring/api/portfolio/PortFolioService.java index d8e17a83b..401641495 100644 --- a/src/main/java/codesquad/fineants/spring/api/portfolio/PortFolioService.java +++ b/src/main/java/codesquad/fineants/spring/api/portfolio/PortFolioService.java @@ -141,7 +141,7 @@ public Portfolio findPortfolio(Long portfolioId) { } public PortfoliosResponse readMyAllPortfolio(AuthMember authMember, int size, Long nextCursor) { - kisService.refreshCurrentPrice(portfolioRepository.findAllByMemberId(authMember.getMemberId()).stream() + kisService.refreshStockCurrentPrice(portfolioRepository.findAllByMemberId(authMember.getMemberId()).stream() .map(Portfolio::getPortfolioHoldings) .flatMap(Collection::stream) .map(PortfolioHolding::getStock) From 242ac35a19df8ab768ccb5ec90e8af5b48855355 Mon Sep 17 00:00:00 2001 From: yonghwankim-dev Date: Tue, 5 Dec 2023 12:46:51 +0900 Subject: [PATCH 11/12] =?UTF-8?q?#33=20docs:=20=EC=83=98=ED=94=8C=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/db/mysql/data.sql | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/main/resources/db/mysql/data.sql b/src/main/resources/db/mysql/data.sql index 3c6f013e9..55b4f85e7 100644 --- a/src/main/resources/db/mysql/data.sql +++ b/src/main/resources/db/mysql/data.sql @@ -30,10 +30,27 @@ INSERT INTO fineAnts.portfolio (id, budget, maximum_loss, name, securities_firm, maximum_is_active, member_id) VALUES (1, 1000000, 900000, '내꿈은 워렌버핏', '토스', 1500000, false, false, 1); -INSERT INTO fineAnts.portfolio_holding (id, create_at, modified_at, - portfolio_id, ticker_symbol) +INSERT INTO fineAnts.portfolio_holding (id, create_at, modified_at, portfolio_id, ticker_symbol) VALUES (1, '2023-10-26 15:25:39.409612', '2023-10-26 15:25:39.409612', 1, '005930'); +INSERT INTO fineAnts.portfolio_holding (id, create_at, modified_at, portfolio_id, ticker_symbol) +VALUES (2, '2023-10-26 15:25:39.409612', '2023-10-26 15:25:39.409612', 1, '000020'); + +INSERT INTO fineAnts.portfolio_holding (id, create_at, modified_at, portfolio_id, ticker_symbol) +VALUES (3, '2023-10-26 15:25:39.409612', '2023-10-26 15:25:39.409612', 1, '000040'); + +INSERT INTO fineAnts.portfolio_holding (id, create_at, modified_at, portfolio_id, ticker_symbol) +VALUES (4, '2023-10-26 15:25:39.409612', '2023-10-26 15:25:39.409612', 1, '000050'); + +INSERT INTO fineAnts.portfolio_holding (id, create_at, modified_at, portfolio_id, ticker_symbol) +VALUES (5, '2023-10-26 15:25:39.409612', '2023-10-26 15:25:39.409612', 1, '000070'); + +INSERT INTO fineAnts.portfolio_holding (id, create_at, modified_at, portfolio_id, ticker_symbol) +VALUES (6, '2023-10-26 15:25:39.409612', '2023-10-26 15:25:39.409612', 1, '000080'); + +INSERT INTO fineAnts.portfolio_holding (id, create_at, modified_at, portfolio_id, ticker_symbol) +VALUES (7, '2023-10-26 15:25:39.409612', '2023-10-26 15:25:39.409612', 1, '000087'); + INSERT INTO fineAnts.purchase_history (id, create_at, modified_at, memo, num_shares, purchase_date, purchase_price_per_share, portfolio_holding_id) VALUES (1, '2023-10-26 15:26:11.219793', '2023-10-26 15:26:11.219793', null, 3, '2023-10-23 13:00:00.000000', 50000, 1); From 3dfb6b147998d50df7b46a2c4fc3831392f20c1b Mon Sep 17 00:00:00 2001 From: yonghwankim-dev Date: Tue, 5 Dec 2023 14:50:02 +0900 Subject: [PATCH 12/12] =?UTF-8?q?#33=20docs:=20=EC=8B=9C=ED=81=AC=EB=A6=BF?= =?UTF-8?q?=20=EA=B0=B1=EC=8B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- secret | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/secret b/secret index 95d7c60c8..8e0073c2a 160000 --- a/secret +++ b/secret @@ -1 +1 @@ -Subproject commit 95d7c60c82ea31e280dc7eb9e917dfda5b4e3f02 +Subproject commit 8e0073c2ab032a0708a64c731410888de51b7161