diff --git a/src/main/java/com/wl2c/elswhereuserservice/client/analysis/api/AnalysisServiceClient.java b/src/main/java/com/wl2c/elswhereuserservice/client/analysis/api/AnalysisServiceClient.java index 8321cb6..51a77ab 100644 --- a/src/main/java/com/wl2c/elswhereuserservice/client/analysis/api/AnalysisServiceClient.java +++ b/src/main/java/com/wl2c/elswhereuserservice/client/analysis/api/AnalysisServiceClient.java @@ -1,5 +1,6 @@ package com.wl2c.elswhereuserservice.client.analysis.api; +import com.wl2c.elswhereuserservice.client.analysis.dto.request.RequestInvestmentPropensityInformationDto; import com.wl2c.elswhereuserservice.client.analysis.dto.response.ResponsePriceRatioDto; import com.wl2c.elswhereuserservice.client.product.dto.request.RequestProductIdListDto; import jakarta.validation.Valid; @@ -16,4 +17,8 @@ public interface AnalysisServiceClient { @PostMapping("/v1/product/price/ratio/list") List getPriceRatioList(@Valid @RequestBody RequestProductIdListDto requestProductIdListDto, @RequestHeader("requestId") String requestId); + + @PostMapping("/v1/investment-propensity/list") + List getSatisfiedInvestmentPropensityProducts(@Valid @RequestBody RequestInvestmentPropensityInformationDto requestInvestmentPropensityInformationDto); + } diff --git a/src/main/java/com/wl2c/elswhereuserservice/client/analysis/dto/request/RequestInvestmentPropensityInformationDto.java b/src/main/java/com/wl2c/elswhereuserservice/client/analysis/dto/request/RequestInvestmentPropensityInformationDto.java new file mode 100644 index 0000000..ae88b1f --- /dev/null +++ b/src/main/java/com/wl2c/elswhereuserservice/client/analysis/dto/request/RequestInvestmentPropensityInformationDto.java @@ -0,0 +1,24 @@ +package com.wl2c.elswhereuserservice.client.analysis.dto.request; + +import com.wl2c.elswhereuserservice.domain.user.model.RepaymentOption; +import com.wl2c.elswhereuserservice.domain.user.model.RiskPropensity; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.List; + +@Getter +@RequiredArgsConstructor +public class RequestInvestmentPropensityInformationDto { + + @Schema(description = "상품 id 리스트", example = "[3, 6, 9, 12]") + private final List productIdList; + + @Schema(description = "투자자 위험 감수 능력", example = "MEDIUM_RISK") + private final RiskPropensity riskPropensity; + + @Schema(description = "희망 상환 기간", example = "EARLY_REPAYMENT") + private final RepaymentOption repaymentOption; + +} diff --git a/src/main/java/com/wl2c/elswhereuserservice/client/product/api/ProductServiceClient.java b/src/main/java/com/wl2c/elswhereuserservice/client/product/api/ProductServiceClient.java index 1e4ef77..42b1e91 100644 --- a/src/main/java/com/wl2c/elswhereuserservice/client/product/api/ProductServiceClient.java +++ b/src/main/java/com/wl2c/elswhereuserservice/client/product/api/ProductServiceClient.java @@ -2,10 +2,13 @@ import com.wl2c.elswhereuserservice.client.product.dto.request.RequestProductIdListDto; import com.wl2c.elswhereuserservice.client.product.dto.response.ResponseSingleProductDto; -import com.wl2c.elswhereuserservice.client.product.dto.response.ResponseSummarizedProductDto; +import com.wl2c.elswhereuserservice.client.product.dto.list.SummarizedProductDto; import com.wl2c.elswhereuserservice.client.product.dto.response.ResponseSummarizedProductForHoldingDto; +import com.wl2c.elswhereuserservice.global.model.dto.ResponsePage; import jakarta.validation.Valid; +import org.springdoc.core.annotations.ParameterObject; import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.data.domain.Pageable; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -16,8 +19,11 @@ public interface ProductServiceClient { @GetMapping("/v1/product/{productId}") ResponseSingleProductDto getProduct(@PathVariable Long productId); + @GetMapping("/v1/product/on-sale") + ResponsePage listByOnSale(@RequestParam(name = "type") String type, @ParameterObject Pageable pageable); + @PostMapping("/v1/product/list") - List listByProductIds(@Valid @RequestBody RequestProductIdListDto requestProductIdListDto); + List listByProductIds(@Valid @RequestBody RequestProductIdListDto requestProductIdListDto); @PostMapping("/v1/product/holding/list") List holdingListByProductIds(@Valid @RequestBody RequestProductIdListDto requestProductIdListDto); diff --git a/src/main/java/com/wl2c/elswhereuserservice/client/product/dto/response/ResponseSummarizedProductDto.java b/src/main/java/com/wl2c/elswhereuserservice/client/product/dto/list/SummarizedProductDto.java similarity index 89% rename from src/main/java/com/wl2c/elswhereuserservice/client/product/dto/response/ResponseSummarizedProductDto.java rename to src/main/java/com/wl2c/elswhereuserservice/client/product/dto/list/SummarizedProductDto.java index 0e5430d..c93020d 100644 --- a/src/main/java/com/wl2c/elswhereuserservice/client/product/dto/response/ResponseSummarizedProductDto.java +++ b/src/main/java/com/wl2c/elswhereuserservice/client/product/dto/list/SummarizedProductDto.java @@ -1,16 +1,16 @@ -package com.wl2c.elswhereuserservice.client.product.dto.response; +package com.wl2c.elswhereuserservice.client.product.dto.list; import com.wl2c.elswhereuserservice.client.product.ProductType; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Builder; import lombok.Getter; +import lombok.RequiredArgsConstructor; import java.math.BigDecimal; import java.time.LocalDate; @Getter -@Builder -public class ResponseSummarizedProductDto { +@RequiredArgsConstructor +public class SummarizedProductDto { @Schema(description = "상품 id", example = "1") private final Long id; diff --git a/src/main/java/com/wl2c/elswhereuserservice/domain/user/controller/UserInvestmentPropensityController.java b/src/main/java/com/wl2c/elswhereuserservice/domain/user/controller/UserInvestmentPropensityController.java index cc2ff76..3f6f209 100644 --- a/src/main/java/com/wl2c/elswhereuserservice/domain/user/controller/UserInvestmentPropensityController.java +++ b/src/main/java/com/wl2c/elswhereuserservice/domain/user/controller/UserInvestmentPropensityController.java @@ -1,68 +1,51 @@ package com.wl2c.elswhereuserservice.domain.user.controller; -import com.wl2c.elswhereuserservice.domain.user.model.dto.request.RequestCreateInvestmentPropensityDto; -import com.wl2c.elswhereuserservice.domain.user.model.dto.response.ResponseInvestmentPropensityDto; +import com.wl2c.elswhereuserservice.client.product.dto.list.SummarizedProductDto; import com.wl2c.elswhereuserservice.domain.user.service.UserInvestmentPropensityService; -import com.wl2c.elswhereuserservice.global.model.dto.ResponseIdDto; +import com.wl2c.elswhereuserservice.global.model.dto.ResponsePage; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.*; +import org.springdoc.core.annotations.ParameterObject; +import org.springframework.data.domain.Pageable; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; import static java.lang.Long.parseLong; -@Tag(name = "사용자의 투자 성향 설문조사", description = "사용자의 투자 성향 설문조사 관련 api") +@Tag(name = "사용자의 투자 성향에 맞는 청약 중인 상품", description = "사용자의 투자 성향에 맞는 청약 중인 상품 관련 api") @RestController -@RequestMapping("/v1/propensity/survey") +@RequestMapping("/v1/propensity") @RequiredArgsConstructor public class UserInvestmentPropensityController { private final UserInvestmentPropensityService userInvestmentPropensityService; /** - * 설문 조사 내용 등록 + * 사용자의 투자 성향에 맞는 청약 중인 상품 리스트 조회 *

- *
- * 설문 조사 첫 등록 및 재참여도 해당됩니다.
- * 설문 조사 양식은 아래 타입에 맞춰서 기입해주세요. + * 해당하는 정렬 타입(type)에 맞게 문자열을 기입해주세요. * - * investmentExperience(ELS 상품 투자 경험) : YES(있음), NO(없음) - * investmentPreferredPeriod(투자 선호 기간) : LESS_THAN_A_YEAR(1년 미만), A_YEAR_OR_TWO(1~2년), MORE_THAN_THREE_YEARS(3년 이상) - * riskTakingAbility(투자자 위험 감수 능력) : RISK_TAKING_TYPE(위험 감수형), STABILITY_SEEKING_TYPE(안정 추구형) + * 최신순 : latest + * 낙인순 : knock-in + * 수익률순 : profit + * 청약 마감일순 : deadline *

- * - * @param dto 설문 조사 양식 dto - */ - @PostMapping - public void create(HttpServletRequest request, - @Valid @RequestBody RequestCreateInvestmentPropensityDto dto) { - userInvestmentPropensityService.create(parseLong(request.getHeader("requestId")), dto); - } - - /** - * 설문 조사 내용 조회 - *

- * 설문 조사에 참여한 적이 없는 경우, "notfound.survey-participation" 오류가 발생합니다. - *

- * - * @return 사용자가 작성한 설문 조사 내용 dto - */ - @GetMapping - public ResponseInvestmentPropensityDto read(HttpServletRequest request) { - return userInvestmentPropensityService.read(parseLong(request.getHeader("requestId"))); - } - - /** - * 설문 조사 참여 여부 확인 *

- * 설문 조사에 참여한 경우, "ok"
- * 설문 조사에 참여한 적이 없는 경우, "notfound.survey-participation" 오류가 발생합니다. + *
+ * 스텝다운 유형의 상품에 대해서 AI가 분석한 각 상품의 safetyScore를 제공합니다.
+ * 스텝다운 유형이 아니거나 스텝다운 유형이지만 분석 정보가 없는 경우에는 null 값으로 제공됩니다.
*

+ * + * @param type 정렬 타입 + * @return 페이징된 사용자의 투자 성향에 맞는 청약 중인 상품 목록 */ - @GetMapping("/check") - public void checkSurveyParticipation(HttpServletRequest request) { - userInvestmentPropensityService.checkSurveyParticipation(parseLong(request.getHeader("requestId"))); + @GetMapping + public ResponsePage personalizedProducts(HttpServletRequest request, + @RequestParam(name = "type") String type, + @ParameterObject Pageable pageable) { + return userInvestmentPropensityService.personalizedProducts(parseLong(request.getHeader("requestId")), type, pageable); } - } diff --git a/src/main/java/com/wl2c/elswhereuserservice/domain/user/controller/UserInvestmentPropensitySurveyController.java b/src/main/java/com/wl2c/elswhereuserservice/domain/user/controller/UserInvestmentPropensitySurveyController.java new file mode 100644 index 0000000..47bb3f4 --- /dev/null +++ b/src/main/java/com/wl2c/elswhereuserservice/domain/user/controller/UserInvestmentPropensitySurveyController.java @@ -0,0 +1,68 @@ +package com.wl2c.elswhereuserservice.domain.user.controller; + +import com.wl2c.elswhereuserservice.domain.user.model.dto.request.RequestCreateInvestmentPropensityDto; +import com.wl2c.elswhereuserservice.domain.user.model.dto.response.ResponseInvestmentPropensityDto; +import com.wl2c.elswhereuserservice.domain.user.service.UserInvestmentPropensityService; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import static java.lang.Long.parseLong; + +@Tag(name = "사용자의 투자 성향 설문조사", description = "사용자의 투자 성향 설문조사 관련 api") +@RestController +@RequestMapping("/v1/propensity/survey") +@RequiredArgsConstructor +public class UserInvestmentPropensitySurveyController { + + private final UserInvestmentPropensityService userInvestmentPropensityService; + + /** + * 설문 조사 내용 등록 + *

+ *
+ * 설문 조사 첫 등록 및 재참여도 해당됩니다.
+ * 설문 조사 양식은 아래 타입에 맞춰서 기입해주세요. + * + * investmentExperience(ELS 상품 투자 경험) : YES(있음), NO(없음) + * riskTakingAbility(투자자 위험 감수 능력) : EXTREME_RISK(초고위험), HIGH_RISK(고위험), MEDIUM_RISK(중위험), LOW_RISK(저위험) + * repaymentOption(희망 상환 기간) : EARLY_REPAYMENT(조기상환), MATURITY_REPAYMENT(만기상환), NO_PREFERENCE(상관없음) + * minPreferredReturn(선호 최소 수익률(연%)) : 실수 값(ex. 8.4) + *

+ * + * @param dto 설문 조사 양식 dto + */ + @PostMapping + public void create(HttpServletRequest request, + @Valid @RequestBody RequestCreateInvestmentPropensityDto dto) { + userInvestmentPropensityService.create(parseLong(request.getHeader("requestId")), dto); + } + + /** + * 설문 조사 내용 조회 + *

+ * 설문 조사에 참여한 적이 없는 경우, "notfound.survey-participation" 오류가 발생합니다. + *

+ * + * @return 사용자가 작성한 설문 조사 내용 dto + */ + @GetMapping + public ResponseInvestmentPropensityDto read(HttpServletRequest request) { + return userInvestmentPropensityService.read(parseLong(request.getHeader("requestId"))); + } + + /** + * 설문 조사 참여 여부 확인 + *

+ * 설문 조사에 참여한 경우, "ok"
+ * 설문 조사에 참여한 적이 없는 경우, "notfound.survey-participation" 오류가 발생합니다. + *

+ */ + @GetMapping("/check") + public void checkSurveyParticipation(HttpServletRequest request) { + userInvestmentPropensityService.checkSurveyParticipation(parseLong(request.getHeader("requestId"))); + } + +} diff --git a/src/main/java/com/wl2c/elswhereuserservice/domain/user/controller/UserProductLikeController.java b/src/main/java/com/wl2c/elswhereuserservice/domain/user/controller/UserProductLikeController.java index 3a40ad7..c90da98 100644 --- a/src/main/java/com/wl2c/elswhereuserservice/domain/user/controller/UserProductLikeController.java +++ b/src/main/java/com/wl2c/elswhereuserservice/domain/user/controller/UserProductLikeController.java @@ -1,6 +1,6 @@ package com.wl2c.elswhereuserservice.domain.user.controller; -import com.wl2c.elswhereuserservice.client.product.dto.response.ResponseSummarizedProductDto; +import com.wl2c.elswhereuserservice.client.product.dto.list.SummarizedProductDto; import com.wl2c.elswhereuserservice.domain.user.service.UserProductLikeService; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; @@ -39,7 +39,7 @@ public void checkIsLiked(HttpServletRequest request, * @return 사용자가 좋아요한 관심 상품 리스트 */ @GetMapping - public List findLikedProducts(HttpServletRequest request) { + public List findLikedProducts(HttpServletRequest request) { return userProductLikeService.findLikedProducts(parseLong(request.getHeader("requestId"))); } } diff --git a/src/main/java/com/wl2c/elswhereuserservice/domain/user/model/InvestmentPreferredPeriod.java b/src/main/java/com/wl2c/elswhereuserservice/domain/user/model/InvestmentPreferredPeriod.java deleted file mode 100644 index 6a0b04d..0000000 --- a/src/main/java/com/wl2c/elswhereuserservice/domain/user/model/InvestmentPreferredPeriod.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.wl2c.elswhereuserservice.domain.user.model; - -public enum InvestmentPreferredPeriod { - /** - * 1년 미만 - */ - LESS_THAN_A_YEAR, - - /** - * 1~2년 - */ - A_YEAR_OR_TWO, - - /** - * 3년 이상 - */ - MORE_THAN_THREE_YEARS -} diff --git a/src/main/java/com/wl2c/elswhereuserservice/domain/user/model/RepaymentOption.java b/src/main/java/com/wl2c/elswhereuserservice/domain/user/model/RepaymentOption.java new file mode 100644 index 0000000..76b4605 --- /dev/null +++ b/src/main/java/com/wl2c/elswhereuserservice/domain/user/model/RepaymentOption.java @@ -0,0 +1,20 @@ +package com.wl2c.elswhereuserservice.domain.user.model; + +public enum RepaymentOption { + + /** + * 조기상환 + */ + EARLY_REPAYMENT, + + /** + * 만기상환 + */ + MATURITY_REPAYMENT, + + /** + * 상관없음 + */ + NO_PREFERENCE + +} diff --git a/src/main/java/com/wl2c/elswhereuserservice/domain/user/model/RiskPropensity.java b/src/main/java/com/wl2c/elswhereuserservice/domain/user/model/RiskPropensity.java new file mode 100644 index 0000000..a9281c4 --- /dev/null +++ b/src/main/java/com/wl2c/elswhereuserservice/domain/user/model/RiskPropensity.java @@ -0,0 +1,25 @@ +package com.wl2c.elswhereuserservice.domain.user.model; + +public enum RiskPropensity { + + /** + * 초고위험 + */ + EXTREME_RISK, + + /** + * 고위험 + */ + HIGH_RISK, + + /** + * 중위험 + */ + MEDIUM_RISK, + + /** + * 저위험 + */ + LOW_RISK + +} diff --git a/src/main/java/com/wl2c/elswhereuserservice/domain/user/model/RiskTakingAbility.java b/src/main/java/com/wl2c/elswhereuserservice/domain/user/model/RiskTakingAbility.java deleted file mode 100644 index 37178d1..0000000 --- a/src/main/java/com/wl2c/elswhereuserservice/domain/user/model/RiskTakingAbility.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.wl2c.elswhereuserservice.domain.user.model; - -public enum RiskTakingAbility { - /** - * 위험 감수형 - */ - RISK_TAKING_TYPE, - - /** - * 안정 추구형 - */ - STABILITY_SEEKING_TYPE -} diff --git a/src/main/java/com/wl2c/elswhereuserservice/domain/user/model/dto/list/SummarizedUserInterestDto.java b/src/main/java/com/wl2c/elswhereuserservice/domain/user/model/dto/list/SummarizedUserInterestDto.java index 8e2b9e1..4b99dbd 100644 --- a/src/main/java/com/wl2c/elswhereuserservice/domain/user/model/dto/list/SummarizedUserInterestDto.java +++ b/src/main/java/com/wl2c/elswhereuserservice/domain/user/model/dto/list/SummarizedUserInterestDto.java @@ -1,7 +1,7 @@ package com.wl2c.elswhereuserservice.domain.user.model.dto.list; import com.wl2c.elswhereuserservice.client.product.ProductType; -import com.wl2c.elswhereuserservice.client.product.dto.response.ResponseSummarizedProductDto; +import com.wl2c.elswhereuserservice.client.product.dto.list.SummarizedProductDto; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.NonNull; @@ -46,18 +46,18 @@ public class SummarizedUserInterestDto { private final BigDecimal safetyScore; public SummarizedUserInterestDto(@NonNull Long interestId, - @NonNull ResponseSummarizedProductDto responseSummarizedProductDto) { + @NonNull SummarizedProductDto summarizedProductDto) { this.interestId = interestId; - this.productId = responseSummarizedProductDto.getId(); - this.issuer = responseSummarizedProductDto.getIssuer(); - this.name = responseSummarizedProductDto.getName(); - this.productType = responseSummarizedProductDto.getProductType(); - this.equities = responseSummarizedProductDto.getEquities(); - this.yieldIfConditionsMet = responseSummarizedProductDto.getYieldIfConditionsMet(); - this.knockIn = responseSummarizedProductDto.getKnockIn(); - this.subscriptionStartDate = responseSummarizedProductDto.getSubscriptionStartDate(); - this.subscriptionEndDate = responseSummarizedProductDto.getSubscriptionEndDate(); - this.safetyScore = responseSummarizedProductDto.getSafetyScore(); + this.productId = summarizedProductDto.getId(); + this.issuer = summarizedProductDto.getIssuer(); + this.name = summarizedProductDto.getName(); + this.productType = summarizedProductDto.getProductType(); + this.equities = summarizedProductDto.getEquities(); + this.yieldIfConditionsMet = summarizedProductDto.getYieldIfConditionsMet(); + this.knockIn = summarizedProductDto.getKnockIn(); + this.subscriptionStartDate = summarizedProductDto.getSubscriptionStartDate(); + this.subscriptionEndDate = summarizedProductDto.getSubscriptionEndDate(); + this.safetyScore = summarizedProductDto.getSafetyScore(); } } diff --git a/src/main/java/com/wl2c/elswhereuserservice/domain/user/model/dto/request/RequestCreateInvestmentPropensityDto.java b/src/main/java/com/wl2c/elswhereuserservice/domain/user/model/dto/request/RequestCreateInvestmentPropensityDto.java index d8741aa..995bda1 100644 --- a/src/main/java/com/wl2c/elswhereuserservice/domain/user/model/dto/request/RequestCreateInvestmentPropensityDto.java +++ b/src/main/java/com/wl2c/elswhereuserservice/domain/user/model/dto/request/RequestCreateInvestmentPropensityDto.java @@ -1,12 +1,14 @@ package com.wl2c.elswhereuserservice.domain.user.model.dto.request; import com.wl2c.elswhereuserservice.domain.user.model.InvestmentExperience; -import com.wl2c.elswhereuserservice.domain.user.model.InvestmentPreferredPeriod; -import com.wl2c.elswhereuserservice.domain.user.model.RiskTakingAbility; +import com.wl2c.elswhereuserservice.domain.user.model.RepaymentOption; +import com.wl2c.elswhereuserservice.domain.user.model.RiskPropensity; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.RequiredArgsConstructor; +import java.math.BigDecimal; + @Getter @RequiredArgsConstructor public class RequestCreateInvestmentPropensityDto { @@ -14,9 +16,13 @@ public class RequestCreateInvestmentPropensityDto { @Schema(description = "ELS 상품 투자 경험", example = "YES") private final InvestmentExperience investmentExperience; - @Schema(description = "투자 선호 기간", example = "LESS_THAN_A_YEAR") - private final InvestmentPreferredPeriod investmentPreferredPeriod; + @Schema(description = "투자자 위험 감수 능력", example = "MEDIUM_RISK") + private final RiskPropensity riskPropensity; + + @Schema(description = "희망 상환 기간", example = "EARLY_REPAYMENT") + private final RepaymentOption repaymentOption; + + @Schema(description = "선호 최소 수익률(연%)", example = "8.4") + private final BigDecimal minPreferredReturn; - @Schema(description = "투자자 위험 감수 능력", example = "RISK_TAKING_TYPE") - private final RiskTakingAbility riskTakingAbility; } diff --git a/src/main/java/com/wl2c/elswhereuserservice/domain/user/model/dto/response/ResponseInvestmentPropensityDto.java b/src/main/java/com/wl2c/elswhereuserservice/domain/user/model/dto/response/ResponseInvestmentPropensityDto.java index f591651..395ab57 100644 --- a/src/main/java/com/wl2c/elswhereuserservice/domain/user/model/dto/response/ResponseInvestmentPropensityDto.java +++ b/src/main/java/com/wl2c/elswhereuserservice/domain/user/model/dto/response/ResponseInvestmentPropensityDto.java @@ -1,23 +1,28 @@ package com.wl2c.elswhereuserservice.domain.user.model.dto.response; import com.wl2c.elswhereuserservice.domain.user.model.InvestmentExperience; -import com.wl2c.elswhereuserservice.domain.user.model.InvestmentPreferredPeriod; -import com.wl2c.elswhereuserservice.domain.user.model.RiskTakingAbility; +import com.wl2c.elswhereuserservice.domain.user.model.RepaymentOption; +import com.wl2c.elswhereuserservice.domain.user.model.RiskPropensity; import com.wl2c.elswhereuserservice.domain.user.model.entity.InvestmentPropensity; import lombok.Getter; +import java.math.BigDecimal; + @Getter public class ResponseInvestmentPropensityDto { private final InvestmentExperience investmentExperience; - private final InvestmentPreferredPeriod investmentPreferredPeriod; + private final RiskPropensity riskPropensity; + + private final RepaymentOption repaymentOption; - private final RiskTakingAbility riskTakingAbility; + private final BigDecimal minPreferredReturn; public ResponseInvestmentPropensityDto(InvestmentPropensity investmentPropensity) { this.investmentExperience = investmentPropensity.getInvestmentExperience(); - this.investmentPreferredPeriod = investmentPropensity.getInvestmentPreferredPeriod(); - this.riskTakingAbility = investmentPropensity.getRiskTakingAbility(); + this.riskPropensity = investmentPropensity.getRiskPropensity(); + this.repaymentOption = investmentPropensity.getRepaymentOption(); + this.minPreferredReturn = investmentPropensity.getMinPreferredReturn(); } } diff --git a/src/main/java/com/wl2c/elswhereuserservice/domain/user/model/entity/InvestmentPropensity.java b/src/main/java/com/wl2c/elswhereuserservice/domain/user/model/entity/InvestmentPropensity.java index 39d86bd..3333964 100644 --- a/src/main/java/com/wl2c/elswhereuserservice/domain/user/model/entity/InvestmentPropensity.java +++ b/src/main/java/com/wl2c/elswhereuserservice/domain/user/model/entity/InvestmentPropensity.java @@ -1,8 +1,8 @@ package com.wl2c.elswhereuserservice.domain.user.model.entity; import com.wl2c.elswhereuserservice.domain.user.model.InvestmentExperience; -import com.wl2c.elswhereuserservice.domain.user.model.InvestmentPreferredPeriod; -import com.wl2c.elswhereuserservice.domain.user.model.RiskTakingAbility; +import com.wl2c.elswhereuserservice.domain.user.model.RepaymentOption; +import com.wl2c.elswhereuserservice.domain.user.model.RiskPropensity; import com.wl2c.elswhereuserservice.global.base.BaseEntity; import jakarta.persistence.*; import lombok.AccessLevel; @@ -12,6 +12,8 @@ import org.hibernate.annotations.OnDelete; import org.hibernate.annotations.OnDeleteAction; +import java.math.BigDecimal; + import static jakarta.persistence.EnumType.STRING; @Entity @@ -33,31 +35,40 @@ public class InvestmentPropensity extends BaseEntity { private InvestmentExperience investmentExperience; @Enumerated(STRING) - private InvestmentPreferredPeriod investmentPreferredPeriod; + private RiskPropensity riskPropensity; @Enumerated(STRING) - private RiskTakingAbility riskTakingAbility; + private RepaymentOption repaymentOption; + + private BigDecimal minPreferredReturn; @Builder public InvestmentPropensity(User user, InvestmentExperience investmentExperience, - InvestmentPreferredPeriod investmentPreferredPeriod, - RiskTakingAbility riskTakingAbility) { + RiskPropensity riskPropensity, + RepaymentOption repaymentOption, + BigDecimal minPreferredReturn) { this.user = user; this.investmentExperience = investmentExperience; - this.investmentPreferredPeriod = investmentPreferredPeriod; - this.riskTakingAbility = riskTakingAbility; + this.riskPropensity = riskPropensity; + this.repaymentOption = repaymentOption; + this.minPreferredReturn = minPreferredReturn; } public void changeInvestmentExperience(InvestmentExperience investmentExperience) { this.investmentExperience = investmentExperience; } - public void changeInvestmentPreferredPeriod(InvestmentPreferredPeriod investmentPreferredPeriod) { - this.investmentPreferredPeriod = investmentPreferredPeriod; + public void changeRiskPropensity(RiskPropensity riskPropensity) { + this.riskPropensity = riskPropensity; } - public void changeRiskTakingAbility(RiskTakingAbility riskTakingAbility) { - this.riskTakingAbility = riskTakingAbility; + public void changeRepaymentOption(RepaymentOption repaymentOption) { + this.repaymentOption = repaymentOption; } + + public void changeMinPreferredReturn(BigDecimal minPreferredReturn) { + this.minPreferredReturn = minPreferredReturn; + } + } diff --git a/src/main/java/com/wl2c/elswhereuserservice/domain/user/service/UserInterestService.java b/src/main/java/com/wl2c/elswhereuserservice/domain/user/service/UserInterestService.java index 7c335c7..1061a8f 100644 --- a/src/main/java/com/wl2c/elswhereuserservice/domain/user/service/UserInterestService.java +++ b/src/main/java/com/wl2c/elswhereuserservice/domain/user/service/UserInterestService.java @@ -2,7 +2,7 @@ import com.wl2c.elswhereuserservice.client.product.api.ProductServiceClient; import com.wl2c.elswhereuserservice.client.product.dto.request.RequestProductIdListDto; -import com.wl2c.elswhereuserservice.client.product.dto.response.ResponseSummarizedProductDto; +import com.wl2c.elswhereuserservice.client.product.dto.list.SummarizedProductDto; import com.wl2c.elswhereuserservice.client.product.exception.ProductNotFoundException; import com.wl2c.elswhereuserservice.domain.user.exception.AlreadyInterestException; import com.wl2c.elswhereuserservice.domain.user.exception.InterestNotFoundException; @@ -73,16 +73,16 @@ public List read(Long userId) { log.info("Before call the product microservice"); CircuitBreaker circuitBreaker = circuitBreakerFactory.create("interestReadCircuitBreaker"); - List responseSummarizedProductDtoList = + List summarizedProductDtoList = circuitBreaker.run(() -> productServiceClient.listByProductIds(new RequestProductIdListDto(productIdList)), throwable -> new ArrayList<>()); log.info("after called the product microservice"); List result = new ArrayList<>(); for (Interest interest : interestList) { - for (ResponseSummarizedProductDto responseSummarizedProductDto : responseSummarizedProductDtoList) { - if (interest.getProductId().equals(responseSummarizedProductDto.getId())) { - result.add(new SummarizedUserInterestDto(interest.getId(), responseSummarizedProductDto)); + for (SummarizedProductDto summarizedProductDto : summarizedProductDtoList) { + if (interest.getProductId().equals(summarizedProductDto.getId())) { + result.add(new SummarizedUserInterestDto(interest.getId(), summarizedProductDto)); break; } } diff --git a/src/main/java/com/wl2c/elswhereuserservice/domain/user/service/UserInvestmentPropensityService.java b/src/main/java/com/wl2c/elswhereuserservice/domain/user/service/UserInvestmentPropensityService.java index f1a8d9e..3bc7d4b 100644 --- a/src/main/java/com/wl2c/elswhereuserservice/domain/user/service/UserInvestmentPropensityService.java +++ b/src/main/java/com/wl2c/elswhereuserservice/domain/user/service/UserInvestmentPropensityService.java @@ -1,5 +1,9 @@ package com.wl2c.elswhereuserservice.domain.user.service; +import com.wl2c.elswhereuserservice.client.analysis.api.AnalysisServiceClient; +import com.wl2c.elswhereuserservice.client.analysis.dto.request.RequestInvestmentPropensityInformationDto; +import com.wl2c.elswhereuserservice.client.product.api.ProductServiceClient; +import com.wl2c.elswhereuserservice.client.product.dto.list.SummarizedProductDto; import com.wl2c.elswhereuserservice.domain.user.exception.UserNotFoundException; import com.wl2c.elswhereuserservice.domain.user.exception.SurveyNotFoundException; import com.wl2c.elswhereuserservice.domain.user.model.dto.request.RequestCreateInvestmentPropensityDto; @@ -8,28 +12,43 @@ import com.wl2c.elswhereuserservice.domain.user.model.entity.User; import com.wl2c.elswhereuserservice.domain.user.repository.UserRepository; import com.wl2c.elswhereuserservice.domain.user.repository.UserInvestmentPropensityRepository; +import com.wl2c.elswhereuserservice.global.model.dto.ResponsePage; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cloud.client.circuitbreaker.CircuitBreaker; +import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.ArrayList; +import java.util.List; + @Service @RequiredArgsConstructor @Transactional(readOnly = true) @Slf4j public class UserInvestmentPropensityService { + private final CircuitBreakerFactory circuitBreakerFactory; + private final UserInvestmentPropensityRepository userInvestmentPropensityRepository; private final UserRepository userRepository; + private final ProductServiceClient productServiceClient; + private final AnalysisServiceClient analysisServiceClient; + @Transactional public void create(Long userId, RequestCreateInvestmentPropensityDto dto) { if (userInvestmentPropensityRepository.findByUserId(userId).isPresent()) { InvestmentPropensity investmentPropensity = userInvestmentPropensityRepository.findByUserId(userId).get(); investmentPropensity.changeInvestmentExperience(dto.getInvestmentExperience()); - investmentPropensity.changeInvestmentPreferredPeriod(dto.getInvestmentPreferredPeriod()); - investmentPropensity.changeRiskTakingAbility(dto.getRiskTakingAbility()); + investmentPropensity.changeRiskPropensity(dto.getRiskPropensity()); + investmentPropensity.changeRepaymentOption(dto.getRepaymentOption()); + investmentPropensity.changeMinPreferredReturn(dto.getMinPreferredReturn()); } else { User user = userRepository.findById(userId).orElseThrow(UserNotFoundException::new); @@ -37,8 +56,9 @@ public void create(Long userId, RequestCreateInvestmentPropensityDto dto) { InvestmentPropensity investmentPropensity = InvestmentPropensity.builder() .user(user) .investmentExperience(dto.getInvestmentExperience()) - .investmentPreferredPeriod(dto.getInvestmentPreferredPeriod()) - .riskTakingAbility(dto.getRiskTakingAbility()) + .riskPropensity(dto.getRiskPropensity()) + .repaymentOption(dto.getRepaymentOption()) + .minPreferredReturn(dto.getMinPreferredReturn()) .build(); userInvestmentPropensityRepository.save(investmentPropensity); @@ -54,4 +74,49 @@ public void checkSurveyParticipation(Long userId) { userInvestmentPropensityRepository.findByUserId(userId).orElseThrow(SurveyNotFoundException::new); } + public ResponsePage personalizedProducts(Long userId, + String type, + Pageable pageable) { + + InvestmentPropensity investmentPropensity = userInvestmentPropensityRepository.findByUserId(userId).orElseThrow(SurveyNotFoundException::new); + + // 청약 중인 상품 가져오기 (product-service) + CircuitBreaker onSaleProductsCircuitBreaker = circuitBreakerFactory.create("onSaleProductsCircuitBreaker"); + ResponsePage onSaleProducts = onSaleProductsCircuitBreaker.run(() -> productServiceClient.listByOnSale(type, pageable), + throwable -> ResponsePage.empty(pageable)); + + // 조회한 상품 중, 사용자의 최소 선호 수익률 조건에 맞는 상품만 필터링 + List filteredMinReturnProducts = onSaleProducts.getContent().stream() + .filter(dto -> dto.getYieldIfConditionsMet().compareTo(investmentPropensity.getMinPreferredReturn()) >= 0) + .toList(); + if (filteredMinReturnProducts.isEmpty()) { + return ResponsePage.empty(pageable); + } + + // 최소 수익률이 필터링된 상품들 중, 위험 감수 능력과 회망상환기간에 부합하는 상품 id 리스트 가져오기 (analysis-service) + List filteredMinReturnProductIds = filteredMinReturnProducts.stream() + .map(SummarizedProductDto::getId) + .toList(); + + RequestInvestmentPropensityInformationDto requestInvestmentPropensityInformationDto = new RequestInvestmentPropensityInformationDto( + filteredMinReturnProductIds, + investmentPropensity.getRiskPropensity(), + investmentPropensity.getRepaymentOption() + ); + CircuitBreaker satisfiedInvestmentPropensityCircuitBreaker = circuitBreakerFactory.create("satisfiedInvestmentPropensityCircuitBreaker"); + List satisfiedInvestmentPropensityProductIds = satisfiedInvestmentPropensityCircuitBreaker.run(() -> + analysisServiceClient.getSatisfiedInvestmentPropensityProducts(requestInvestmentPropensityInformationDto), + throwable -> new ArrayList<>()); + + // 필터링된 상품 중 만족하는 상품 ID가 있는 상품만 추출 + List finalFilteredProducts = filteredMinReturnProducts.stream() + .filter(dto -> satisfiedInvestmentPropensityProductIds.contains(dto.getId())) + .toList(); + + // 필터링된 리스트를 기반으로 ResponsePage 생성 + Page page = new PageImpl<>(finalFilteredProducts, pageable, finalFilteredProducts.size()); + return new ResponsePage<>(page); + + } + } diff --git a/src/main/java/com/wl2c/elswhereuserservice/domain/user/service/UserProductLikeService.java b/src/main/java/com/wl2c/elswhereuserservice/domain/user/service/UserProductLikeService.java index bb073c7..4102dcb 100644 --- a/src/main/java/com/wl2c/elswhereuserservice/domain/user/service/UserProductLikeService.java +++ b/src/main/java/com/wl2c/elswhereuserservice/domain/user/service/UserProductLikeService.java @@ -2,7 +2,7 @@ import com.wl2c.elswhereuserservice.client.product.api.ProductServiceClient; import com.wl2c.elswhereuserservice.client.product.dto.request.RequestProductIdListDto; -import com.wl2c.elswhereuserservice.client.product.dto.response.ResponseSummarizedProductDto; +import com.wl2c.elswhereuserservice.client.product.dto.list.SummarizedProductDto; import com.wl2c.elswhereuserservice.domain.user.exception.ProductLikeNotFoundException; import com.wl2c.elswhereuserservice.domain.user.model.entity.ProductLike; import com.wl2c.elswhereuserservice.domain.user.repository.UserProductLikeRepository; @@ -31,7 +31,7 @@ public void findByProductIdAndUserId(Long productId, Long userId) { userProductLikeRepository.findByProductIdAndUserId(productId, userId).orElseThrow(ProductLikeNotFoundException::new); } - public List findLikedProducts(Long userId) { + public List findLikedProducts(Long userId) { // redis -> db로 dump 요청 productServiceClient.dumpLike(String.valueOf(userId)); @@ -43,12 +43,12 @@ public List findLikedProducts(Long userId) { log.info("Before call the product microservice"); CircuitBreaker circuitBreaker = circuitBreakerFactory.create("likedProductReadCircuitBreaker"); - List responseSummarizedProductDtoList = + List summarizedProductDtoList = circuitBreaker.run(() -> productServiceClient.listByProductIds(new RequestProductIdListDto(productIdList)), throwable -> new ArrayList<>()); log.info("after called the product microservice"); - return responseSummarizedProductDtoList; + return summarizedProductDtoList; } } diff --git a/src/main/java/com/wl2c/elswhereuserservice/global/model/dto/ResponsePage.java b/src/main/java/com/wl2c/elswhereuserservice/global/model/dto/ResponsePage.java new file mode 100644 index 0000000..17a7b33 --- /dev/null +++ b/src/main/java/com/wl2c/elswhereuserservice/global/model/dto/ResponsePage.java @@ -0,0 +1,77 @@ +package com.wl2c.elswhereuserservice.global.model.dto; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; + +import java.io.Serializable; +import java.util.Collections; +import java.util.List; + +@Getter +public class ResponsePage implements Serializable { + + @Schema(description = "컨텐츠") + private final List content; + + @Schema(description = "다음 페이지가 있는지?", example = "true") + private final boolean hasNext; + + @Schema(description = "총 페이지 수", example = "51") + private final int totalPages; + + @Schema(description = "총 요소 개수", example = "1021") + private final long totalElements; + + @Schema(description = "조회한 페이지", example = "3") + private final int page; + + @Schema(description = "페이지 컨텐츠 개수", example = "20") + private final int size; + + @Schema(description = "처음 페이지인지?", example = "false") + private final boolean first; + + @Schema(description = "마지막 페이지인지?", example = "false") + private final boolean last; + + public ResponsePage(Page page) { + final PageImpl pageInfo = new PageImpl<>(page.getContent(), page.getPageable(), page.getTotalElements()); + this.content = pageInfo.getContent(); + this.hasNext = pageInfo.hasNext(); + this.totalPages = pageInfo.getTotalPages(); + this.totalElements = pageInfo.getTotalElements(); + this.page = pageInfo.getNumber(); + this.size = pageInfo.getSize(); + this.first = pageInfo.isFirst(); + this.last = pageInfo.isLast(); + } + + @JsonCreator + public ResponsePage(@JsonProperty("content") List content, + @JsonProperty("hasNext") boolean hasNext, + @JsonProperty("totalPages") int totalPages, + @JsonProperty("totalElements") long totalElements, + @JsonProperty("page") int page, + @JsonProperty("size") int size, + @JsonProperty("first") boolean first, + @JsonProperty("last") boolean last) { + this.content = content; + this.hasNext = hasNext; + this.totalPages = totalPages; + this.totalElements = totalElements; + this.page = page; + this.size = size; + this.first = first; + this.last = last; + } + + public static ResponsePage empty(Pageable pageable) { + Page emptyPage = new PageImpl<>(Collections.emptyList(), pageable, 0); + return new ResponsePage<>(emptyPage); + } +}