Skip to content

Commit

Permalink
[fix] 알림 수정 (#344)
Browse files Browse the repository at this point in the history
* #333 feat: 종목 지정가 알림 로직 변경

- 알림 발송 -> 알림 저장이 아닌 알림 저장 -> 알림 발송으로 변경
- 알림 발송에 실패해도 알림 저장이 되도록 변경

* #343 feat: 포트폴리오 알림 로직 수정

- 알림 발송 -> 알림 저장이 아닌 알림 저장 -> 알림 발송으로 변경
- 알림 발송에 실패해도 알림 저장이 되도록 변경

* #343 fix: kis 프로퍼티명 버그 수정
  • Loading branch information
yonghwankim-dev authored May 28, 2024
1 parent 39b8a4c commit b48ce4e
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 61 deletions.
2 changes: 1 addition & 1 deletion secret
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;

import codesquad.fineants.global.errors.exception.KisException;
import codesquad.fineants.domain.kis.properties.OauthKisProperties;
import codesquad.fineants.domain.kis.domain.dto.response.KisClosingPrice;
import codesquad.fineants.domain.kis.domain.dto.response.KisDividend;
import codesquad.fineants.domain.kis.domain.dto.response.KisDividendWrapper;
import codesquad.fineants.domain.kis.properties.OauthKisProperties;
import codesquad.fineants.global.errors.exception.KisException;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;
import reactor.util.retry.Retry;
Expand Down Expand Up @@ -52,12 +52,12 @@ public Mono<KisAccessToken> fetchAccessToken() {
requestBodyMap.put("appsecret", oauthKisProperties.getSecretkey());
return webClient
.post()
.uri(oauthKisProperties.getTokenURI())
.uri(oauthKisProperties.getTokenUrl())
.bodyValue(requestBodyMap)
.retrieve()
.onStatus(HttpStatus::isError, this::handleError)
.bodyToMono(KisAccessToken.class)
.retryWhen(Retry.fixedDelay(Long.MAX_VALUE, Duration.ofMinutes(1)))
.retryWhen(Retry.fixedDelay(Long.MAX_VALUE, Duration.ofSeconds(5)))
.log();
}

Expand All @@ -74,7 +74,7 @@ public Mono<KisCurrentPrice> fetchCurrentPrice(String tickerSymbol, String autho
queryParamMap.add("fid_input_iscd", tickerSymbol);

return performGet(
oauthKisProperties.getCurrentPriceURI(),
oauthKisProperties.getCurrentPriceUrl(),
headerMap,
queryParamMap,
KisCurrentPrice.class
Expand All @@ -98,7 +98,7 @@ public Mono<KisClosingPrice> fetchClosingPrice(String tickerSymbol, String autho
queryParamMap.add("FID_ORG_ADJ_PRC", "0");

return performGet(
oauthKisProperties.getLastDayClosingPriceURI(),
oauthKisProperties.getClosingPriceUrl(),
headerMap,
queryParamMap,
KisClosingPrice.class
Expand All @@ -124,7 +124,7 @@ public String fetchDividend(String tickerSymbol, String authorization) {
queryParamMap.add("SHT_CD", tickerSymbol);

return performGet(
oauthKisProperties.getDividendURI(),
oauthKisProperties.getDividendUrl(),
headerMap,
queryParamMap,
String.class,
Expand All @@ -150,7 +150,7 @@ public List<KisDividend> fetchDividendAll(LocalDate from, LocalDate to, String a
queryParamMap.add("SHT_CD", Strings.EMPTY);

KisDividendWrapper result = performGet(
oauthKisProperties.getDividendURI(),
oauthKisProperties.getDividendUrl(),
headerMap,
queryParamMap,
KisDividendWrapper.class,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,19 @@ public class OauthKisProperties {

private final String appkey;
private final String secretkey;
private final String tokenURI;
private final String currentPriceURI;
private final String lastDayClosingPriceURI;
private final String dividendURI;
private final String tokenUrl;
private final String currentPriceUrl;
private final String closingPriceUrl;
private final String dividendUrl;

@ConstructorBinding
public OauthKisProperties(String appkey, String secretkey, String tokenURI, String currentPriceURI,
String lastDayClosingPriceURI, String dividendURI) {
public OauthKisProperties(String appkey, String secretkey, String tokenUrl, String currentPriceUrl,
String closingPriceUrl, String dividendUrl) {
this.appkey = appkey;
this.secretkey = secretkey;
this.tokenURI = tokenURI;
this.currentPriceURI = currentPriceURI;
this.lastDayClosingPriceURI = lastDayClosingPriceURI;
this.dividendURI = dividendURI;
this.tokenUrl = tokenUrl;
this.currentPriceUrl = currentPriceUrl;
this.closingPriceUrl = closingPriceUrl;
this.dividendUrl = dividendUrl;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.apache.logging.log4j.util.Strings;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand Down Expand Up @@ -94,7 +96,7 @@ public PortfolioNotifyMessagesResponse notifyTargetGain() {
List<Portfolio> portfolios = portfolioRepository.findAllWithAll().stream()
.peek(portfolio -> portfolio.applyCurrentPriceAllHoldingsBy(currentPriceRepository))
.collect(Collectors.toList());
Function<PortfolioNotifyMessageItem, PortfolioNotifyMessageItem> sentFunction = item -> {
Function<PortfolioNotificationResponse, PortfolioNotificationResponse> sentFunction = item -> {
sentManager.addTargetGainSendHistory(Long.valueOf(item.getReferenceId()));
return item;
};
Expand All @@ -108,7 +110,7 @@ public PortfolioNotifyMessagesResponse notifyTargetGainBy(Long portfolioId) {
.peek(p -> p.applyCurrentPriceAllHoldingsBy(currentPriceRepository))
.findFirst()
.orElseThrow(() -> new FineAntsException(PortfolioErrorCode.NOT_FOUND_PORTFOLIO));
Function<PortfolioNotifyMessageItem, PortfolioNotifyMessageItem> sentFunction = item -> {
Function<PortfolioNotificationResponse, PortfolioNotificationResponse> sentFunction = item -> {
sentManager.addTargetGainSendHistory(Long.valueOf(item.getReferenceId()));
return item;
};
Expand All @@ -121,7 +123,7 @@ public PortfolioNotifyMessagesResponse notifyMaxLoss() {
List<Portfolio> portfolios = portfolioRepository.findAllWithAll().stream()
.peek(portfolio -> portfolio.applyCurrentPriceAllHoldingsBy(currentPriceRepository))
.collect(Collectors.toList());
Function<PortfolioNotifyMessageItem, PortfolioNotifyMessageItem> sentFunction = item -> {
Function<PortfolioNotificationResponse, PortfolioNotificationResponse> sentFunction = item -> {
sentManager.addMaxLossSendHistory(Long.valueOf(item.getReferenceId()));
return item;
};
Expand All @@ -135,7 +137,7 @@ public PortfolioNotifyMessagesResponse notifyMaxLoss(Long portfolioId) {
.stream().peek(p -> p.applyCurrentPriceAllHoldingsBy(currentPriceRepository))
.findAny()
.orElseThrow(() -> new FineAntsException(PortfolioErrorCode.NOT_FOUND_PORTFOLIO));
Function<PortfolioNotifyMessageItem, PortfolioNotifyMessageItem> sentFunction = item -> {
Function<PortfolioNotificationResponse, PortfolioNotificationResponse> sentFunction = item -> {
sentManager.addMaxLossSendHistory(Long.valueOf(item.getReferenceId()));
return item;
};
Expand All @@ -145,7 +147,7 @@ public PortfolioNotifyMessagesResponse notifyMaxLoss(Long portfolioId) {
private PortfolioNotifyMessagesResponse notifyMessage(
List<Portfolio> portfolios,
NotificationPolicy<Portfolio> policy,
Function<PortfolioNotifyMessageItem, PortfolioNotifyMessageItem> sentFunction) {
Function<PortfolioNotificationResponse, PortfolioNotificationResponse> sentFunction) {
// 포트폴리오별 FCM 토큰 리스트맵 생성
Map<Portfolio, List<String>> fcmTokenMap = portfolios.stream()
.collect(Collectors.toMap(
Expand All @@ -162,28 +164,35 @@ private PortfolioNotifyMessagesResponse notifyMessage(
}
}

// 알림 저장
List<PortfolioNotificationResponse> portfolioNotificationResponses = notifyMessages.stream()
.distinct()
.map(message -> this.saveNotification(PortfolioNotificationRequest.from(message)))
// 발송 이력 저장
.map(sentFunction)
.collect(Collectors.toList());
log.debug("portfolioNotificationResponses : {}", portfolioNotificationResponses);

// 알림 전송
Map<String, String> messageIdMap = new HashMap<>();
List<SentNotifyMessage> sentNotifyMessages = new ArrayList<>();
for (NotifyMessage notifyMessage : notifyMessages) {
firebaseMessagingService.send(notifyMessage.toMessage())
.ifPresentOrElse(
messageId -> sentNotifyMessages.add(SentNotifyMessage.create(notifyMessage, messageId)),
messageId -> {
messageIdMap.put(notifyMessage.getReferenceId(), messageId);
sentNotifyMessages.add(SentNotifyMessage.create(notifyMessage, messageId));
},
() -> fcmService.deleteToken(notifyMessage.getToken()));
}
log.info("포트폴리오 알림 전송 결과, sentNotifyMessage={}", sentNotifyMessages);

List<PortfolioNotifyMessageItem> items = sentNotifyMessages.stream()
.distinct()
// 알림 저장
.map(message -> {
PortfolioNotificationResponse response = this.saveNotification(
PortfolioNotificationRequest.from(message.getNotifyMessage()));
String messageId = message.getMessageId();
List<PortfolioNotifyMessageItem> items = portfolioNotificationResponses.stream()
.map(response -> {
String messageId = messageIdMap.getOrDefault(response.getReferenceId(), Strings.EMPTY);
return PortfolioNotifyMessageItem.from(response, messageId);
})
// 발송 이력 저장
.map(sentFunction)
.collect(Collectors.toList());
log.debug("notifyMessage : {}", items);
return PortfolioNotifyMessagesResponse.create(items);
}

Expand All @@ -195,7 +204,7 @@ public TargetPriceNotifyMessageResponse notifyTargetPriceBy(List<String> tickerS
.map(StockTargetPrice::getTargetPriceNotifications)
.flatMap(Collection::stream)
.collect(Collectors.toList());
Function<TargetPriceNotifyMessageItem, TargetPriceNotifyMessageItem> sentFunction = item -> {
Function<TargetPriceNotificationResponse, TargetPriceNotificationResponse> sentFunction = item -> {
sentManager.addTargetPriceSendHistory(item.getTargetPriceNotificationId());
return item;
};
Expand All @@ -215,7 +224,7 @@ public TargetPriceNotifyMessageResponse notifyTargetPriceBy(Long memberId) {
.flatMap(Collection::stream)
.collect(Collectors.toList());

Function<TargetPriceNotifyMessageItem, TargetPriceNotifyMessageItem> sentFunction = item -> {
Function<TargetPriceNotificationResponse, TargetPriceNotificationResponse> sentFunction = item -> {
sentManager.addTargetPriceSendHistory(item.getTargetPriceNotificationId());
return item;
};
Expand All @@ -229,7 +238,7 @@ public TargetPriceNotifyMessageResponse notifyTargetPriceBy(Long memberId) {
private TargetPriceNotifyMessageResponse notifyTargetPrice(
List<TargetPriceNotification> targetPrices,
NotificationPolicy<TargetPriceNotification> policy,
Function<TargetPriceNotifyMessageItem, TargetPriceNotifyMessageItem> sentFunction) {
Function<TargetPriceNotificationResponse, TargetPriceNotificationResponse> sentFunction) {
log.debug("종목 지정가 알림 발송 서비스 시작, targetPrices={}", targetPrices);

// 지정가 알림별 FCM 토큰 리스트맵 생성
Expand All @@ -249,30 +258,39 @@ private TargetPriceNotifyMessageResponse notifyTargetPrice(
}
log.debug("notifyMessage : {}", notifyMessages);

// 알림 저장
List<TargetPriceNotificationResponse> notificationSaveResponse = notifyMessages.stream()
.distinct()
// 알림 저장
.map(message -> this.saveNotification(StockNotificationRequest.from(message)))
// 발송 이력 저장
.map(sentFunction)
.sorted(Comparator.comparing(TargetPriceNotificationResponse::getReferenceId))
.collect(Collectors.toList());
log.debug("종목 지정가 알림 발송 서비스 결과, notificationSaveResponse={}", notificationSaveResponse);

// 알림 전송
Map<String, String> messageIdMap = new HashMap<>();
List<SentNotifyMessage> sentNotifyMessages = new ArrayList<>();
for (NotifyMessage notifyMessage : notifyMessages) {
firebaseMessagingService.send(notifyMessage.toMessage())
.ifPresentOrElse(
messageId -> sentNotifyMessages.add(SentNotifyMessage.create(notifyMessage, messageId)),
messageId -> {
messageIdMap.put(notifyMessage.getReferenceId(), messageId);
sentNotifyMessages.add(SentNotifyMessage.create(notifyMessage, messageId));
},
() -> fcmService.deleteToken(notifyMessage.getToken()));
}
log.info("종목 지정가 FCM 알림 발송 결과, sentNotifyMessage={}", sentNotifyMessages);

// 알림 저장
List<TargetPriceNotifyMessageItem> items = sentNotifyMessages.stream()
.distinct()
// 알림 저장
.map(message -> {
TargetPriceNotificationResponse response = this.saveNotification(
StockNotificationRequest.from(message.getNotifyMessage()));
String messageId = message.getMessageId();
// 응답 리스폰스 생성
List<TargetPriceNotifyMessageItem> items = notificationSaveResponse.stream()
.map(response -> {
String messageId = messageIdMap.getOrDefault(response.getReferenceId(), Strings.EMPTY);
return TargetPriceNotifyMessageItem.from(response, messageId);
})
// 발송 이력 저장
.map(sentFunction)
.sorted(Comparator.comparing(TargetPriceNotifyMessageItem::getReferenceId))
.collect(Collectors.toList());
log.debug("종목 지정가 알림 발송 서비스 결과, items={}", items);

return TargetPriceNotifyMessageResponse.from(items);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package codesquad.fineants.domain.stock_target_price.domain.dto.response;

import codesquad.fineants.domain.common.money.Money;
import codesquad.fineants.domain.notification.domain.entity.type.NotificationType;
import codesquad.fineants.domain.notification.domain.dto.response.TargetPriceNotificationResponse;
import codesquad.fineants.domain.notification.domain.entity.type.NotificationType;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/application-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ spring:
datasource:
hikari:
leak-detection-threshold: 2000
connection-timeout: 30000
jpa:
database: mysql
database-platform: org.hibernate.dialect.MySQL8Dialect
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import codesquad.fineants.domain.fcm_token.domain.entity.FcmToken;
import codesquad.fineants.domain.fcm_token.repository.FcmRepository;
import codesquad.fineants.domain.fcm_token.service.FirebaseMessagingService;
import codesquad.fineants.domain.kis.aop.AccessTokenAspect;
import codesquad.fineants.domain.kis.client.KisCurrentPrice;
import codesquad.fineants.domain.kis.repository.CurrentPriceRepository;
import codesquad.fineants.domain.kis.service.KisService;
Expand Down Expand Up @@ -111,6 +112,9 @@ class NotificationServiceTest extends AbstractContainerBaseTest {
@MockBean
private FirebaseMessagingService firebaseMessagingService;

@MockBean
private AccessTokenAspect accessTokenAspect;

@AfterEach
void tearDown() {
notificationRepository.deleteAllInBatch();
Expand Down Expand Up @@ -197,7 +201,7 @@ void notifyTargetGainBy_whenNoTargetGain_thenNotSendNotification() {
);
}

@DisplayName("토큰이 유효하지 않아서 목표 수익률 알림을 보낼수 없다")
@DisplayName("토큰이 유효하지 않아서 목표 수익률 알림을 보낼수 없지만, 알림은 저장된다")
@Test
void notifyTargetGainBy_whenInvalidFcmToken_thenDeleteFcmToken() {
// given
Expand Down Expand Up @@ -230,7 +234,7 @@ void notifyTargetGainBy_whenInvalidFcmToken_thenDeleteFcmToken() {

// then
assertAll(
() -> assertThat(response.getNotifications()).isEmpty(),
() -> assertThat(response.getNotifications()).hasSize(1),
() -> assertThat(fcmRepository.findById(fcmToken.getId())).isEmpty()
);
}
Expand Down Expand Up @@ -313,7 +317,7 @@ void notifyMaxLoss_whenNotifySettingIsInActive_thenResponseEmptyList(boolean bro
);
}

@DisplayName("토큰이 유효하지 않아서 최대 손실율 달성 알림을 보낼수 없다")
@DisplayName("토큰이 유효하지 않아서 최대 손실율 달성 알림을 보낼수 없지만, 알림은 저장된다")
@Test
void notifyMaxLoss_whenInvalidFcmToken_thenDeleteFcmToken() throws FirebaseMessagingException {
// given
Expand Down Expand Up @@ -346,7 +350,7 @@ void notifyMaxLoss_whenInvalidFcmToken_thenDeleteFcmToken() throws FirebaseMessa

// then
assertAll(
() -> assertThat(response.getNotifications()).isEmpty(),
() -> assertThat(response.getNotifications()).hasSize(1),
() -> assertThat(fcmRepository.findById(fcmToken.getId())).isEmpty()
);
}
Expand Down Expand Up @@ -564,9 +568,9 @@ void notifyTargetPrice_whenExistNotification_thenNotSentNotification() {
.hasSize(2);
}

@DisplayName("종목 지정가 도달 알림을 보내는데 실패하면 알림을 저장하지 않아야 한다")
@DisplayName("종목 지정가 도달 알림을 보내는데 실패해도 알림은 저장되어야 한다")
@Test
void notifyTargetPrice_whenFailSendingNotification_thenNotSaveNotification() {
void notifyTargetPrice_whenFailSendingNotification_thenSaveNotification() {
// given
Member member = memberRepository.save(createMember());
notificationPreferenceRepository.save(NotificationPreference.builder()
Expand All @@ -591,13 +595,12 @@ void notifyTargetPrice_whenFailSendingNotification_thenNotSaveNotification() {
List.of(stock.getTickerSymbol()));

// then
NotificationType type = NotificationType.STOCK_TARGET_PRICE;
assertThat(response.getNotifications())
.asList()
.isEmpty();
.hasSize(1);
assertThat(notificationRepository.findAllByMemberId(member.getId()))
.asList()
.hasSize(0);
.hasSize(1);
}

@DisplayName("티커 심볼을 기준으로 종목 지정가 알림을 발송한다")
Expand Down
Loading

0 comments on commit b48ce4e

Please sign in to comment.