From 60ee715d3e92e465eb0f32b6b90dd926b8073ff7 Mon Sep 17 00:00:00 2001 From: lsh9672 Date: Fri, 13 Jun 2025 01:10:25 +0900 Subject: [PATCH] =?UTF-8?q?Fix=20:=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hi/recommend/biz/domain/Location.java | 36 +--- .../biz/domain/RecommendHistory.java | 11 +- .../recommend/biz/domain/RecommendStore.java | 3 + .../recommend/biz/domain/RecommendType.java | 17 +- .../recommend/biz/domain/TasteCategory.java | 18 +- .../hi/recommend/biz/domain/TasteProfile.java | 18 +- .../usecase/out/AiRecommendRepository.java | 2 +- .../infra/gateway/AiRecommendAdapter.java | 133 ++++++------- .../infra/gateway/LocationServiceAdapter.java | 132 ++++++------- .../gateway/RecommendRepositoryAdapter.java | 180 ++++++++++++------ .../infra/gateway/UserPreferenceAdapter.java | 79 ++++---- 11 files changed, 326 insertions(+), 303 deletions(-) diff --git a/recommend/src/main/java/com/ktds/hi/recommend/biz/domain/Location.java b/recommend/src/main/java/com/ktds/hi/recommend/biz/domain/Location.java index 5f9335b..7aaf0c5 100644 --- a/recommend/src/main/java/com/ktds/hi/recommend/biz/domain/Location.java +++ b/recommend/src/main/java/com/ktds/hi/recommend/biz/domain/Location.java @@ -1,40 +1,16 @@ package com.ktds.hi.recommend.biz.domain; -import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; -import lombok.NoArgsConstructor; -/** - * 위치 도메인 클래스 - * 위치 정보를 담는 도메인 객체 - */ @Getter @Builder -@NoArgsConstructor -@AllArgsConstructor public class Location { - - private Long id; - private String address; + private Long id; // 추가 private Double latitude; private Double longitude; - private String city; - private String district; - private String country; - - /** - * 좌표 업데이트 - */ - public Location updateCoordinates(Double newLatitude, Double newLongitude) { - return Location.builder() - .id(this.id) - .address(this.address) - .latitude(newLatitude) - .longitude(newLongitude) - .city(this.city) - .district(this.district) - .country(this.country) - .build(); - } -} + private String address; + private String city; // 추가 + private String district; // 추가 + private String country; // 추가 +} \ No newline at end of file diff --git a/recommend/src/main/java/com/ktds/hi/recommend/biz/domain/RecommendHistory.java b/recommend/src/main/java/com/ktds/hi/recommend/biz/domain/RecommendHistory.java index bc05b3f..b4bccdf 100644 --- a/recommend/src/main/java/com/ktds/hi/recommend/biz/domain/RecommendHistory.java +++ b/recommend/src/main/java/com/ktds/hi/recommend/biz/domain/RecommendHistory.java @@ -4,17 +4,24 @@ import lombok.Builder; import lombok.Getter; import java.time.LocalDateTime; +import java.util.List; @Getter @Builder public class RecommendHistory { private Long id; private Long memberId; - private Long storeId; - private String recommendType; + private Long storeId; // 기존 필드 + private String recommendType; // 기존 필드 (String) private Double score; private String reason; private LocalDateTime recommendedAt; private Boolean clicked; private Boolean visited; + + // 추가 필드들 (Adapter에서 사용) + private List recommendedStoreIds; + private RecommendType recommendTypeEnum; + private String criteria; + private LocalDateTime createdAt; } \ No newline at end of file diff --git a/recommend/src/main/java/com/ktds/hi/recommend/biz/domain/RecommendStore.java b/recommend/src/main/java/com/ktds/hi/recommend/biz/domain/RecommendStore.java index 1a31fa8..108ecc8 100644 --- a/recommend/src/main/java/com/ktds/hi/recommend/biz/domain/RecommendStore.java +++ b/recommend/src/main/java/com/ktds/hi/recommend/biz/domain/RecommendStore.java @@ -21,4 +21,7 @@ public class RecommendStore { private Double recommendScore; private Double distance; private String priceRange; + + // 추가 필드 + private RecommendType recommendType; } \ No newline at end of file diff --git a/recommend/src/main/java/com/ktds/hi/recommend/biz/domain/RecommendType.java b/recommend/src/main/java/com/ktds/hi/recommend/biz/domain/RecommendType.java index 0ba2192..d15afbe 100644 --- a/recommend/src/main/java/com/ktds/hi/recommend/biz/domain/RecommendType.java +++ b/recommend/src/main/java/com/ktds/hi/recommend/biz/domain/RecommendType.java @@ -1,24 +1,19 @@ package com.ktds.hi.recommend.biz.domain; -/** - * 추천 유형 열거형 - * 추천의 종류를 정의 - */ public enum RecommendType { - TASTE_BASED("취향 기반"), + AI_RECOMMENDATION("AI 추천"), LOCATION_BASED("위치 기반"), POPULARITY_BASED("인기 기반"), COLLABORATIVE_FILTERING("협업 필터링"), - AI_RECOMMENDATION("AI 추천"), - SIMILAR_USER("유사 사용자 기반"); - + PREFERENCE_BASED("취향 기반"); + private final String description; - + RecommendType(String description) { this.description = description; } - + public String getDescription() { return description; } -} +} \ No newline at end of file diff --git a/recommend/src/main/java/com/ktds/hi/recommend/biz/domain/TasteCategory.java b/recommend/src/main/java/com/ktds/hi/recommend/biz/domain/TasteCategory.java index d62487b..e6a425c 100644 --- a/recommend/src/main/java/com/ktds/hi/recommend/biz/domain/TasteCategory.java +++ b/recommend/src/main/java/com/ktds/hi/recommend/biz/domain/TasteCategory.java @@ -1,30 +1,24 @@ package com.ktds.hi.recommend.biz.domain; -/** - * 취향 카테고리 열거형 - * 음식 카테고리를 정의 - */ public enum TasteCategory { KOREAN("한식"), CHINESE("중식"), JAPANESE("일식"), WESTERN("양식"), - FAST_FOOD("패스트푸드"), CAFE("카페"), + FASTFOOD("패스트푸드"), DESSERT("디저트"), CHICKEN("치킨"), PIZZA("피자"), - ASIAN("아시안"), - VEGETARIAN("채식"), - SEAFOOD("해산물"); - + OTHER("기타"); + private final String description; - + TasteCategory(String description) { this.description = description; } - + public String getDescription() { return description; } -} +} \ No newline at end of file diff --git a/recommend/src/main/java/com/ktds/hi/recommend/biz/domain/TasteProfile.java b/recommend/src/main/java/com/ktds/hi/recommend/biz/domain/TasteProfile.java index 7339aa1..792ea63 100644 --- a/recommend/src/main/java/com/ktds/hi/recommend/biz/domain/TasteProfile.java +++ b/recommend/src/main/java/com/ktds/hi/recommend/biz/domain/TasteProfile.java @@ -5,16 +5,26 @@ import lombok.Getter; import java.time.LocalDateTime; import java.util.List; +import java.util.Map; @Getter @Builder public class TasteProfile { private Long id; private Long memberId; - private List cuisinePreferences; - private String priceRange; - private Integer distancePreference; - private List tasteTags; + private List cuisinePreferences; // 기존 유지 + private String priceRange; // 기존 유지 + private Integer distancePreference; // 기존 유지 + private List tasteTags; // 기존 유지 + + // 추가 필드들 (Adapter에서 사용) + private List preferredCategories; + private Map categoryScores; + private List preferredTags; + private Map behaviorPatterns; + private Double pricePreference; + private Double distancePreferenceDouble; // distancePreference와 구분 + private LocalDateTime createdAt; private LocalDateTime updatedAt; } \ No newline at end of file diff --git a/recommend/src/main/java/com/ktds/hi/recommend/biz/usecase/out/AiRecommendRepository.java b/recommend/src/main/java/com/ktds/hi/recommend/biz/usecase/out/AiRecommendRepository.java index a6e564e..69b9ee0 100644 --- a/recommend/src/main/java/com/ktds/hi/recommend/biz/usecase/out/AiRecommendRepository.java +++ b/recommend/src/main/java/com/ktds/hi/recommend/biz/usecase/out/AiRecommendRepository.java @@ -15,4 +15,4 @@ public interface AiRecommendRepository { // 추가 필요한 메서드들 List filterByPreferences(List stores, TasteProfile tasteProfile, String tags); String getStoreSummary(Long storeId); -} \ No newline at end of file +} diff --git a/recommend/src/main/java/com/ktds/hi/recommend/infra/gateway/AiRecommendAdapter.java b/recommend/src/main/java/com/ktds/hi/recommend/infra/gateway/AiRecommendAdapter.java index f0a1fdc..b609716 100644 --- a/recommend/src/main/java/com/ktds/hi/recommend/infra/gateway/AiRecommendAdapter.java +++ b/recommend/src/main/java/com/ktds/hi/recommend/infra/gateway/AiRecommendAdapter.java @@ -3,99 +3,86 @@ package com.ktds.hi.recommend.infra.gateway; import com.ktds.hi.recommend.biz.usecase.out.AiRecommendRepository; import com.ktds.hi.recommend.biz.domain.RecommendStore; import com.ktds.hi.recommend.biz.domain.RecommendType; +import com.ktds.hi.recommend.biz.domain.TasteProfile; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; +import java.util.Arrays; import java.util.List; import java.util.Map; -/** - * AI 추천 어댑터 클래스 - * AI 기반 추천 기능을 구현 (현재는 Mock 구현) - */ @Component @RequiredArgsConstructor @Slf4j public class AiRecommendAdapter implements AiRecommendRepository { - + @Override public List recommendStoresByAI(Long memberId, Map preferences) { - log.info("AI 기반 매장 추천 요청: memberId={}, preferences={}", memberId, preferences); - - // Mock 구현 - 실제로는 AI 모델 API 호출 - return List.of( - RecommendStore.builder() - .storeId(1L) - .storeName("AI 추천 매장 1") - .address("서울시 강남구 역삼동") - .category("한식") - .tags(List.of("맛집", "깔끔", "한식")) - .rating(4.5) - .reviewCount(150) - .distance(500.0) - .recommendScore(92.0) - .recommendType(RecommendType.AI_RECOMMENDATION) - .recommendReason("사용자 취향과 92% 일치") - .build(), - - RecommendStore.builder() - .storeId(2L) - .storeName("AI 추천 매장 2") - .address("서울시 강남구 논현동") - .category("일식") - .tags(List.of("초밥", "신선", "일식")) - .rating(4.3) - .reviewCount(89) - .distance(800.0) - .recommendScore(87.0) - .recommendType(RecommendType.AI_RECOMMENDATION) - .recommendReason("사용자가 선호하는 일식 카테고리") - .build() + log.info("AI 기반 매장 추천 요청: memberId={}", memberId); + + return Arrays.asList( + RecommendStore.builder() + .storeId(1L) + .storeName("AI 추천 매장 1") + .address("서울시 강남구 역삼동") + .category("한식") + .tags(Arrays.asList("맛집", "깔끔", "한식")) + .rating(4.5) + .reviewCount(150) + .distance(500.0) + .recommendScore(92.0) + .recommendType(RecommendType.AI_RECOMMENDATION) + .recommendReason("사용자 취향과 92% 일치") + .build() ); } - + @Override public List recommendStoresBySimilarUsers(Long memberId) { log.info("유사 사용자 기반 추천 요청: memberId={}", memberId); - - // Mock 구현 - return List.of( - RecommendStore.builder() - .storeId(3L) - .storeName("유사 취향 추천 매장") - .address("서울시 서초구 서초동") - .category("양식") - .tags(List.of("파스타", "분위기", "양식")) - .rating(4.4) - .reviewCount(203) - .distance(1200.0) - .recommendScore(85.0) - .recommendType(RecommendType.SIMILAR_USER) - .recommendReason("비슷한 취향의 사용자들이 좋아하는 매장") - .build() + + return Arrays.asList( + RecommendStore.builder() + .storeId(3L) + .storeName("유사 취향 추천 매장") + .address("서울시 서초구 서초동") + .category("양식") + .tags(Arrays.asList("파스타", "분위기", "데이트")) + .rating(4.2) + .reviewCount(78) + .distance(1200.0) + .recommendScore(85.0) + .recommendType(RecommendType.COLLABORATIVE_FILTERING) + .recommendReason("비슷한 취향의 사용자들이 선호") + .build() ); } - + @Override public List recommendStoresByCollaborativeFiltering(Long memberId) { - log.info("협업 필터링 추천 요청: memberId={}", memberId); - - // Mock 구현 - return List.of( - RecommendStore.builder() - .storeId(4L) - .storeName("협업 필터링 추천 매장") - .address("서울시 마포구 홍대입구") - .category("카페") - .tags(List.of("커피", "디저트", "분위기")) - .rating(4.2) - .reviewCount(127) - .distance(2500.0) - .recommendScore(82.0) - .recommendType(RecommendType.COLLABORATIVE_FILTERING) - .recommendReason("사용자 행동 패턴 기반 추천") - .build() - ); + return recommendStoresBySimilarUsers(memberId); } -} + + @Override + public List filterByPreferences(List stores, TasteProfile tasteProfile, String tags) { + log.info("취향 기반 매장 필터링: 매장 수={}, 태그={}", stores.size(), tags); + + // 간단한 필터링 로직 구현 + return stores.stream() + .filter(store -> { + if (tasteProfile.getPreferredTags() != null) { + return store.getTags().stream() + .anyMatch(tag -> tasteProfile.getPreferredTags().contains(tag)); + } + return true; + }) + .toList(); + } + + @Override + public String getStoreSummary(Long storeId) { + log.info("매장 AI 요약 조회: storeId={}", storeId); + return "AI가 분석한 매장 요약: 고객들이 맛과 서비스를 높이 평가하는 매장입니다."; + } +} \ No newline at end of file diff --git a/recommend/src/main/java/com/ktds/hi/recommend/infra/gateway/LocationServiceAdapter.java b/recommend/src/main/java/com/ktds/hi/recommend/infra/gateway/LocationServiceAdapter.java index d3e430a..9f0e4d5 100644 --- a/recommend/src/main/java/com/ktds/hi/recommend/infra/gateway/LocationServiceAdapter.java +++ b/recommend/src/main/java/com/ktds/hi/recommend/infra/gateway/LocationServiceAdapter.java @@ -8,99 +8,91 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; +import java.util.Arrays; import java.util.List; -/** - * 위치 서비스 어댑터 클래스 - * 위치 기반 서비스 기능을 구현 (현재는 Mock 구현) - */ @Component @RequiredArgsConstructor @Slf4j public class LocationServiceAdapter implements LocationRepository { - + @Override public Location saveLocation(Location location) { log.info("위치 정보 저장: {}", location.getAddress()); - - // Mock 구현 + return Location.builder() - .id(1L) - .address(location.getAddress()) - .latitude(location.getLatitude()) - .longitude(location.getLongitude()) - .city("서울시") - .district("강남구") - .country("대한민국") - .build(); + .id(1L) + .address(location.getAddress()) + .latitude(location.getLatitude()) + .longitude(location.getLongitude()) + .city("서울시") + .district("강남구") + .country("대한민국") + .build(); } - + @Override - public List findStoresWithinRadius(Double latitude, Double longitude, Integer radius) { + public List findStoresWithinDistance(Double latitude, Double longitude, Integer radius) { log.info("반경 내 매장 조회: lat={}, lon={}, radius={}", latitude, longitude, radius); - - // Mock 구현 - return List.of( - RecommendStore.builder() - .storeId(5L) - .storeName("근처 매장 1") - .address("서울시 강남구 역삼동 123-45") - .category("한식") - .tags(List.of("근처", "맛집")) - .rating(4.1) - .reviewCount(95) - .distance(300.0) - .recommendScore(78.0) - .recommendType(RecommendType.LOCATION_BASED) - .recommendReason("현재 위치에서 300m 거리") - .build(), - - RecommendStore.builder() - .storeId(6L) - .storeName("근처 매장 2") - .address("서울시 강남구 역삼동 678-90") - .category("카페") - .tags(List.of("커피", "디저트")) - .rating(4.0) - .reviewCount(67) - .distance(450.0) - .recommendScore(75.0) - .recommendType(RecommendType.LOCATION_BASED) - .recommendReason("현재 위치에서 450m 거리") - .build() + + return Arrays.asList( + RecommendStore.builder() + .storeId(5L) + .storeName("근처 매장 1") + .address("서울시 강남구 역삼동 123-45") + .category("한식") + .tags(Arrays.asList("근처", "맛집")) + .rating(4.1) + .reviewCount(95) + .distance(300.0) + .recommendScore(78.0) + .recommendType(RecommendType.LOCATION_BASED) + .recommendReason("현재 위치에서 300m 거리") + .build(), + + RecommendStore.builder() + .storeId(6L) + .storeName("근처 매장 2") + .address("서울시 강남구 역삼동 678-90") + .category("카페") + .tags(Arrays.asList("커피", "디저트")) + .rating(4.0) + .reviewCount(67) + .distance(450.0) + .recommendScore(75.0) + .recommendType(RecommendType.LOCATION_BASED) + .recommendReason("현재 위치에서 450m 거리") + .build() ); } - + @Override public Double calculateDistance(Double lat1, Double lon1, Double lat2, Double lon2) { - // Haversine 공식을 사용한 거리 계산 - final int R = 6371; // 지구의 반지름 (km) - + // 하버사인 공식을 이용한 거리 계산 + final int R = 6371; // 지구 반지름 (km) + double latDistance = Math.toRadians(lat2 - lat1); double lonDistance = Math.toRadians(lon2 - lon1); - double a = Math.sin(latDistance / 2) * Math.sin(latDistance / 2) - + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) - * Math.sin(lonDistance / 2) * Math.sin(lonDistance / 2); - + + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) + * Math.sin(lonDistance / 2) * Math.sin(lonDistance / 2); double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - double distance = R * c * 1000; // 미터로 변환 - - return distance; + + return R * c * 1000; // 미터 단위로 반환 } - + @Override public Location geocodeAddress(String address) { - log.info("주소 좌표 변환 요청: {}", address); - - // Mock 구현 - 실제로는 Google Maps API 등 사용 + log.info("주소 좌표 변환: {}", address); + + // Mock 구현 - 실제로는 지도 API 호출 return Location.builder() - .address(address) - .latitude(37.5665) - .longitude(126.9780) - .city("서울시") - .district("중구") - .country("대한민국") - .build(); + .address(address) + .latitude(37.5665) + .longitude(126.9780) + .city("서울시") + .district("중구") + .country("대한민국") + .build(); } -} +} \ No newline at end of file diff --git a/recommend/src/main/java/com/ktds/hi/recommend/infra/gateway/RecommendRepositoryAdapter.java b/recommend/src/main/java/com/ktds/hi/recommend/infra/gateway/RecommendRepositoryAdapter.java index e5288a6..d306e62 100644 --- a/recommend/src/main/java/com/ktds/hi/recommend/infra/gateway/RecommendRepositoryAdapter.java +++ b/recommend/src/main/java/com/ktds/hi/recommend/infra/gateway/RecommendRepositoryAdapter.java @@ -1,110 +1,178 @@ package com.ktds.hi.recommend.infra.gateway; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import com.ktds.hi.recommend.biz.usecase.out.RecommendRepository; import com.ktds.hi.recommend.biz.domain.RecommendHistory; +import com.ktds.hi.recommend.biz.domain.RecommendStore; import com.ktds.hi.recommend.biz.domain.TasteProfile; +import com.ktds.hi.recommend.biz.domain.RecommendType; import com.ktds.hi.recommend.infra.gateway.repository.RecommendHistoryJpaRepository; import com.ktds.hi.recommend.infra.gateway.repository.TasteProfileJpaRepository; import com.ktds.hi.recommend.infra.gateway.entity.RecommendHistoryEntity; import com.ktds.hi.recommend.infra.gateway.entity.TasteProfileEntity; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; +import java.time.LocalDateTime; +import java.util.Arrays; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; -/** - * 추천 리포지토리 어댑터 클래스 - * 도메인 리포지토리 인터페이스를 JPA 리포지토리에 연결 - */ @Component @RequiredArgsConstructor +@Slf4j public class RecommendRepositoryAdapter implements RecommendRepository { - + private final RecommendHistoryJpaRepository recommendHistoryJpaRepository; private final TasteProfileJpaRepository tasteProfileJpaRepository; - + private final ObjectMapper objectMapper = new ObjectMapper(); + @Override public RecommendHistory saveRecommendHistory(RecommendHistory history) { - RecommendHistoryEntity entity = toRecommendHistoryEntity(history); - RecommendHistoryEntity savedEntity = recommendHistoryJpaRepository.save(entity); - return toRecommendHistory(savedEntity); + try { + RecommendHistoryEntity entity = toRecommendHistoryEntity(history); + RecommendHistoryEntity savedEntity = recommendHistoryJpaRepository.save(entity); + return toRecommendHistory(savedEntity); + } catch (Exception e) { + log.error("추천 히스토리 저장 실패", e); + throw new RuntimeException("추천 히스토리 저장 실패", e); + } } - + @Override public List findRecommendHistoriesByMemberId(Long memberId) { List entities = recommendHistoryJpaRepository.findByMemberIdOrderByCreatedAtDesc(memberId); return entities.stream() - .map(this::toRecommendHistory) - .collect(Collectors.toList()); + .map(this::toRecommendHistory) + .toList(); } - + @Override public TasteProfile saveTasteProfile(TasteProfile profile) { - TasteProfileEntity entity = toTasteProfileEntity(profile); - TasteProfileEntity savedEntity = tasteProfileJpaRepository.save(entity); - return toTasteProfile(savedEntity); + try { + TasteProfileEntity entity = toTasteProfileEntity(profile); + TasteProfileEntity savedEntity = tasteProfileJpaRepository.save(entity); + return toTasteProfile(savedEntity); + } catch (Exception e) { + log.error("취향 프로필 저장 실패", e); + throw new RuntimeException("취향 프로필 저장 실패", e); + } } - + @Override public Optional findTasteProfileByMemberId(Long memberId) { return tasteProfileJpaRepository.findByMemberId(memberId) - .map(this::toTasteProfile); + .map(this::toTasteProfile); } - - /** - * 엔티티를 도메인으로 변환 - */ + + @Override + public Optional findStoreById(Long storeId) { + // Mock 구현 - 실제로는 Store 서비스 호출 또는 캐시에서 조회 + return Optional.of(RecommendStore.builder() + .storeId(storeId) + .storeName("매장 " + storeId) + .address("서울시 강남구") + .category("한식") + .rating(4.5) + .tags(Arrays.asList("맛집", "친절")) + .distance(500.0) + .priceRange("중간") + .build()); + } + + @Override + public List findPopularStores(String category, Integer limit) { + // Mock 구현 + return Arrays.asList( + RecommendStore.builder() + .storeId(1L) + .storeName("인기 매장 1") + .category(category != null ? category : "한식") + .rating(4.8) + .tags(Arrays.asList("인기", "맛집")) + .build() + ); + } + + @Override + public void logStoreClick(Long memberId, Long storeId) { + log.info("매장 클릭 로그: memberId={}, storeId={}", memberId, storeId); + // 비동기 로깅 구현 + } + + @Override + public void logRecommendation(Long memberId, List storeIds, String context) { + try { + RecommendHistory history = RecommendHistory.builder() + .memberId(memberId) + .recommendedStoreIds(storeIds) + .criteria(context) + .recommendTypeEnum(RecommendType.PREFERENCE_BASED) + .createdAt(LocalDateTime.now()) + .build(); + saveRecommendHistory(history); + } catch (Exception e) { + log.error("추천 로그 저장 실패", e); + } + } + + // 변환 메서드들 private RecommendHistory toRecommendHistory(RecommendHistoryEntity entity) { return RecommendHistory.builder() - .id(entity.getId()) - .memberId(entity.getMemberId()) - .recommendedStoreIds(entity.getRecommendedStoreIdsList()) - .recommendType(entity.getRecommendType()) - .criteria(entity.getCriteria()) - .createdAt(entity.getCreatedAt()) - .build(); + .id(entity.getId()) + .memberId(entity.getMemberId()) + .recommendedStoreIds(entity.getRecommendedStoreIdsList()) + .recommendTypeEnum(entity.getRecommendType()) + .criteria(entity.getCriteria()) + .createdAt(entity.getCreatedAt()) + .build(); } - + private TasteProfile toTasteProfile(TasteProfileEntity entity) { return TasteProfile.builder() - .id(entity.getId()) - .memberId(entity.getMemberId()) - .preferredCategories(entity.getPreferredCategoriesList()) - .categoryScores(entity.getCategoryScoresMap()) - .preferredTags(entity.getPreferredTagsList()) - .behaviorPatterns(entity.getBehaviorPatternsMap()) - .pricePreference(entity.getPricePreference()) - .distancePreference(entity.getDistancePreference()) - .createdAt(entity.getCreatedAt()) - .updatedAt(entity.getUpdatedAt()) - .build(); + .id(entity.getId()) + .memberId(entity.getMemberId()) + .preferredCategories(entity.getPreferredCategoriesList()) + .categoryScores(entity.getCategoryScoresMap()) + .preferredTags(entity.getPreferredTagsList()) + .behaviorPatterns(entity.getBehaviorPatternsMap()) + .pricePreference(entity.getPricePreference()) + .distancePreferenceDouble(entity.getDistancePreference()) + .createdAt(entity.getCreatedAt()) + .updatedAt(entity.getUpdatedAt()) + .build(); } - - /** - * 도메인을 엔티티로 변환 - */ + private RecommendHistoryEntity toRecommendHistoryEntity(RecommendHistory domain) { - return RecommendHistoryEntity.builder() + try { + return RecommendHistoryEntity.builder() .id(domain.getId()) .memberId(domain.getMemberId()) - .recommendedStoreIdsJson(domain.getRecommendedStoreIds().toString()) // JSON 변환 필요 - .recommendType(domain.getRecommendType()) + .recommendedStoreIdsJson(objectMapper.writeValueAsString(domain.getRecommendedStoreIds())) + .recommendType(domain.getRecommendTypeEnum()) .criteria(domain.getCriteria()) .build(); + } catch (Exception e) { + throw new RuntimeException("Entity 변환 실패", e); + } } - + private TasteProfileEntity toTasteProfileEntity(TasteProfile domain) { - return TasteProfileEntity.builder() + try { + return TasteProfileEntity.builder() .id(domain.getId()) .memberId(domain.getMemberId()) - .preferredCategoriesJson(domain.getPreferredCategories().toString()) // JSON 변환 필요 - .categoryScoresJson(domain.getCategoryScores().toString()) // JSON 변환 필요 - .preferredTagsJson(domain.getPreferredTags().toString()) // JSON 변환 필요 - .behaviorPatternsJson(domain.getBehaviorPatterns().toString()) // JSON 변환 필요 + .preferredCategoriesJson(objectMapper.writeValueAsString(domain.getPreferredCategories())) + .categoryScoresJson(objectMapper.writeValueAsString(domain.getCategoryScores())) + .preferredTagsJson(objectMapper.writeValueAsString(domain.getPreferredTags())) + .behaviorPatternsJson(objectMapper.writeValueAsString(domain.getBehaviorPatterns())) .pricePreference(domain.getPricePreference()) - .distancePreference(domain.getDistancePreference()) + .distancePreference(domain.getDistancePreferenceDouble()) .build(); + } catch (Exception e) { + throw new RuntimeException("Entity 변환 실패", e); + } } } diff --git a/recommend/src/main/java/com/ktds/hi/recommend/infra/gateway/UserPreferenceAdapter.java b/recommend/src/main/java/com/ktds/hi/recommend/infra/gateway/UserPreferenceAdapter.java index a6ab80b..187b11f 100644 --- a/recommend/src/main/java/com/ktds/hi/recommend/infra/gateway/UserPreferenceAdapter.java +++ b/recommend/src/main/java/com/ktds/hi/recommend/infra/gateway/UserPreferenceAdapter.java @@ -3,88 +3,79 @@ package com.ktds.hi.recommend.infra.gateway; import com.ktds.hi.recommend.biz.usecase.out.UserPreferenceRepository; import com.ktds.hi.recommend.biz.domain.TasteProfile; import com.ktds.hi.recommend.biz.domain.TasteCategory; -import com.ktds.hi.recommend.infra.gateway.repository.TasteProfileJpaRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.time.LocalDateTime; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Optional; -/** - * 사용자 선호도 어댑터 클래스 - * 사용자 취향 데이터 처리 기능을 구현 - */ @Component @RequiredArgsConstructor @Slf4j public class UserPreferenceAdapter implements UserPreferenceRepository { - - private final TasteProfileJpaRepository tasteProfileJpaRepository; + private final RecommendRepositoryAdapter recommendRepositoryAdapter; - + @Override public Optional getMemberPreferences(Long memberId) { return recommendRepositoryAdapter.findTasteProfileByMemberId(memberId); } - + @Override public Map analyzePreferencesFromReviews(Long memberId) { log.info("리뷰 기반 취향 분석 시작: memberId={}", memberId); - - // Mock 구현 - 실제로는 리뷰 서비스 API 호출하여 분석 + return Map.of( - "preferredCategories", List.of(TasteCategory.KOREAN, TasteCategory.JAPANESE), - "categoryScores", Map.of( - "한식", 85.0, - "일식", 78.0, - "양식", 65.0 - ), - "preferredTags", List.of("맛집", "깔끔", "친절"), - "pricePreference", 60.0, // 0-100 점수 - "distancePreference", 70.0, - "behaviorPatterns", Map.of( - "weekendDining", true, - "avgRating", 4.2, - "reviewFrequency", "medium" - ) + "preferredCategories", Arrays.asList(TasteCategory.KOREAN, TasteCategory.JAPANESE), + "categoryScores", Map.of( + "한식", 85.0, + "일식", 78.0, + "양식", 65.0 + ), + "preferredTags", Arrays.asList("맛집", "깔끔", "친절"), + "pricePreference", 60.0, + "distancePreference", 70.0, + "behaviorPatterns", Map.of( + "weekendDining", true, + "avgRating", 4.2, + "reviewFrequency", "medium" + ) ); } - + @Override public List findSimilarTasteMembers(Long memberId) { log.info("유사 취향 사용자 조회: memberId={}", memberId); - - // Mock 구현 - 실제로는 ML 모델 또는 유사도 계산 알고리즘 사용 - return List.of(123L, 456L, 789L); + return Arrays.asList(123L, 456L, 789L); } - + @Override public TasteProfile updateTasteProfile(Long memberId, Map analysisData) { log.info("취향 프로필 업데이트: memberId={}", memberId); - - // 기존 프로필 조회 또는 새로 생성 + Optional existingProfile = getMemberPreferences(memberId); - + TasteProfile.TasteProfileBuilder builder = TasteProfile.builder() - .memberId(memberId) - .preferredCategories((List) analysisData.get("preferredCategories")) - .categoryScores((Map) analysisData.get("categoryScores")) - .preferredTags((List) analysisData.get("preferredTags")) - .behaviorPatterns((Map) analysisData.get("behaviorPatterns")) - .pricePreference((Double) analysisData.get("pricePreference")) - .distancePreference((Double) analysisData.get("distancePreference")) - .updatedAt(LocalDateTime.now()); - + .memberId(memberId) + .preferredCategories((List) analysisData.get("preferredCategories")) + .categoryScores((Map) analysisData.get("categoryScores")) + .preferredTags((List) analysisData.get("preferredTags")) + .behaviorPatterns((Map) analysisData.get("behaviorPatterns")) + .pricePreference((Double) analysisData.get("pricePreference")) + .distancePreferenceDouble((Double) analysisData.get("distancePreference")) + .updatedAt(LocalDateTime.now()); + if (existingProfile.isPresent()) { builder.id(existingProfile.get().getId()) - .createdAt(existingProfile.get().getCreatedAt()); + .createdAt(existingProfile.get().getCreatedAt()); } else { builder.createdAt(LocalDateTime.now()); } - + TasteProfile updatedProfile = builder.build(); return recommendRepositoryAdapter.saveTasteProfile(updatedProfile); }