33import com .DecodEat .domain .products .client .PythonAnalysisClient ;
44import com .DecodEat .domain .products .converter .ProductConverter ;
55import com .DecodEat .domain .products .dto .request .AnalysisRequestDto ;
6+ import com .DecodEat .domain .products .dto .request .ProductBasedRecommendationRequestDto ;
67import com .DecodEat .domain .products .dto .request .ProductRegisterRequestDto ;
78import com .DecodEat .domain .products .dto .response .*;
89import com .DecodEat .domain .products .entity .*;
1314import com .DecodEat .domain .users .entity .User ;
1415import com .DecodEat .domain .users .repository .UserRepository ;
1516import com .DecodEat .domain .users .service .UserBehaviorService ;
17+ import com .DecodEat .global .apiPayload .code .status .ErrorStatus ;
1618import com .DecodEat .global .aws .s3 .AmazonS3Manager ;
1719import com .DecodEat .global .dto .PageResponseDto ;
1820import com .DecodEat .global .exception .GeneralException ;
21+ import jdk .jfr .Frequency ;
1922import lombok .RequiredArgsConstructor ;
2023import lombok .extern .slf4j .Slf4j ;
2124import org .springframework .data .domain .*;
@@ -51,8 +54,8 @@ public class ProductService {
5154
5255 public ProductDetailDto getDetail (Long id , User user ) {
5356 Product product = productRepository .findById (id ).orElseThrow (() -> new GeneralException (PRODUCT_NOT_EXISTED ));
54- if (user != null )
55- userBehaviorService .saveUserBehavior (user ,product , Behavior .VIEW );
57+ if (user != null )
58+ userBehaviorService .saveUserBehavior (user , product , Behavior .VIEW );
5659
5760 List <ProductInfoImage > images = productImageRepository .findByProduct (product );
5861 List <String > imageUrls = images .stream ().map (ProductInfoImage ::getImageUrl ).toList ();
@@ -61,8 +64,8 @@ public ProductDetailDto getDetail(Long id, User user) {
6164
6265 // 좋아요 여부 확인
6366 boolean isLiked = false ;
64- if (user != null ){
65- isLiked = productLikeRepository .existsByUserAndProduct (user ,product );
67+ if (user != null ) {
68+ isLiked = productLikeRepository .existsByUserAndProduct (user , product );
6669 }
6770 return ProductConverter .toProductDetailDto (product , imageUrls , productNutrition , isLiked );
6871 }
@@ -106,7 +109,7 @@ public ProductRegisterResponseDto addProduct(User user, ProductRegisterRequestDt
106109 // 파이썬 서버에 비동기로 분석 요청
107110 requestAnalysisAsync (savedProduct .getProductId (), productInfoImageUrls );
108111
109- userBehaviorService .saveUserBehavior (user ,savedProduct ,Behavior .REGISTER ); // todo: 만약에 분석 실패?
112+ userBehaviorService .saveUserBehavior (user , savedProduct , Behavior .REGISTER ); // todo: 만약에 분석 실패?
110113
111114 return ProductConverter .toProductRegisterDto (savedProduct , productInfoImageUrls );
112115 }
@@ -175,7 +178,7 @@ public PageResponseDto<ProductSearchResponseDto.ProductPrevDto> searchProducts(S
175178 return new PageResponseDto <>(result );
176179 }
177180
178- public PageResponseDto <ProductRegisterHistoryDto > getRegisterHistory (User user , Pageable pageable ){
181+ public PageResponseDto <ProductRegisterHistoryDto > getRegisterHistory (User user , Pageable pageable ) {
179182
180183 Long userId = user .getId ();
181184
@@ -185,10 +188,32 @@ public PageResponseDto<ProductRegisterHistoryDto> getRegisterHistory(User user,
185188 return new PageResponseDto <>(result );
186189 }
187190
191+ public List <ProductSearchResponseDto .ProductPrevDto > getProductBasedRecommendation (Long productId , int limit ) {
192+
193+ ProductBasedRecommendationRequestDto request =
194+ new ProductBasedRecommendationRequestDto (productId .intValue (), limit );
195+
196+ ProductBasedRecommendationResponseDto response =
197+ pythonAnalysisClient .getProductBasedRecommendation (request ).block ();
198+
199+ if (response == null ) {
200+ log .warn ("No recommendation response for product ID: {}" , productId );
201+ throw new GeneralException (NO_RECOMMENDATION_PRODUCT_BASED );
202+ }
203+
204+ List <Product > productList = response .getRecommendations ().stream ()
205+ .map (r -> productRepository .findById (r .getProductId ())
206+ .orElseThrow (() -> new GeneralException (PRODUCT_NOT_EXISTED )))
207+ .toList ();
208+
209+ return productList .stream ().map (ProductConverter ::toProductPrevDto ).toList ();
210+ }
211+
212+
188213 @ Async
189214 public void requestAnalysisAsync (Long productId , List <String > imageUrls ) {
190215 log .info ("Starting async analysis for product ID: {}" , productId );
191-
216+
192217 if (imageUrls == null || imageUrls .isEmpty ()) {
193218 log .warn ("No images to analyze for product ID: {}" , productId );
194219 updateProductStatus (productId , DecodeStatus .FAILED , "No images provided for analysis" );
@@ -218,7 +243,7 @@ public void requestAnalysisAsync(Long productId, List<String> imageUrls) {
218243 @ Transactional
219244 public void processAnalysisResult (Long productId , AnalysisResponseDto response ) {
220245 log .info ("Processing analysis result for product ID: {} with status: {}" , productId , response .getDecodeStatus ());
221-
246+
222247 try {
223248 Product product = productRepository .findById (productId )
224249 .orElseThrow (() -> new GeneralException (PRODUCT_NOT_EXISTED ));
@@ -244,10 +269,10 @@ public void updateProductStatus(Long productId, DecodeStatus status, String mess
244269 try {
245270 Product product = productRepository .findById (productId )
246271 .orElseThrow (() -> new GeneralException (PRODUCT_NOT_EXISTED ));
247-
272+
248273 product .setDecodeStatus (status );
249274 productRepository .save (product );
250-
275+
251276 log .info ("Updated product ID: {} status to: {} - {}" , productId , status , message );
252277 } catch (Exception e ) {
253278 log .error ("Failed to update product status for ID: {}" , productId , e );
@@ -256,7 +281,7 @@ public void updateProductStatus(Long productId, DecodeStatus status, String mess
256281
257282 private void saveNutritionInfo (Long productId , AnalysisResponseDto response ) {
258283 log .info ("Saving nutrition info for product ID: {}" , productId );
259-
284+
260285 try {
261286 Product product = productRepository .findById (productId )
262287 .orElseThrow (() -> new GeneralException (PRODUCT_NOT_EXISTED ));
@@ -277,7 +302,7 @@ private void saveNutritionInfo(Long productId, AnalysisResponseDto response) {
277302 .sugar (parseDouble (response .getNutrition_info ().getSugar ()))
278303 .transFat (parseDouble (response .getNutrition_info ().getTrans_fat ()))
279304 .build ();
280-
305+
281306 productNutritionRepository .save (nutrition );
282307 log .info ("Saved nutrition info for product ID: {}" , productId );
283308 }
@@ -366,7 +391,7 @@ public ProductLikeResponseDTO addOrUpdateLike(Long userId, Long productId) {
366391 // 이미 눌렀으면 → 좋아요 취소
367392 productLikeRepository .delete (existingLike .get ());
368393 isLiked = false ;
369- userBehaviorService .deleteUserBehavior (user ,product , Behavior .LIKE );
394+ userBehaviorService .deleteUserBehavior (user , product , Behavior .LIKE );
370395
371396 } else {
372397 // 처음 누르면 → 좋아요 추가
@@ -376,7 +401,7 @@ public ProductLikeResponseDTO addOrUpdateLike(Long userId, Long productId) {
376401 .build ();
377402 productLikeRepository .save (productLike );
378403 isLiked = true ;
379- userBehaviorService .saveUserBehavior (user ,product , Behavior .LIKE );
404+ userBehaviorService .saveUserBehavior (user , product , Behavior .LIKE );
380405 }
381406 return ProductConverter .toProductLikeDTO (productId , isLiked );
382407 }
0 commit comments