Skip to content

Commit 1860c74

Browse files
authored
[FIX/#103] 추천 로직 오류 수정 (#106)
## 🔗 Related Issue <!-- 이슈 번호를 작성하여 종료시켜주세요 --> - Closes #103 ## 📝 Summary <!-- 작업한 기능을 설명해주세요 --> - 추천 로직 비동기 전환 - 응답 구조 개선 및 에러 핸들링 강화 - DTO 구조 개선 - 추천 업데이트 로직을 금융 mbti 내부로 통합 ## 🔄 Changes <!-- 구체적으로 어떤 파일/로직이 변경되었는지 체크해주세요 --> - [X] API 변경 (추가/수정) - [ ] 데이터 및 도메인 변경 (DB, 비즈니스 로직) - [ ] 설정 또는 인프라 관련 변경 - [ ] 리팩토링 ## 💬 Questions & Review Points <!-- 리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요 --> ## 📸 API Test Results (Swagger) <!-- API 테스트 스크린샷 첨부 --> ## ✅ Checklist - [ ] API 테스트 완료 - [ ] 테스트 결과 사진 첨부 - [ ] 빌드 성공 확인 (./gradlew build)
1 parent cb64cb0 commit 1860c74

10 files changed

Lines changed: 136 additions & 491 deletions

File tree

src/main/java/org/umc/valuedi/domain/mbti/service/FinanceMbtiService.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.umc.valuedi.domain.mbti.service;
22

33
import lombok.RequiredArgsConstructor;
4+
import lombok.extern.slf4j.Slf4j;
45
import org.springframework.stereotype.Service;
56
import org.springframework.transaction.annotation.Transactional;
67
import org.umc.valuedi.domain.mbti.converter.FinanceMbtiTestConverter;
@@ -12,11 +13,13 @@
1213
import org.umc.valuedi.domain.mbti.validator.FinanceMbtiTestValidator;
1314
import org.umc.valuedi.domain.member.entity.Member;
1415
import org.umc.valuedi.domain.member.repository.MemberRepository;
16+
import org.umc.valuedi.domain.savings.service.RecommendationService;
1517

1618
import java.util.List;
1719
import java.util.Map;
1820
import java.util.stream.Collectors;
1921

22+
@Slf4j
2023
@Service
2124
@RequiredArgsConstructor
2225
@Transactional
@@ -28,6 +31,7 @@ public class FinanceMbtiService {
2831
private final FinanceMbtiScoringService scoringService;
2932
private final FinanceMbtiTestValidator financeMbtiTestValidator;
3033
private final FinanceMbtiTestConverter financeMbtiTestConverter;
34+
private final RecommendationService recommendationService;
3135

3236
public MemberMbtiTest submitTest(Long memberId, FinanceMbtiTestRequestDto req) {
3337

@@ -46,9 +50,17 @@ public MemberMbtiTest submitTest(Long memberId, FinanceMbtiTestRequestDto req) {
4650
));
4751

4852
FinanceMbtiScoringService.ScoreResult score = scoringService.score(activeQuestions, answersByQuestionId);
49-
5053
MemberMbtiTest test = financeMbtiTestConverter.toEntity(member, req, score, activeQuestionMap);
5154

52-
return memberMbtiTestRepository.save(test);
55+
MemberMbtiTest savedTest = memberMbtiTestRepository.save(test);
56+
57+
try {
58+
recommendationService.generateAndSaveRecommendations(memberId);
59+
log.info("[Recommend] MBTI 검사 후 자동 추천 생성 성공. memberId={}", memberId);
60+
} catch (Exception e) {
61+
// 제미나이 호출이 실패해도 MBTI 저장은 유지되도록 예외를 삼키고 로그만 남김
62+
log.error("[Recommend] MBTI 저장에는 성공했으나, 자동 추천 생성 중 오류 발생. memberId={}: {}", memberId, e.getMessage());
63+
}
64+
return savedTest;
5365
}
5466
}

src/main/java/org/umc/valuedi/domain/savings/controller/RecommendationController.java

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,7 @@
33
import lombok.RequiredArgsConstructor;
44
import org.springframework.web.bind.annotation.*;
55
import org.umc.valuedi.domain.savings.dto.response.SavingsResponseDTO;
6-
import org.umc.valuedi.domain.savings.entity.RecommendationBatch;
7-
import org.umc.valuedi.domain.savings.enums.RecommendationStatus;
8-
import org.umc.valuedi.domain.savings.service.RecommendationAsyncWorker;
96
import org.umc.valuedi.domain.savings.service.RecommendationService;
10-
import org.umc.valuedi.domain.savings.service.RecommendationTxService;
117
import org.umc.valuedi.global.apiPayload.ApiResponse;
128
import org.umc.valuedi.global.apiPayload.code.GeneralSuccessCode;
139
import org.umc.valuedi.global.security.annotation.CurrentMember;
@@ -18,28 +14,14 @@
1814
public class RecommendationController implements RecommendationControllerDocs {
1915

2016
private final RecommendationService recommendationService;
21-
private final RecommendationTxService recommendationTxService;
22-
private final RecommendationAsyncWorker recommendationAsyncWorker;
2317

24-
// 추천 생성 트리거(비동기)
18+
// 추천 생성
2519
@PostMapping
26-
public ApiResponse<SavingsResponseDTO.TriggerResponse> recommend(
20+
public ApiResponse<SavingsResponseDTO.SavingsListResponse> recommend(
2721
@CurrentMember Long memberId
2822
) {
29-
SavingsResponseDTO.TriggerDecision triggerDecision = recommendationTxService.triggerRecommendation(memberId);
30-
31-
// 진행 중이면 새로 실행하지 않음
32-
if (triggerDecision.shouldStartAsync()) {
33-
recommendationAsyncWorker.generateAndSaveAsync(memberId, triggerDecision.batchId());
34-
}
35-
36-
return ApiResponse.onSuccess(GeneralSuccessCode.ACCEPTED,
37-
SavingsResponseDTO.TriggerResponse.builder()
38-
.batchId(triggerDecision.batchId())
39-
.status(triggerDecision.status())
40-
.message(triggerDecision.message())
41-
.build()
42-
);
23+
SavingsResponseDTO.SavingsListResponse result = recommendationService.generateAndSaveRecommendations(memberId);
24+
return ApiResponse.onSuccess(GeneralSuccessCode.OK, result);
4325
}
4426

4527
// 최신 추천 15개 조회

src/main/java/org/umc/valuedi/domain/savings/controller/RecommendationControllerDocs.java

Lines changed: 48 additions & 163 deletions
Large diffs are not rendered by default.

src/main/java/org/umc/valuedi/domain/savings/converter/SavingsConverter.java

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,8 @@ public static SavingsResponseDTO.SavingsListResponse toSavingsListResponseDTO(Li
8989

9090
return SavingsResponseDTO.SavingsListResponse.builder()
9191
.totalCount(totalCount)
92-
.maxPageNo(maxPageNo)
9392
.nowPageNo(nowPageNo)
93+
.hasNext(nowPageNo < maxPageNo)
9494
.products(products)
9595
.build();
9696
}
@@ -109,22 +109,31 @@ public static SavingsResponseDTO.SavingsDetailResponse toSavingsDetailResponseDT
109109
))
110110
.toList();
111111

112-
SavingsResponseDTO.SavingsDetailResponse.SavingProductDetail product = new SavingsResponseDTO.SavingsDetailResponse.SavingProductDetail(
113-
savings.getKorCoNm(),
114-
savings.getFinPrdtCd(),
115-
savings.getFinPrdtNm(),
116-
savings.getJoinWay(),
117-
savings.getMtrtInt(),
118-
savings.getSpclCnd(),
119-
savings.getJoinDeny(),
120-
savings.getJoinMember(),
121-
savings.getEtcNote(),
122-
savings.getMaxLimit() == null ? null : String.valueOf(savings.getMaxLimit()),
123-
options
124-
);
112+
SavingsOption representativeOption = savings.getSavingsOptionList().stream()
113+
.filter(o -> Integer.valueOf(12).equals(o.getSaveTrm()))
114+
.findFirst()
115+
.orElseGet(() -> savings.getSavingsOptionList().stream()
116+
.filter(o -> o.getIntrRate2() != null)
117+
.max(java.util.Comparator.comparing(SavingsOption::getIntrRate2))
118+
.orElse(null));
119+
120+
Double basicRate = (representativeOption != null) ? representativeOption.getIntrRate() : null;
121+
Double maxRate = (representativeOption != null) ? representativeOption.getIntrRate2() : null;
125122

126123
return SavingsResponseDTO.SavingsDetailResponse.builder()
127-
.product(product)
124+
.korCoNm(savings.getKorCoNm())
125+
.finPrdtCd(savings.getFinPrdtCd())
126+
.finPrdtNm(savings.getFinPrdtNm())
127+
.basicRate(basicRate)
128+
.maxRate(maxRate)
129+
.joinWay(savings.getJoinWay())
130+
.mtrtInt(savings.getMtrtInt())
131+
.spclCnd(savings.getSpclCnd())
132+
.joinDeny(savings.getJoinDeny())
133+
.joinMember(savings.getJoinMember())
134+
.etcNote(savings.getEtcNote())
135+
.maxLimit(savings.getMaxLimit() == null ? null : String.valueOf(savings.getMaxLimit()))
136+
.options(options)
128137
.build();
129138
}
130139

src/main/java/org/umc/valuedi/domain/savings/dto/response/SavingsResponseDTO.java

Lines changed: 16 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,9 @@ public class SavingsResponseDTO {
1111
@Builder
1212
public record SavingsListResponse(
1313
int totalCount, // 총 상품건수
14-
int maxPageNo, // 총 페이지 건수
15-
int nowPageNo, // 현재 조회 페이지 번호
16-
List<RecommendedSavingProduct> products, // 상품 목록
17-
RecommendationStatus status, // 상품 추천 상태 (PENDING | SUCCESS | FAILED)
18-
String message // 상품 추천 상태 메시지
14+
int nowPageNo, // 현재 페이지
15+
boolean hasNext, // 다음 페이지 존재 여부
16+
List<RecommendedSavingProduct> products // 상품 목록
1917
) {
2018
// 상품 목록 조회
2119
public record RecommendedSavingProduct(
@@ -29,23 +27,20 @@ public record RecommendedSavingProduct(
2927

3028
@Builder
3129
public record SavingsDetailResponse(
32-
SavingProductDetail product // 상품
30+
String korCoNm,
31+
String finPrdtCd,
32+
String finPrdtNm,
33+
Double basicRate,
34+
Double maxRate,
35+
String joinWay,
36+
String mtrtInt,
37+
String spclCnd,
38+
String joinDeny,
39+
String joinMember,
40+
String etcNote,
41+
String maxLimit,
42+
List<Option> options
3343
) {
34-
// 적금 상품 상세 조회
35-
public record SavingProductDetail (
36-
String korCoNm, // 금융회사 명
37-
String finPrdtCd, // 금융상품 코드
38-
String finPrdtNm, // 금융 상품명
39-
String joinWay, // 가입 방법
40-
String mtrtInt, // 만기 후 이자율
41-
String spclCnd, // 우대조건
42-
String joinDeny, // 가입 제한
43-
String joinMember, // 가입대상
44-
String etcNote, // 기타 유의사항
45-
String maxLimit, // 최고한도
46-
List<Option> options
47-
) {}
48-
4944
public record Option (
5045
String intrRateType, // 저축 금리 유형
5146
String intrRateTypeNm, // 저축 금리 유형명
@@ -72,19 +67,4 @@ public record RecommendedProduct(
7267
String rsrvTypeNm,
7368
BigDecimal score
7469
) {}
75-
76-
@Builder
77-
public record TriggerResponse(
78-
Long batchId,
79-
RecommendationStatus status, // PENDING | SUCCESS | FAILED
80-
String message
81-
) {}
82-
83-
@Builder
84-
public record TriggerDecision(
85-
Long batchId,
86-
RecommendationStatus status,
87-
String message,
88-
boolean shouldStartAsync
89-
) {}
9070
}

src/main/java/org/umc/valuedi/domain/savings/exception/code/SavingsErrorCode.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@
99
@AllArgsConstructor
1010
public enum SavingsErrorCode implements BaseErrorCode {
1111

12-
SAVINGS_NOT_FOUND(HttpStatus.NOT_FOUND, "SAVINGS404_1", "해당 적금 상품을 찾을 수 없습니다.")
12+
SAVINGS_NOT_FOUND(HttpStatus.NOT_FOUND, "SAVINGS404_1", "해당 적금 상품을 찾을 수 없습니다."),
13+
RECOMMENDATION_NOT_FOUND(HttpStatus.NOT_FOUND, "SAVINGS404_2", "아직 추천받은 내역이 없습니다. 먼저 상품 추천을 진행해 주세요."),
14+
FILTERED_RECOMMENDATION_NOT_FOUND(HttpStatus.NOT_FOUND, "SAVINGS400_1", "해당 필터 조건에 맞는 추천 상품이 없습니다."),
15+
RECOMMENDATION_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "SAVINGS500_1", "AI 추천 생성에 실패했습니다. 다시 시도해 주세요.")
1316
;
1417

1518
private final HttpStatus status;

src/main/java/org/umc/valuedi/domain/savings/service/RecommendationAsyncWorker.java

Lines changed: 0 additions & 51 deletions
This file was deleted.

0 commit comments

Comments
 (0)