diff --git a/build.gradle b/build.gradle index 414423c..16340e9 100644 --- a/build.gradle +++ b/build.gradle @@ -97,6 +97,10 @@ dependencies { annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta" annotationProcessor 'jakarta.annotation:jakarta.annotation-api' annotationProcessor 'jakarta.persistence:jakarta.persistence-api' + + // kafka + implementation 'org.springframework.kafka:spring-kafka:3.1.0' + } tasks.named('test') { diff --git a/src/main/java/com/devspacehub/ast/common/constant/ProfileType.java b/src/main/java/com/devspacehub/ast/common/constant/ProfileType.java index 1301005..281c688 100644 --- a/src/main/java/com/devspacehub/ast/common/constant/ProfileType.java +++ b/src/main/java/com/devspacehub/ast/common/constant/ProfileType.java @@ -24,11 +24,22 @@ public enum ProfileType { private final String code; private final String accountStatus; - + /** + * 실행 환경 별 계좌 상태를 반환한다. + * @return 계좌 상태 + */ public static String getAccountStatus() { return PROD.code.equals(getActiveProfile()) ? PROD.accountStatus : TEST.accountStatus; } + /** + * 실행 환경 별 코드를 반환한다. + * @return 코드 + */ + public static ProfileType getActiveProfileType() { + return PROD.code.equals(getActiveProfile()) ? PROD : TEST; + } + /** * 동작 중인 애플리케이션이 'Prod'이면 True를 반환한다. 반대는 False. * @return 'Prod' 여부 Boolean 값. @@ -44,7 +55,7 @@ public static Boolean isProdActive() { * 현재 동작 중인 애플리케이션의 Profile을 반환한다. * @return 애플리케이션의 profile */ - static String getActiveProfile() { + private static String getActiveProfile() { return System.getProperty("spring.profiles.active"); } } diff --git a/src/main/java/com/devspacehub/ast/domain/mashup/service/MashupService.java b/src/main/java/com/devspacehub/ast/domain/mashup/service/MashupService.java index 0fc75f6..a2bea08 100644 --- a/src/main/java/com/devspacehub/ast/domain/mashup/service/MashupService.java +++ b/src/main/java/com/devspacehub/ast/domain/mashup/service/MashupService.java @@ -15,7 +15,7 @@ import com.devspacehub.ast.domain.my.service.MyService; import com.devspacehub.ast.domain.my.service.MyServiceFactory; import com.devspacehub.ast.domain.notification.Notificator; -import com.devspacehub.ast.domain.notification.dto.MessageContentDto; +import com.devspacehub.ast.domain.notification.dto.ItemConclusionResultDto; import com.devspacehub.ast.domain.oauth.service.OAuthService; import com.devspacehub.ast.domain.orderTrading.OrderTrading; import com.devspacehub.ast.domain.orderTrading.OrderTradingServiceFactory; @@ -78,7 +78,7 @@ public void startOrderConclusionResultProcess(OpenApiType openApiType) { myServiceImpl.updateMyReservationOrderUseYn(orderConclusion, today); // 체결된 종목들에 대해 디스코드 메시지 전달 - notificator.sendMessage(MessageContentDto.ConclusionResult.fromOne(openApiType, getAccountStatus(), orderConclusion)); + notificator.sendStockResultMessage(ItemConclusionResultDto.from(openApiType, getAccountStatus(), orderConclusion)); RequestUtil.timeDelay(); } } diff --git a/src/main/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/OverseasReservationBuyOrderService.java b/src/main/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/OverseasReservationBuyOrderService.java index 8a150d8..903e31a 100644 --- a/src/main/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/OverseasReservationBuyOrderService.java +++ b/src/main/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/OverseasReservationBuyOrderService.java @@ -21,7 +21,7 @@ import com.devspacehub.ast.domain.my.service.MyService; import com.devspacehub.ast.domain.my.service.MyServiceFactory; import com.devspacehub.ast.domain.notification.Notificator; -import com.devspacehub.ast.domain.notification.dto.MessageContentDto; +import com.devspacehub.ast.domain.notification.dto.DefaultItemInfoDto.ItemOrderResultDto; import com.devspacehub.ast.domain.orderTrading.OrderTrading; import com.devspacehub.ast.domain.orderTrading.OrderTradingRepository; import com.devspacehub.ast.domain.orderTrading.dto.OverseasStockOrderApiReqDto; @@ -134,7 +134,7 @@ private OrderTrading buy(ReservationStockItem reservationItem, OpenApiProperties /** * 예약 종목 데이터 validation check * @param reservation 예약 종목 데이터 - * @return 유효하면 True + * @throws InvalidValueException 값이 유효하지 않은 경우 */ protected void validateValue(ReservationStockItem reservation) { if (StringUtils.isBlank(reservation.getItemCode())) { @@ -197,7 +197,7 @@ private BigDecimal getMyDeposit(ReservationStockItem reservation) { /** * 예수금과 주문할 총 가격을 비교하여 주문 가능한지 체크한다. * @param reservation 해외 예약 종목 정보 Dto - * @return 주문 가능한지 여부 boolean 값 + * @throws InsufficientMoneyException 잔고가 부족한 경우 */ protected void sufficientDepositCheck(ReservationStockItem reservation, BigDecimal myDeposit) { StockItemDto overseasStockItem = reservation.getStockItem(); @@ -261,7 +261,7 @@ public List saveOrderInfos(List orderTradingInfos) { @Override public void orderApiResultProcess(OrderTrading orderTrading) { LogUtils.tradingOrderSuccess(OVERSEAS_STOCK_RESERVATION_BUY_ORDER, orderTrading.getItemNameKor()); - notificator.sendMessage(MessageContentDto.OrderResult.fromOne(OVERSEAS_STOCK_RESERVATION_BUY_ORDER, getAccountStatus(), orderTrading)); + notificator.sendStockResultMessage(ItemOrderResultDto.from(OVERSEAS_STOCK_RESERVATION_BUY_ORDER, getAccountStatus(), orderTrading)); } /** diff --git a/src/main/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/ReservationBuyOrderServiceImpl.java b/src/main/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/ReservationBuyOrderServiceImpl.java index cf93d5a..7aa5917 100644 --- a/src/main/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/ReservationBuyOrderServiceImpl.java +++ b/src/main/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/ReservationBuyOrderServiceImpl.java @@ -23,7 +23,7 @@ import com.devspacehub.ast.domain.my.service.MyService; import com.devspacehub.ast.domain.my.service.MyServiceFactory; import com.devspacehub.ast.domain.notification.Notificator; -import com.devspacehub.ast.domain.notification.dto.MessageContentDto; +import com.devspacehub.ast.domain.notification.dto.DefaultItemInfoDto.ItemOrderResultDto; import com.devspacehub.ast.domain.orderTrading.OrderTrading; import com.devspacehub.ast.domain.orderTrading.OrderTradingRepository; import com.devspacehub.ast.domain.orderTrading.dto.DomesticStockOrderExternalReqDto; @@ -216,6 +216,6 @@ public List saveOrderInfos(List orderTradingInfos) { @Override public void orderApiResultProcess(OrderTrading orderTrading) { LogUtils.tradingOrderSuccess(DOMESTIC_STOCK_RESERVATION_BUY_ORDER, orderTrading.getItemNameKor()); - notificator.sendMessage(MessageContentDto.OrderResult.fromOne(DOMESTIC_STOCK_RESERVATION_BUY_ORDER, getAccountStatus(), orderTrading)); + notificator.sendStockResultMessage(ItemOrderResultDto.from(DOMESTIC_STOCK_RESERVATION_BUY_ORDER, getAccountStatus(), orderTrading)); } } diff --git a/src/main/java/com/devspacehub/ast/domain/notification/Notificator.java b/src/main/java/com/devspacehub/ast/domain/notification/Notificator.java index a04449c..dac310f 100644 --- a/src/main/java/com/devspacehub/ast/domain/notification/Notificator.java +++ b/src/main/java/com/devspacehub/ast/domain/notification/Notificator.java @@ -7,80 +7,36 @@ */ package com.devspacehub.ast.domain.notification; -import com.devspacehub.ast.common.constant.OpenApiType; -import com.devspacehub.ast.common.constant.ResultCode; -import com.devspacehub.ast.domain.notification.dto.DiscordWebhookNotifyRequestDto; -import com.devspacehub.ast.domain.notification.dto.MessageContentDto; -import com.devspacehub.ast.exception.error.NotificationException; -import com.devspacehub.ast.exception.error.InvalidValueException; +import com.devspacehub.ast.common.constant.ProfileType; +import com.devspacehub.ast.infra.kafka.config.KafkaTopicType; +import com.devspacehub.ast.infra.kafka.dto.MessageDto; +import com.devspacehub.ast.infra.kafka.producer.MessageProducer; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; -import org.springframework.web.reactive.function.client.WebClient; -import org.springframework.web.reactive.function.client.WebClientResponseException; -import java.util.function.Consumer; - /** - * Discord Webhook 이용한 알림 서비스 + * 알림 클래스 */ @Slf4j @Service +@RequiredArgsConstructor public class Notificator { - @Value("${notify.discord-webhook.url}") - private String discordWebhookUrl; - private static final String DOMESTIC_ORDER_NOTI_SENDER_NAME = "국내 주문 봇"; - private static final String DOMESTIC_CONCLUSION_NOTI_SENDER_NAME = "국내 체결 봇"; - - private static final String OVERSEAS_ORDER_NOTI_SENDER_NAME = "해외 주문 봇"; - private static final String OVERSEAS_CONCLUSION_NOTI_SENDER_NAME = "해외 체결 봇"; + private final MessageProducer messageProducer; /** - * 단일 메시지 발송 요청 - * @param content 메세지 내용 DTO + * 주식 결과 알림 메세지 발송 요청한다. + * @param content MessageDto 상속하는 메세지 Dto 클래스 */ - public void sendMessage(T content) { - String senderName = getSenderName(content.getOpenApiType()); - String msg = content.createMessage(content); - Consumer headers = DiscordWebhookNotifyRequestDto.setHeaders(); - DiscordWebhookNotifyRequestDto requestBody = DiscordWebhookNotifyRequestDto.builder() - .senderName(senderName) - .message(msg) - .build(); - - try { - WebClient.builder() - .baseUrl(discordWebhookUrl) - .build() - .post() - .headers(headers) - .bodyValue(requestBody) - .retrieve() - .toBodilessEntity() - .block(); - - } catch (Exception ex) { - if (ex instanceof WebClientResponseException webClientResponseException && HttpStatus.NO_CONTENT.equals(webClientResponseException.getStatusCode())) { - return; - } - throw new NotificationException(ex.getMessage()); - } + public void sendStockResultMessage(T content) { + messageProducer.produce(this.createTopic(KafkaTopicType.STOCK_RESULT_NOTI_TOPIC), content); } + /** - * 알림 봇 이름 지정 - * @param openApiType OpenApiType - * @return 알림 봇 이름 + * 환경 별 토픽을 생성한다. + * @return 토픽 */ - private String getSenderName(OpenApiType openApiType) { - return switch (openApiType) { - case DOMESTIC_STOCK_BUY_ORDER, DOMESTIC_STOCK_SELL_ORDER, DOMESTIC_STOCK_RESERVATION_BUY_ORDER -> DOMESTIC_ORDER_NOTI_SENDER_NAME; - case DOMESTIC_ORDER_CONCLUSION_FIND -> DOMESTIC_CONCLUSION_NOTI_SENDER_NAME; - case OVERSEAS_STOCK_BUY_ORDER, OVERSEAS_STOCK_SELL_ORDER, OVERSEAS_STOCK_RESERVATION_BUY_ORDER -> OVERSEAS_ORDER_NOTI_SENDER_NAME; - case OVERSEAS_ORDER_CONCLUSION_FIND -> OVERSEAS_CONCLUSION_NOTI_SENDER_NAME; - default -> throw new InvalidValueException(ResultCode.INVALID_OPENAPI_TYPE_ERROR); - }; + private String createTopic(KafkaTopicType topicType) { + return topicType.getValue(ProfileType.getActiveProfileType()); } - } diff --git a/src/main/java/com/devspacehub/ast/domain/notification/dto/DefaultItemInfoDto.java b/src/main/java/com/devspacehub/ast/domain/notification/dto/DefaultItemInfoDto.java new file mode 100644 index 0000000..b97ea1d --- /dev/null +++ b/src/main/java/com/devspacehub/ast/domain/notification/dto/DefaultItemInfoDto.java @@ -0,0 +1,65 @@ +/* + © 2024 devspacehub, Inc. All rights reserved. + + name : DefaultItemInfoDto + creation : 2024.9.18 + author : Yoonji Moon + */ + +package com.devspacehub.ast.domain.notification.dto; + +import com.devspacehub.ast.common.constant.OpenApiType; +import com.devspacehub.ast.domain.orderTrading.OrderTrading; +import com.devspacehub.ast.infra.kafka.dto.MessageDto; +import lombok.Getter; +import lombok.ToString; +import lombok.experimental.SuperBuilder; + +import java.math.BigDecimal; + +/** + * 메세지 발송 시 생성되는 주식 관련 정보 Dto + */ +@Getter +@SuperBuilder +public abstract class DefaultItemInfoDto extends MessageDto { + private String accountStatusKor; + private String itemNameKor; + private String itemCode; + private OpenApiType openApiType; + private int orderQuantity; + private BigDecimal orderPrice; + private String orderNumber; + private String orderTime; + + /** + * 주문 결과 Dto. + * DefaultItemInfoDto를 상속한다. + */ + @ToString + @SuperBuilder + public static class ItemOrderResultDto extends DefaultItemInfoDto { + + /** + * 전달받는 인자로 주문 결과 Dto 인스턴스를 반환한다. + * @param openApiType OpenApi Type + * @param accountStatus 계좌 상태 + * @param orderTrading 주문 결과 Dto + * @return ItemOrderResultDto + */ + public static ItemOrderResultDto from(OpenApiType openApiType, String accountStatus, OrderTrading orderTrading) { + return ItemOrderResultDto.builder() + .title("주문 완료") + .accountStatusKor(accountStatus) + .itemNameKor(orderTrading.getItemNameKor()) + .itemCode(orderTrading.getItemCode()) + .openApiType(openApiType) + .orderQuantity(orderTrading.getOrderQuantity()) + .orderPrice(orderTrading.getOrderPrice()) + .orderNumber(orderTrading.getOrderNumber()) + .orderTime(orderTrading.getOrderTime()) + .build(); + } + } + +} diff --git a/src/main/java/com/devspacehub/ast/domain/notification/dto/DiscordWebhookNotifyRequestDto.java b/src/main/java/com/devspacehub/ast/domain/notification/dto/DiscordWebhookNotifyRequestDto.java deleted file mode 100644 index bae578e..0000000 --- a/src/main/java/com/devspacehub/ast/domain/notification/dto/DiscordWebhookNotifyRequestDto.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - © 2023 devspacehub, Inc. All rights reserved. - - name : DiscordWebhookNotifyRequestDto - creation : 2024.1.27 - author : Yoonji Moon - */ -package com.devspacehub.ast.domain.notification.dto; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; - -import java.util.function.Consumer; - -/** - * Discord Webhook 이용한 알림 요청 DTO. - */ -@Builder -@Getter -@Setter -public class DiscordWebhookNotifyRequestDto { - @JsonProperty(value = "username") - private String senderName; - @JsonProperty(value = "content") - private String message; - - public static Consumer setHeaders() { - HttpHeaders headers = new HttpHeaders(); - headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); - return httpHeaders -> httpHeaders.addAll(headers); - } -} diff --git a/src/main/java/com/devspacehub/ast/domain/notification/dto/ItemConclusionResultDto.java b/src/main/java/com/devspacehub/ast/domain/notification/dto/ItemConclusionResultDto.java new file mode 100644 index 0000000..63a672b --- /dev/null +++ b/src/main/java/com/devspacehub/ast/domain/notification/dto/ItemConclusionResultDto.java @@ -0,0 +1,51 @@ +/* + © 2024 devspacehub, Inc. All rights reserved. + + name : ItemConclusionResultDto + creation : 2024.9.18 + author : Yoonji Moon + */ + +package com.devspacehub.ast.domain.notification.dto; + +import com.devspacehub.ast.common.constant.OpenApiType; +import com.devspacehub.ast.domain.my.dto.orderConclusion.OrderConclusionDto; +import lombok.ToString; +import lombok.experimental.SuperBuilder; + +import java.math.BigDecimal; + +/** + * 주식 체결 결과 Dto + * DefaultItemInfoDto 상속 + */ +@ToString +@SuperBuilder +public class ItemConclusionResultDto extends DefaultItemInfoDto { + private int concludedQuantity; + private BigDecimal concludedPrice; + + + /** + * 전달받는 인자로 주식 체결 결과 Dto 인스턴스 생성하여 반환한다. + * @param orderApiType OpenApi Type + * @param accountStatus 계좌 상태 + * @param orderConclusion 주문 결과 정보 + * @return ItemConclusionResultDto + */ + public static ItemConclusionResultDto from(OpenApiType orderApiType, String accountStatus, OrderConclusionDto orderConclusion) { + return ItemConclusionResultDto.builder() + .title("체결 완료") + .accountStatusKor(accountStatus) + .itemNameKor(orderConclusion.getItemNameKor()) + .itemCode(orderConclusion.getItemCode()) + .openApiType(orderApiType) + .orderQuantity(orderConclusion.getOrderQuantity()) + .orderPrice(orderConclusion.getOrderPrice()) + .orderNumber(orderConclusion.getOrderNumber()) + .concludedQuantity(orderConclusion.getConcludedQuantity()) + .concludedPrice(orderConclusion.getConcludedPrice()) + .orderTime(orderConclusion.getOrderTime()) + .build(); + } +} diff --git a/src/main/java/com/devspacehub/ast/domain/notification/dto/MessageContentDto.java b/src/main/java/com/devspacehub/ast/domain/notification/dto/MessageContentDto.java deleted file mode 100644 index 03c72ed..0000000 --- a/src/main/java/com/devspacehub/ast/domain/notification/dto/MessageContentDto.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - © 2024 devspacehub, Inc. All rights reserved. - - name : MessageContentDto - creation : 2024.4.24 - author : Yoonji Moon - */ - -package com.devspacehub.ast.domain.notification.dto; - -import com.devspacehub.ast.common.constant.OpenApiType; -import com.devspacehub.ast.domain.orderTrading.OrderTrading; -import com.devspacehub.ast.domain.my.dto.orderConclusion.OrderConclusionDto; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -import java.math.BigDecimal; - -/** - * 디스코드 메시지 내용 DTO 추상 클래스. - */ -@Getter -@NoArgsConstructor -@SuperBuilder -@AllArgsConstructor -public abstract class MessageContentDto { - private String title; - private String accountStatusKor; - private String itemNameKor; - private String itemCode; - private OpenApiType openApiType; - private int orderQuantity; - private BigDecimal orderPrice; - private String orderNumber; - private String orderTime; - - - /** - * 알림 메시지 공통 내용 작성 - * @param content MessageContentDto 상속받는 메시지 내용 DTO - * @return 완성된 메시지 내용 - */ - public String createMessage(T content) { - StringBuilder sb = new StringBuilder(); - - sb.append("**[").append(content.getOrderTime()).append("] ").append(content.getTitle()).append("**"); - sb.append("\n계좌 상태 : ").append(content.getAccountStatusKor()); - sb.append("\n종목명 : ").append(content.getItemNameKor()); - sb.append(" (").append(content.getItemCode()).append(")"); - sb.append("\n매매구분 : ").append(content.getOpenApiType().getDiscription()); - sb.append("\n주문번호 : ").append(content.getOrderNumber()); - sb.append("\n주문수량 : ").append(content.getOrderQuantity()).append("주"); - sb.append("\n주문단가 : ").append(content.getOrderPrice()); - - return sb.toString(); - } - - /** - * 매수/매도 주문 결과 메세지 DTO - * MessageContentDto 상속 - */ - @Getter - @SuperBuilder - @AllArgsConstructor - public static class OrderResult extends MessageContentDto{ - - public static OrderResult fromOne(OpenApiType openApiType, String accountStatus, OrderTrading orderTrading) { - return OrderResult.builder() - .title("주문 완료") - .accountStatusKor(accountStatus) - .itemNameKor(orderTrading.getItemNameKor()) - .itemCode(orderTrading.getItemCode()) - .openApiType(openApiType) - .orderQuantity(orderTrading.getOrderQuantity()) - .orderPrice(orderTrading.getOrderPrice()) - .orderNumber(orderTrading.getOrderNumber()) - .orderTime(orderTrading.getOrderTime()) - .build(); - } - } - - /** - * 체결 결과 메세지 DTO - * MessageContentDto 상속 - */ - @Getter - @SuperBuilder - public static class ConclusionResult extends MessageContentDto { - private int concludedQuantity; - private BigDecimal concludedPrice; - - public static ConclusionResult fromOne(OpenApiType orderApiType, String accountStatus, OrderConclusionDto orderConclusion) { - return ConclusionResult.builder() - .title("체결 완료") - .accountStatusKor(accountStatus) - .itemNameKor(orderConclusion.getItemNameKor()) - .itemCode(orderConclusion.getItemCode()) - .openApiType(orderApiType) - .orderQuantity(orderConclusion.getOrderQuantity()) - .orderPrice(orderConclusion.getOrderPrice()) - .orderNumber(orderConclusion.getOrderNumber()) - .concludedQuantity(orderConclusion.getConcludedQuantity()) - .concludedPrice(orderConclusion.getConcludedPrice()) - .orderTime(orderConclusion.getOrderTime()) - .build(); - } - - @Override - public String createMessage(T content) { - ConclusionResult embodiedContent = (ConclusionResult) content; - - StringBuilder commonMessage = new StringBuilder(super.createMessage(content)); - commonMessage.append("\n체결수량 : ").append(embodiedContent.getConcludedQuantity()).append("주"); - commonMessage.append("\n체결금액 : ").append(embodiedContent.getConcludedPrice()); - return commonMessage.toString(); - } - } -} diff --git a/src/main/java/com/devspacehub/ast/domain/orderTrading/service/BuyOrderServiceImpl.java b/src/main/java/com/devspacehub/ast/domain/orderTrading/service/BuyOrderServiceImpl.java index 48fdd01..aeab398 100644 --- a/src/main/java/com/devspacehub/ast/domain/orderTrading/service/BuyOrderServiceImpl.java +++ b/src/main/java/com/devspacehub/ast/domain/orderTrading/service/BuyOrderServiceImpl.java @@ -23,7 +23,6 @@ import com.devspacehub.ast.domain.my.service.MyService; import com.devspacehub.ast.domain.my.service.MyServiceFactory; import com.devspacehub.ast.domain.notification.Notificator; -import com.devspacehub.ast.domain.notification.dto.MessageContentDto; import com.devspacehub.ast.domain.orderTrading.OrderTrading; import com.devspacehub.ast.domain.orderTrading.OrderTradingRepository; import com.devspacehub.ast.domain.orderTrading.dto.DomesticStockOrderExternalReqDto; @@ -53,6 +52,7 @@ import static com.devspacehub.ast.common.constant.ProfileType.*; import static com.devspacehub.ast.common.constant.YesNoStatus.YES; import static com.devspacehub.ast.domain.marketStatus.dto.DomStockTradingVolumeRankingExternalResDto.*; +import static com.devspacehub.ast.domain.notification.dto.DefaultItemInfoDto.*; /** * 국내 주식 주문 서비스 구현체 - 매수 @@ -295,7 +295,7 @@ public List saveOrderInfos(List orderTradingInfos) { @Override public void orderApiResultProcess(OrderTrading orderTrading) { LogUtils.tradingOrderSuccess(DOMESTIC_STOCK_BUY_ORDER, orderTrading.getItemNameKor()); - notificator.sendMessage(MessageContentDto.OrderResult.fromOne(DOMESTIC_STOCK_BUY_ORDER, getAccountStatus(), orderTrading)); + notificator.sendStockResultMessage(ItemOrderResultDto.from(DOMESTIC_STOCK_BUY_ORDER, getAccountStatus(), orderTrading)); } /** diff --git a/src/main/java/com/devspacehub/ast/domain/orderTrading/service/SellOrderServiceImpl.java b/src/main/java/com/devspacehub/ast/domain/orderTrading/service/SellOrderServiceImpl.java index dd4fb15..5cc599f 100644 --- a/src/main/java/com/devspacehub/ast/domain/orderTrading/service/SellOrderServiceImpl.java +++ b/src/main/java/com/devspacehub/ast/domain/orderTrading/service/SellOrderServiceImpl.java @@ -17,7 +17,6 @@ import com.devspacehub.ast.domain.my.stockBalance.dto.response.StockBalanceApiResDto; import com.devspacehub.ast.domain.my.service.MyService; import com.devspacehub.ast.domain.notification.Notificator; -import com.devspacehub.ast.domain.notification.dto.MessageContentDto; import com.devspacehub.ast.domain.orderTrading.OrderTrading; import com.devspacehub.ast.domain.orderTrading.OrderTradingRepository; import com.devspacehub.ast.domain.orderTrading.dto.DomesticStockOrderExternalReqDto; @@ -41,6 +40,7 @@ import static com.devspacehub.ast.common.constant.CommonConstants.*; import static com.devspacehub.ast.common.constant.OpenApiType.DOMESTIC_STOCK_SELL_ORDER; import static com.devspacehub.ast.common.constant.ProfileType.getAccountStatus; +import static com.devspacehub.ast.domain.notification.dto.DefaultItemInfoDto.*; /** * 국내 주식 주문 서비스 구현체 - 매도 @@ -185,7 +185,7 @@ public List saveOrderInfos(List orderTradingInfos) { @Override public void orderApiResultProcess(OrderTrading orderTrading) { LogUtils.tradingOrderSuccess(DOMESTIC_STOCK_SELL_ORDER, orderTrading.getItemNameKor()); - notificator.sendMessage(MessageContentDto.OrderResult.fromOne(DOMESTIC_STOCK_SELL_ORDER, getAccountStatus(), orderTrading)); + notificator.sendStockResultMessage(ItemOrderResultDto.from(DOMESTIC_STOCK_SELL_ORDER, getAccountStatus(), orderTrading)); } /** diff --git a/src/main/java/com/devspacehub/ast/domain/orderTrading/service/overseas/OverseasBuyOrderServiceImpl.java b/src/main/java/com/devspacehub/ast/domain/orderTrading/service/overseas/OverseasBuyOrderServiceImpl.java index 6d06d4b..18c2a1d 100644 --- a/src/main/java/com/devspacehub/ast/domain/orderTrading/service/overseas/OverseasBuyOrderServiceImpl.java +++ b/src/main/java/com/devspacehub/ast/domain/orderTrading/service/overseas/OverseasBuyOrderServiceImpl.java @@ -21,7 +21,6 @@ import com.devspacehub.ast.domain.marketStatus.service.OverseasMarketStatusService; import com.devspacehub.ast.domain.my.dto.MyServiceRequestDto; import com.devspacehub.ast.domain.my.service.MyService; -import com.devspacehub.ast.domain.notification.dto.MessageContentDto; import com.devspacehub.ast.domain.orderTrading.OrderTrading; import com.devspacehub.ast.domain.orderTrading.service.TradingService; import com.devspacehub.ast.exception.error.BusinessException; @@ -46,6 +45,7 @@ import static com.devspacehub.ast.common.constant.OpenApiType.OVERSEAS_STOCK_BUY_ORDER; import static com.devspacehub.ast.common.constant.ProfileType.getAccountStatus; import static com.devspacehub.ast.domain.marketStatus.dto.OverseasStockConditionSearchResDto.*; +import static com.devspacehub.ast.domain.notification.dto.DefaultItemInfoDto.*; /** * 해외 주식 주문 서비스 구현체 - 매수 @@ -218,7 +218,7 @@ public List saveOrderInfos(List orderTradingInfos) { */ public void orderApiResultProcess(OrderTrading orderTrading) { LogUtils.tradingOrderSuccess(OVERSEAS_STOCK_BUY_ORDER, orderTrading.getItemNameKor()); - notificator.sendMessage(MessageContentDto.OrderResult.fromOne(OVERSEAS_STOCK_BUY_ORDER, getAccountStatus(), orderTrading)); + notificator.sendStockResultMessage(ItemOrderResultDto.from(OVERSEAS_STOCK_BUY_ORDER, getAccountStatus(), orderTrading)); } diff --git a/src/main/java/com/devspacehub/ast/domain/orderTrading/service/overseas/OverseasSellOrderServiceImpl.java b/src/main/java/com/devspacehub/ast/domain/orderTrading/service/overseas/OverseasSellOrderServiceImpl.java index 5ed6141..057b1c2 100644 --- a/src/main/java/com/devspacehub/ast/domain/orderTrading/service/overseas/OverseasSellOrderServiceImpl.java +++ b/src/main/java/com/devspacehub/ast/domain/orderTrading/service/overseas/OverseasSellOrderServiceImpl.java @@ -17,7 +17,6 @@ import com.devspacehub.ast.domain.my.service.MyServiceFactory; import com.devspacehub.ast.domain.my.stockBalance.dto.response.overseas.OverseasStockBalanceApiResDto; import com.devspacehub.ast.domain.notification.Notificator; -import com.devspacehub.ast.domain.notification.dto.MessageContentDto; import com.devspacehub.ast.domain.orderTrading.OrderTrading; import com.devspacehub.ast.domain.orderTrading.OrderTradingRepository; import com.devspacehub.ast.domain.orderTrading.dto.OverseasStockOrderApiReqDto; @@ -42,6 +41,7 @@ import static com.devspacehub.ast.common.constant.CommonConstants.*; import static com.devspacehub.ast.common.constant.OpenApiType.OVERSEAS_STOCK_SELL_ORDER; import static com.devspacehub.ast.common.constant.ProfileType.getAccountStatus; +import static com.devspacehub.ast.domain.notification.dto.DefaultItemInfoDto.*; /** * 해외 주식 주문 서비스 구현체 - 매도 @@ -190,7 +190,7 @@ public List saveOrderInfos(List orderTradingInfos) { @Override public void orderApiResultProcess(OrderTrading orderTrading) { LogUtils.tradingOrderSuccess(OVERSEAS_STOCK_SELL_ORDER, orderTrading.getItemNameKor()); - notificator.sendMessage(MessageContentDto.OrderResult.fromOne(OVERSEAS_STOCK_SELL_ORDER, getAccountStatus(), orderTrading)); + notificator.sendStockResultMessage(ItemOrderResultDto.from(OVERSEAS_STOCK_SELL_ORDER, getAccountStatus(), orderTrading)); } private MyService myServiceImpl() { diff --git a/src/main/java/com/devspacehub/ast/infra/kafka/config/KafkaProducerConfig.java b/src/main/java/com/devspacehub/ast/infra/kafka/config/KafkaProducerConfig.java new file mode 100644 index 0000000..5a94fe5 --- /dev/null +++ b/src/main/java/com/devspacehub/ast/infra/kafka/config/KafkaProducerConfig.java @@ -0,0 +1,58 @@ +/* + © 2024 devspacehub, Inc. All rights reserved. + + name : KafkaProducerConfig + creation : 2024.9.15 + author : Yoonji Moon + */ + +package com.devspacehub.ast.infra.kafka.config; + +import com.devspacehub.ast.infra.kafka.dto.MessageDto; +import org.apache.kafka.clients.producer.ProducerConfig; +import org.apache.kafka.common.serialization.StringSerializer; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.kafka.core.DefaultKafkaProducerFactory; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.kafka.core.ProducerFactory; +import org.springframework.kafka.support.serializer.JsonSerializer; + +import java.util.HashMap; +import java.util.Map; + +/** + * Kafka Producer Configuration + */ +@Configuration +public class KafkaProducerConfig { + + @Value("${kafka.producer.bootstrap-server.host}") + private String bootstrapServerHost; + @Value("${kafka.producer.bootstrap-server.port}") + private int bootstrapServerPort; + + /** + * Kafka Producer 설정 프로퍼티 세팅하여 반환한다. + * @return ProducerFactory + */ + @Bean + public ProducerFactory kafkaProducerFactory() { + Map props = new HashMap<>(); + props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class); + props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, String.format("%s:%d", bootstrapServerHost, bootstrapServerPort)); + + return new DefaultKafkaProducerFactory<>(props); + } + + /** + * Kafka Producer Bean + * @return KafkaTemplate + */ + @Bean + public KafkaTemplate kafkaTemplate(){ + return new KafkaTemplate<>(kafkaProducerFactory()); + } +} diff --git a/src/main/java/com/devspacehub/ast/infra/kafka/config/KafkaTopicType.java b/src/main/java/com/devspacehub/ast/infra/kafka/config/KafkaTopicType.java new file mode 100644 index 0000000..694fce0 --- /dev/null +++ b/src/main/java/com/devspacehub/ast/infra/kafka/config/KafkaTopicType.java @@ -0,0 +1,32 @@ +/* + © 2024 devspacehub, Inc. All rights reserved. + + name : KafkaTopicType + creation : 2024.9.18 + author : Yoonji Moon + */ + +package com.devspacehub.ast.infra.kafka.config; + +import com.devspacehub.ast.common.constant.ProfileType; +import lombok.RequiredArgsConstructor; + +/** + * Kafka Topic Type Enum + */ +@RequiredArgsConstructor +public enum KafkaTopicType { + STOCK_RESULT_NOTI_TOPIC("Stock-Result-Notification", "Stock-Result-Notification_TEST"); + + private final String prodValue; + private final String testValue; + + /** + * 환경 별 토픽 값 반환한다. + * @param profileType 동작 중인 프로파일 타입 + * @return kafka 토픽 + */ + public String getValue(ProfileType profileType) { + return ProfileType.PROD.equals(profileType) ? this.prodValue : this.testValue; + } +} diff --git a/src/main/java/com/devspacehub/ast/infra/kafka/dto/MessageDto.java b/src/main/java/com/devspacehub/ast/infra/kafka/dto/MessageDto.java new file mode 100644 index 0000000..e604941 --- /dev/null +++ b/src/main/java/com/devspacehub/ast/infra/kafka/dto/MessageDto.java @@ -0,0 +1,23 @@ +/* + © 2024 devspacehub, Inc. All rights reserved. + + name : MessageDto + creation : 2024.8.30 + author : Yoonji Moon + */ + +package com.devspacehub.ast.infra.kafka.dto; + +import lombok.Getter; +import lombok.ToString; +import lombok.experimental.SuperBuilder; + +/** + * Kafka Producer를 통해 발행하는 공통 Dto 클래스 + */ +@Getter +@ToString +@SuperBuilder +public abstract class MessageDto { + protected String title; +} diff --git a/src/main/java/com/devspacehub/ast/infra/kafka/producer/MessageProducer.java b/src/main/java/com/devspacehub/ast/infra/kafka/producer/MessageProducer.java new file mode 100644 index 0000000..3549176 --- /dev/null +++ b/src/main/java/com/devspacehub/ast/infra/kafka/producer/MessageProducer.java @@ -0,0 +1,54 @@ +/* + © 2024 devspacehub, Inc. All rights reserved. + + name : MessageProducer + creation : 2024.8.30 + author : Yoonji Moon + */ + +package com.devspacehub.ast.infra.kafka.producer; + +import com.devspacehub.ast.infra.kafka.dto.MessageDto; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.kafka.clients.producer.ProducerRecord; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.stereotype.Component; + +/** + * 주식 결과 (주문 결과, 체결 결과 등) 메시지 발행하는 Producer + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class MessageProducer { + private final KafkaTemplate template; + + /** + * 메세지를 발행한다. + * @param topic 데이터의 토픽 + * @param message MessageDto 상속하는 메세지 Dto 클래스 + */ + public void produce(String topic, T message) { + try { + template.send(this.createRecord(topic, message)); + } catch (Exception e) { + log.error(String.format("cause: %s, result: failed to produce '%s' message.", e.getMessage(), message.getTitle()), e); + } finally { + template.flush(); + } + + } + + /** + * 메세지 발행 위해 ProducerRecord 생성한다. + * @param topic 데이터의 토픽 + * @param message MessageDto 상속하는 메세지 Dto 클래스 + * @return ProducerRecord + */ + private ProducerRecord createRecord(String topic, T message) { + log.info("produce message={}, topic={}", message, topic); + + return new ProducerRecord<>(topic, message); + } +} \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index da8e901..9672c83 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -11,6 +11,9 @@ spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver + url: ${DB_URL} + username: ${DB_USERNAME} + password: ${DB_PASSWORD} server: port: 8081 @@ -22,17 +25,18 @@ notify: discord-webhook: url: https://discord.com/api/webhooks/1200783808279101572/58SyrsR6ZnWmbPCBDll6ONWAqY7AMZIV15V5L3g2Grb_KQtNkkJrZSaTuNqOBWLlUv3d +kafka: + producer: + bootstrap-server: + host: localhost + port: 9092 + --- spring: config: activate: on-profile: local - datasource: - url: ${DB_URL} - username: ${DB_USERNAME} - password: ${DB_PASSWORD} - properties: hibernate: format_sql: true @@ -59,11 +63,6 @@ spring: activate: on-profile: beta - datasource: - url: ${DB_URL} - username: ${DB_USERNAME} - password: ${DB_PASSWORD} - properties: hibernate: format_sql: true @@ -85,11 +84,6 @@ spring: activate: on-profile: prod - datasource: - url: ${DB_URL} - username: ${DB_USERNAME} - password: ${DB_PASSWORD} - properties: hibernate: show_sql: false @@ -105,8 +99,4 @@ server: logging: level: org.hibernate: - SQL: error - -notify: - discord-webhook: - url: https://discord.com/api/webhooks/1207310185216081960/kMoNygVLXY-0Gth4gz1LP8Nobkt9aa50z0CENUCHKw9jDEFNZmJXev8VVYwUzibfbRmQ \ No newline at end of file + SQL: error \ No newline at end of file diff --git a/src/test/java/com/devspacehub/ast/common/constant/ProfileTypeTest.java b/src/test/java/com/devspacehub/ast/common/constant/ProfileTypeTest.java index 3db88e4..c039494 100644 --- a/src/test/java/com/devspacehub/ast/common/constant/ProfileTypeTest.java +++ b/src/test/java/com/devspacehub/ast/common/constant/ProfileTypeTest.java @@ -12,18 +12,17 @@ import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; class ProfileTypeTest { @DisplayName("시스템 프로퍼티로 설정한 값을 가져와서 어떤 환경인지 확인한다.") @Test void getActiveProfile() { //given - System.setProperty("spring.profiles.active", "local"); + System.setProperty("spring.profiles.active", "test"); // when - String activeProfile = ProfileType.getActiveProfile(); + ProfileType result = ProfileType.getActiveProfileType(); // then - assertThat(activeProfile).isEqualTo("local"); + assertThat(result).isEqualTo(ProfileType.TEST); } diff --git a/src/test/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/OverseasReservationBuyOrderServiceTest.java b/src/test/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/OverseasReservationBuyOrderServiceTest.java index 5ecc5ba..3e709d6 100644 --- a/src/test/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/OverseasReservationBuyOrderServiceTest.java +++ b/src/test/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/OverseasReservationBuyOrderServiceTest.java @@ -16,7 +16,7 @@ import com.devspacehub.ast.domain.my.reservationOrderInfo.ReservationOrderInfoRepository; import com.devspacehub.ast.domain.my.reservationOrderInfo.dto.ReservationStockItem; import com.devspacehub.ast.domain.notification.Notificator; -import com.devspacehub.ast.domain.notification.dto.MessageContentDto; +import com.devspacehub.ast.domain.notification.dto.DefaultItemInfoDto; import com.devspacehub.ast.domain.orderTrading.OrderTrading; import com.devspacehub.ast.domain.orderTrading.OrderTradingRepository; import com.devspacehub.ast.domain.orderTrading.dto.OverseasStockOrderApiReqDto; @@ -197,11 +197,11 @@ void throw_InsufficientMoneyException_when_myDeposit_is_lessThan_totalOrderPrice void call_notificator_when_orderResult_is_success() { OrderTrading orderTrading = OrderTrading.builder().itemCode("AAPL").itemNameKor("애플").orderQuantity(1).orderPrice(new BigDecimal("29.000")) .orderNumber("193284932").orderTime("090018").build(); - willDoNothing().given(notificator).sendMessage(any(MessageContentDto.OrderResult.class)); + willDoNothing().given(notificator).sendStockResultMessage(any(DefaultItemInfoDto.ItemOrderResultDto.class)); overseasReservationBuyOrderService.orderApiResultProcess(orderTrading); - verify(notificator).sendMessage(any(MessageContentDto.OrderResult.class)); + verify(notificator).sendStockResultMessage(any(DefaultItemInfoDto.ItemOrderResultDto.class)); } @DisplayName("주문가가 0원일 때 InvalidValueException이 발생한다.") diff --git a/src/test/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/ReservationBuyOrderServiceIntegrationTest.java b/src/test/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/ReservationBuyOrderServiceIntegrationTest.java index 8faa651..fd2c124 100644 --- a/src/test/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/ReservationBuyOrderServiceIntegrationTest.java +++ b/src/test/java/com/devspacehub/ast/domain/my/reservationOrderInfo/service/ReservationBuyOrderServiceIntegrationTest.java @@ -21,12 +21,12 @@ import com.devspacehub.ast.domain.my.service.MyServiceFactory; import com.devspacehub.ast.domain.my.service.MyServiceImpl; import com.devspacehub.ast.domain.notification.Notificator; -import com.devspacehub.ast.domain.notification.dto.MessageContentDto; import com.devspacehub.ast.domain.orderTrading.OrderTrading; import com.devspacehub.ast.domain.orderTrading.OrderTradingRepository; import com.devspacehub.ast.domain.orderTrading.dto.DomesticStockOrderExternalReqDto; import com.devspacehub.ast.domain.orderTrading.dto.StockOrderApiResDto; import com.devspacehub.ast.domain.orderTrading.service.CurrentStockPriceInfoBuilder; +import com.devspacehub.ast.infra.kafka.dto.MessageDto; import com.devspacehub.ast.util.OpenApiRequest; import jakarta.persistence.EntityNotFoundException; import org.junit.jupiter.api.DisplayName; @@ -118,7 +118,7 @@ void order() { given(myService.getBuyOrderPossibleCash(any(MyServiceRequestDto.Domestic.class))).willReturn(BigDecimal.valueOf(10000)); given(marketStatusService.getCurrentStockPrice(givenEntity.getItemCode())).willReturn(currentStockPriceResponseOutput); - doNothing().when(notificator).sendMessage(any(MessageContentDto.class)); + doNothing().when(notificator).sendStockResultMessage(any(MessageDto.class)); // when reservationBuyOrderService.order(openApiProperties, openApiType); diff --git a/src/test/java/com/devspacehub/ast/domain/notification/NotificatorTest.java b/src/test/java/com/devspacehub/ast/domain/notification/NotificatorTest.java new file mode 100644 index 0000000..52e3225 --- /dev/null +++ b/src/test/java/com/devspacehub/ast/domain/notification/NotificatorTest.java @@ -0,0 +1,51 @@ +/* + © 2024 devspacehub, Inc. All rights reserved. + + name : NotificatorTest + creation : 2024.9.22 + author : Yoonji Moon + */ + +package com.devspacehub.ast.domain.notification; + +import com.devspacehub.ast.common.constant.OpenApiType; +import com.devspacehub.ast.domain.notification.dto.ItemConclusionResultDto; +import com.devspacehub.ast.infra.kafka.producer.MessageProducer; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.math.BigDecimal; + +import static org.mockito.Mockito.only; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class NotificatorTest { + @InjectMocks + Notificator notificator; + @Mock + MessageProducer messageProducer; + + @DisplayName("토픽명과 메세지 데이터를 전달하여 메세지 발송을 요청한다.") + @Test + void sendStockResultMessage() { + System.setProperty("spring.profiles.active", "test"); + ItemConclusionResultDto given = ItemConclusionResultDto.builder() + .openApiType(OpenApiType.OVERSEAS_ORDER_CONCLUSION_FIND) + .itemCode("AAPL") + .orderTime("105010") + .orderPrice(BigDecimal.valueOf(228.20)) + .orderQuantity(2) + .concludedPrice(BigDecimal.valueOf(228.20)) + .concludedQuantity(2) + .build(); + + notificator.sendStockResultMessage(given); + + verify(messageProducer, only()).produce("Stock-Result-Notification_TEST", given); + } +} \ No newline at end of file diff --git a/src/test/java/com/devspacehub/ast/domain/notification/dto/MessageContentDtoTest.java b/src/test/java/com/devspacehub/ast/domain/notification/dto/MessageContentDtoTest.java deleted file mode 100644 index 5148552..0000000 --- a/src/test/java/com/devspacehub/ast/domain/notification/dto/MessageContentDtoTest.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - © 2024 devspacehub, Inc. All rights reserved. - - name : MessageContentDtoTest - creation : 2024.4.25 - author : Yoonji Moon - */ - -package com.devspacehub.ast.domain.notification.dto; - -import com.devspacehub.ast.common.constant.OpenApiType; -import com.devspacehub.ast.common.constant.ProfileType; -import com.devspacehub.ast.domain.orderTrading.OrderTrading; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import java.math.BigDecimal; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; - -/** - * MessageContentDto 테스트 코드. - */ -class MessageContentDtoTest { - - @DisplayName("체결 결과 DTO를 이용해 디스코드 메시지 전송 DTO를 생성한다.") - @Test - void createMessageTest_conclusion() { - // given - MessageContentDto.ConclusionResult orderResult = MessageContentDto.ConclusionResult.builder() - .title("체결 완료") - .accountStatusKor("모의") - .itemNameKor("삼성전자") - .itemCode("005930") - .openApiType(OpenApiType.DOMESTIC_ORDER_CONCLUSION_FIND) - .orderQuantity(10) - .orderPrice(BigDecimal.valueOf(80000)) - .orderNumber("0123456") - .orderTime("090130") - .concludedQuantity(10) - .concludedPrice(BigDecimal.valueOf(80000)) - .build(); - // when - String message = orderResult.createMessage(orderResult); - // then - assertEquals(""" - **[090130] 체결 완료** - 계좌 상태 : 모의 - 종목명 : 삼성전자 (005930) - 매매구분 : 국내 주식 일별주문 체결 조회 - 주문번호 : 0123456 - 주문수량 : 10주 - 주문단가 : 80000 - 체결수량 : 10주 - 체결금액 : 80000""", message); - } - - @DisplayName("매수 주문 결과 DTO를 이용해 디스코드 메시지 전송 DTO를 생성한다.") - @Test - void createMessageTest_orderResult() { - // given - MessageContentDto.OrderResult orderResult = MessageContentDto.OrderResult.builder() - .title("주문 완료") - .accountStatusKor("실전") - .itemNameKor("삼성전자") - .itemCode("005930") - .openApiType(OpenApiType.DOMESTIC_STOCK_BUY_ORDER) - .orderQuantity(10) - .orderPrice(BigDecimal.valueOf(80000)) - .orderNumber("0123456") - .orderTime("090130") - .build(); - // when - String message = orderResult.createMessage(orderResult); - // then - assertEquals(""" - **[090130] 주문 완료** - 계좌 상태 : 실전 - 종목명 : 삼성전자 (005930) - 매매구분 : 국내 주식(현금)-매수 - 주문번호 : 0123456 - 주문수량 : 10주 - 주문단가 : 80000""", message); - } - @DisplayName("OpenApiType, 운영 상태 값, 주문 결과를 전달하여 디스코드 메세지 > 주문 결과 Dto를 생성한다.") - @Test - void orderResultTest_fromOne() { - // given - System.setProperty("spring.profiles.active", "test"); - OpenApiType givenOpenApiType = OpenApiType.OVERSEAS_STOCK_SELL_ORDER; - OrderTrading givenOrderTrading = OrderTrading.builder() - .itemCode("AAPL") - .itemNameKor("애플") - .orderPrice(new BigDecimal("134.02")) - .orderNumber("111111") - .orderQuantity(1) - .orderTime("093030") - .build(); - // when - MessageContentDto.OrderResult result = MessageContentDto.OrderResult.fromOne( - givenOpenApiType, ProfileType.getAccountStatus(), givenOrderTrading); - // then - assertThat(result.getTitle()).isEqualTo("주문 완료"); - assertThat(result.getItemNameKor()).isEqualTo("애플"); - assertThat(result.getOrderPrice()).isEqualTo(new BigDecimal("134.02")); - assertThat(result.getAccountStatusKor()).isEqualTo("모의"); - assertThat(result.getOpenApiType()).isEqualTo(givenOpenApiType); - } -} \ No newline at end of file diff --git a/src/test/java/com/devspacehub/ast/infra/kafka/config/KafkaTopicTypeTest.java b/src/test/java/com/devspacehub/ast/infra/kafka/config/KafkaTopicTypeTest.java new file mode 100644 index 0000000..04b6d30 --- /dev/null +++ b/src/test/java/com/devspacehub/ast/infra/kafka/config/KafkaTopicTypeTest.java @@ -0,0 +1,26 @@ +/* + © 2024 devspacehub, Inc. All rights reserved. + + name : KafkaTopicTypeTest + creation : 2024.9.25 + author : Yoonji Moon + */ + +package com.devspacehub.ast.infra.kafka.config; + +import com.devspacehub.ast.common.constant.ProfileType; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class KafkaTopicTypeTest { + + @DisplayName("환경 ProfileType에 따라 적절한 topic 이름을 반환한다.") + @Test + void return_topicName_by_profileType() { + String result = KafkaTopicType.STOCK_RESULT_NOTI_TOPIC.getValue(ProfileType.TEST); + + assertThat(result).isEqualTo("Stock-Result-Notification_TEST"); + } +} \ No newline at end of file diff --git a/src/test/java/com/devspacehub/ast/infra/kafka/producer/MessageProducerIntegrationTest.java b/src/test/java/com/devspacehub/ast/infra/kafka/producer/MessageProducerIntegrationTest.java new file mode 100644 index 0000000..a1bd636 --- /dev/null +++ b/src/test/java/com/devspacehub/ast/infra/kafka/producer/MessageProducerIntegrationTest.java @@ -0,0 +1,44 @@ +/* + © 2024 devspacehub, Inc. All rights reserved. + + name : MessageProducerIntegrationTest + creation : 2024.9.20 + author : Yoonji Moon + */ + +package com.devspacehub.ast.infra.kafka.producer; + +import com.devspacehub.ast.common.constant.OpenApiType; +import com.devspacehub.ast.domain.notification.dto.DefaultItemInfoDto; +import com.devspacehub.ast.infra.kafka.dto.MessageDto; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.math.BigDecimal; + +@SpringBootTest +@ActiveProfiles("local") +class MessageProducerIntegrationTest { + @Autowired + MessageProducer messageProducer; + + // TODO 발행된 데이터를 확인하는 방법 있을지 확인 필요 + @DisplayName("주문 결과 데이터를 발행한다.") + @Test + void produce() { + MessageDto given = DefaultItemInfoDto.ItemOrderResultDto.ItemOrderResultDto.builder() + .title("주문 결과 테스트") + .openApiType(OpenApiType.DOMESTIC_STOCK_BUY_ORDER) + .itemCode("005930") + .orderTime("090100") + .orderPrice(BigDecimal.valueOf(63000)) + .orderQuantity(2) + .build(); + + messageProducer.produce("Stock Result Notification_TEST", given); + } + +} \ No newline at end of file