From ed692449d51308c890248f59d3178fad5227080b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=5BCLOUD4=5D=20=EA=B3=A0=EB=B2=94=EC=84=9D?= Date: Thu, 1 May 2025 22:45:38 +0900 Subject: [PATCH 1/4] =?UTF-8?q?style:=20UserInterest=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=EB=AA=85=20=EC=8A=A4=EB=84=A4=EC=9D=B4=ED=81=AC=20?= =?UTF-8?q?=EC=BC=80=EC=9D=B4=EC=8A=A4=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hyetaekon/common/config/SecurityPath.java | 4 +++- .../com/hyetaekon/hyetaekon/user/entity/User.java | 2 +- .../controller/UserInterestController.java | 12 ++++++------ .../dto/CategorizedInterestsResponseDto.java | 3 +-- .../dto/CategorizedInterestsWithSelectionDto.java | 2 +- .../dto/InterestItemDto.java | 2 +- .../dto/InterestSelectionRequestDto.java | 2 +- .../entity/UserInterest.java | 2 +- .../entity/UserInterestEnum.java | 2 +- .../repository/UserInterestRepository.java | 5 ++--- .../service/UserInterestService.java | 12 ++++++------ 11 files changed, 24 insertions(+), 24 deletions(-) rename src/main/java/com/hyetaekon/hyetaekon/{UserInterest => userInterest}/controller/UserInterestController.java (84%) rename src/main/java/com/hyetaekon/hyetaekon/{UserInterest => userInterest}/dto/CategorizedInterestsResponseDto.java (76%) rename src/main/java/com/hyetaekon/hyetaekon/{UserInterest => userInterest}/dto/CategorizedInterestsWithSelectionDto.java (83%) rename src/main/java/com/hyetaekon/hyetaekon/{UserInterest => userInterest}/dto/InterestItemDto.java (81%) rename src/main/java/com/hyetaekon/hyetaekon/{UserInterest => userInterest}/dto/InterestSelectionRequestDto.java (91%) rename src/main/java/com/hyetaekon/hyetaekon/{UserInterest => userInterest}/entity/UserInterest.java (92%) rename src/main/java/com/hyetaekon/hyetaekon/{UserInterest => userInterest}/entity/UserInterestEnum.java (97%) rename src/main/java/com/hyetaekon/hyetaekon/{UserInterest => userInterest}/repository/UserInterestRepository.java (81%) rename src/main/java/com/hyetaekon/hyetaekon/{UserInterest => userInterest}/service/UserInterestService.java (91%) diff --git a/src/main/java/com/hyetaekon/hyetaekon/common/config/SecurityPath.java b/src/main/java/com/hyetaekon/hyetaekon/common/config/SecurityPath.java index 799ce8b..176ae35 100644 --- a/src/main/java/com/hyetaekon/hyetaekon/common/config/SecurityPath.java +++ b/src/main/java/com/hyetaekon/hyetaekon/common/config/SecurityPath.java @@ -28,7 +28,9 @@ public class SecurityPath { "/api/interests", "/api/interests/me", "/api/posts", - "/api/posts/*" + "/api/posts/*", + "/api/search/history", + "/api/search/history/*" }; // hasRole("ADMIN") diff --git a/src/main/java/com/hyetaekon/hyetaekon/user/entity/User.java b/src/main/java/com/hyetaekon/hyetaekon/user/entity/User.java index babb41b..17f79d4 100644 --- a/src/main/java/com/hyetaekon/hyetaekon/user/entity/User.java +++ b/src/main/java/com/hyetaekon/hyetaekon/user/entity/User.java @@ -1,6 +1,6 @@ package com.hyetaekon.hyetaekon.user.entity; -import com.hyetaekon.hyetaekon.UserInterest.entity.UserInterest; +import com.hyetaekon.hyetaekon.userInterest.entity.UserInterest; import com.hyetaekon.hyetaekon.bookmark.entity.Bookmark; import com.hyetaekon.hyetaekon.recommend.entity.Recommend; import jakarta.persistence.*; diff --git a/src/main/java/com/hyetaekon/hyetaekon/UserInterest/controller/UserInterestController.java b/src/main/java/com/hyetaekon/hyetaekon/userInterest/controller/UserInterestController.java similarity index 84% rename from src/main/java/com/hyetaekon/hyetaekon/UserInterest/controller/UserInterestController.java rename to src/main/java/com/hyetaekon/hyetaekon/userInterest/controller/UserInterestController.java index 6e15c52..3de8529 100644 --- a/src/main/java/com/hyetaekon/hyetaekon/UserInterest/controller/UserInterestController.java +++ b/src/main/java/com/hyetaekon/hyetaekon/userInterest/controller/UserInterestController.java @@ -1,10 +1,10 @@ -package com.hyetaekon.hyetaekon.UserInterest.controller; +package com.hyetaekon.hyetaekon.userInterest.controller; -import com.hyetaekon.hyetaekon.UserInterest.dto.CategorizedInterestsResponseDto; -import com.hyetaekon.hyetaekon.UserInterest.dto.CategorizedInterestsWithSelectionDto; -import com.hyetaekon.hyetaekon.UserInterest.dto.InterestSelectionRequestDto; -import com.hyetaekon.hyetaekon.UserInterest.entity.UserInterestEnum; -import com.hyetaekon.hyetaekon.UserInterest.service.UserInterestService; +import com.hyetaekon.hyetaekon.userInterest.dto.CategorizedInterestsResponseDto; +import com.hyetaekon.hyetaekon.userInterest.dto.CategorizedInterestsWithSelectionDto; +import com.hyetaekon.hyetaekon.userInterest.dto.InterestSelectionRequestDto; +import com.hyetaekon.hyetaekon.userInterest.entity.UserInterestEnum; +import com.hyetaekon.hyetaekon.userInterest.service.UserInterestService; import com.hyetaekon.hyetaekon.common.jwt.CustomUserDetails; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; diff --git a/src/main/java/com/hyetaekon/hyetaekon/UserInterest/dto/CategorizedInterestsResponseDto.java b/src/main/java/com/hyetaekon/hyetaekon/userInterest/dto/CategorizedInterestsResponseDto.java similarity index 76% rename from src/main/java/com/hyetaekon/hyetaekon/UserInterest/dto/CategorizedInterestsResponseDto.java rename to src/main/java/com/hyetaekon/hyetaekon/userInterest/dto/CategorizedInterestsResponseDto.java index f156cbc..b251e1c 100644 --- a/src/main/java/com/hyetaekon/hyetaekon/UserInterest/dto/CategorizedInterestsResponseDto.java +++ b/src/main/java/com/hyetaekon/hyetaekon/userInterest/dto/CategorizedInterestsResponseDto.java @@ -1,8 +1,7 @@ -package com.hyetaekon.hyetaekon.UserInterest.dto; +package com.hyetaekon.hyetaekon.userInterest.dto; import lombok.AllArgsConstructor; import lombok.Getter; -import lombok.Setter; import java.util.List; import java.util.Map; diff --git a/src/main/java/com/hyetaekon/hyetaekon/UserInterest/dto/CategorizedInterestsWithSelectionDto.java b/src/main/java/com/hyetaekon/hyetaekon/userInterest/dto/CategorizedInterestsWithSelectionDto.java similarity index 83% rename from src/main/java/com/hyetaekon/hyetaekon/UserInterest/dto/CategorizedInterestsWithSelectionDto.java rename to src/main/java/com/hyetaekon/hyetaekon/userInterest/dto/CategorizedInterestsWithSelectionDto.java index 907417a..6e60355 100644 --- a/src/main/java/com/hyetaekon/hyetaekon/UserInterest/dto/CategorizedInterestsWithSelectionDto.java +++ b/src/main/java/com/hyetaekon/hyetaekon/userInterest/dto/CategorizedInterestsWithSelectionDto.java @@ -1,4 +1,4 @@ -package com.hyetaekon.hyetaekon.UserInterest.dto; +package com.hyetaekon.hyetaekon.userInterest.dto; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/com/hyetaekon/hyetaekon/UserInterest/dto/InterestItemDto.java b/src/main/java/com/hyetaekon/hyetaekon/userInterest/dto/InterestItemDto.java similarity index 81% rename from src/main/java/com/hyetaekon/hyetaekon/UserInterest/dto/InterestItemDto.java rename to src/main/java/com/hyetaekon/hyetaekon/userInterest/dto/InterestItemDto.java index f572bda..070e03c 100644 --- a/src/main/java/com/hyetaekon/hyetaekon/UserInterest/dto/InterestItemDto.java +++ b/src/main/java/com/hyetaekon/hyetaekon/userInterest/dto/InterestItemDto.java @@ -1,4 +1,4 @@ -package com.hyetaekon.hyetaekon.UserInterest.dto; +package com.hyetaekon.hyetaekon.userInterest.dto; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/com/hyetaekon/hyetaekon/UserInterest/dto/InterestSelectionRequestDto.java b/src/main/java/com/hyetaekon/hyetaekon/userInterest/dto/InterestSelectionRequestDto.java similarity index 91% rename from src/main/java/com/hyetaekon/hyetaekon/UserInterest/dto/InterestSelectionRequestDto.java rename to src/main/java/com/hyetaekon/hyetaekon/userInterest/dto/InterestSelectionRequestDto.java index a34be5e..7196e3c 100644 --- a/src/main/java/com/hyetaekon/hyetaekon/UserInterest/dto/InterestSelectionRequestDto.java +++ b/src/main/java/com/hyetaekon/hyetaekon/userInterest/dto/InterestSelectionRequestDto.java @@ -1,4 +1,4 @@ -package com.hyetaekon.hyetaekon.UserInterest.dto; +package com.hyetaekon.hyetaekon.userInterest.dto; import lombok.Getter; diff --git a/src/main/java/com/hyetaekon/hyetaekon/UserInterest/entity/UserInterest.java b/src/main/java/com/hyetaekon/hyetaekon/userInterest/entity/UserInterest.java similarity index 92% rename from src/main/java/com/hyetaekon/hyetaekon/UserInterest/entity/UserInterest.java rename to src/main/java/com/hyetaekon/hyetaekon/userInterest/entity/UserInterest.java index 118b63c..b7ace16 100644 --- a/src/main/java/com/hyetaekon/hyetaekon/UserInterest/entity/UserInterest.java +++ b/src/main/java/com/hyetaekon/hyetaekon/userInterest/entity/UserInterest.java @@ -1,4 +1,4 @@ -package com.hyetaekon.hyetaekon.UserInterest.entity; +package com.hyetaekon.hyetaekon.userInterest.entity; import com.hyetaekon.hyetaekon.user.entity.User; import jakarta.persistence.*; diff --git a/src/main/java/com/hyetaekon/hyetaekon/UserInterest/entity/UserInterestEnum.java b/src/main/java/com/hyetaekon/hyetaekon/userInterest/entity/UserInterestEnum.java similarity index 97% rename from src/main/java/com/hyetaekon/hyetaekon/UserInterest/entity/UserInterestEnum.java rename to src/main/java/com/hyetaekon/hyetaekon/userInterest/entity/UserInterestEnum.java index 815a1b2..a738203 100644 --- a/src/main/java/com/hyetaekon/hyetaekon/UserInterest/entity/UserInterestEnum.java +++ b/src/main/java/com/hyetaekon/hyetaekon/userInterest/entity/UserInterestEnum.java @@ -1,4 +1,4 @@ -package com.hyetaekon.hyetaekon.UserInterest.entity; +package com.hyetaekon.hyetaekon.userInterest.entity; import lombok.Getter; diff --git a/src/main/java/com/hyetaekon/hyetaekon/UserInterest/repository/UserInterestRepository.java b/src/main/java/com/hyetaekon/hyetaekon/userInterest/repository/UserInterestRepository.java similarity index 81% rename from src/main/java/com/hyetaekon/hyetaekon/UserInterest/repository/UserInterestRepository.java rename to src/main/java/com/hyetaekon/hyetaekon/userInterest/repository/UserInterestRepository.java index 560495b..aa1fd77 100644 --- a/src/main/java/com/hyetaekon/hyetaekon/UserInterest/repository/UserInterestRepository.java +++ b/src/main/java/com/hyetaekon/hyetaekon/userInterest/repository/UserInterestRepository.java @@ -1,10 +1,9 @@ -package com.hyetaekon.hyetaekon.UserInterest.repository; +package com.hyetaekon.hyetaekon.userInterest.repository; -import com.hyetaekon.hyetaekon.UserInterest.entity.UserInterest; +import com.hyetaekon.hyetaekon.userInterest.entity.UserInterest; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import java.util.Arrays; import java.util.List; @Repository diff --git a/src/main/java/com/hyetaekon/hyetaekon/UserInterest/service/UserInterestService.java b/src/main/java/com/hyetaekon/hyetaekon/userInterest/service/UserInterestService.java similarity index 91% rename from src/main/java/com/hyetaekon/hyetaekon/UserInterest/service/UserInterestService.java rename to src/main/java/com/hyetaekon/hyetaekon/userInterest/service/UserInterestService.java index 84d1a91..204353c 100644 --- a/src/main/java/com/hyetaekon/hyetaekon/UserInterest/service/UserInterestService.java +++ b/src/main/java/com/hyetaekon/hyetaekon/userInterest/service/UserInterestService.java @@ -1,10 +1,10 @@ -package com.hyetaekon.hyetaekon.UserInterest.service; +package com.hyetaekon.hyetaekon.userInterest.service; -import com.hyetaekon.hyetaekon.UserInterest.dto.CategorizedInterestsWithSelectionDto; -import com.hyetaekon.hyetaekon.UserInterest.dto.InterestItemDto; -import com.hyetaekon.hyetaekon.UserInterest.entity.UserInterest; -import com.hyetaekon.hyetaekon.UserInterest.entity.UserInterestEnum; -import com.hyetaekon.hyetaekon.UserInterest.repository.UserInterestRepository; +import com.hyetaekon.hyetaekon.userInterest.dto.CategorizedInterestsWithSelectionDto; +import com.hyetaekon.hyetaekon.userInterest.dto.InterestItemDto; +import com.hyetaekon.hyetaekon.userInterest.entity.UserInterest; +import com.hyetaekon.hyetaekon.userInterest.entity.UserInterestEnum; +import com.hyetaekon.hyetaekon.userInterest.repository.UserInterestRepository; import com.hyetaekon.hyetaekon.common.exception.ErrorCode; import com.hyetaekon.hyetaekon.common.exception.GlobalException; import com.hyetaekon.hyetaekon.user.entity.User; From b946e0ea872cb2c48a0b3bbfd53a86a7037d19b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=5BCLOUD4=5D=20=EA=B3=A0=EB=B2=94=EC=84=9D?= Date: Thu, 1 May 2025 22:46:07 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20redis=EB=A5=BC=20=ED=99=9C=EC=9A=A9?= =?UTF-8?q?=ED=95=9C=20=EC=9C=A0=EC=A0=80=EB=B3=84=20=EA=B2=80=EC=83=89?= =?UTF-8?q?=EA=B8=B0=EB=A1=9D=20=EC=83=9D=EC=84=B1,=20=EC=82=AD=EC=A0=9C?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/redis/SearchHistoryConfig.java | 24 +++++ .../dto/mongodb/ServiceSearchCriteriaDto.java | 4 - .../service/mongodb/ServiceSearchService.java | 12 ++- .../searchHistory/Dto/SearchHistoryDto.java | 25 +++++ .../Repository/SearchHistoryRepository.java | 16 ++++ .../Service/SearchHistoryService.java | 94 +++++++++++++++++++ .../controller/SearchHistoryController.java | 49 ++++++++++ .../searchHistory/entity/SearchHistory.java | 36 +++++++ 8 files changed, 252 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/hyetaekon/hyetaekon/common/config/redis/SearchHistoryConfig.java create mode 100644 src/main/java/com/hyetaekon/hyetaekon/searchHistory/Dto/SearchHistoryDto.java create mode 100644 src/main/java/com/hyetaekon/hyetaekon/searchHistory/Repository/SearchHistoryRepository.java create mode 100644 src/main/java/com/hyetaekon/hyetaekon/searchHistory/Service/SearchHistoryService.java create mode 100644 src/main/java/com/hyetaekon/hyetaekon/searchHistory/controller/SearchHistoryController.java create mode 100644 src/main/java/com/hyetaekon/hyetaekon/searchHistory/entity/SearchHistory.java diff --git a/src/main/java/com/hyetaekon/hyetaekon/common/config/redis/SearchHistoryConfig.java b/src/main/java/com/hyetaekon/hyetaekon/common/config/redis/SearchHistoryConfig.java new file mode 100644 index 0000000..2f2e137 --- /dev/null +++ b/src/main/java/com/hyetaekon/hyetaekon/common/config/redis/SearchHistoryConfig.java @@ -0,0 +1,24 @@ +package com.hyetaekon.hyetaekon.common.config.redis; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Configuration +@EnableRedisRepositories(basePackages = "com.hyetaekon.hyetaekon.publicservice.repository.redis") +public class SearchHistoryConfig { + + @Bean + public RedisTemplate searchHistoryRedisTemplate(RedisTemplate redisTemplate) { + // 키와 값의 직렬화 방식 지정 + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class)); + redisTemplate.setHashKeySerializer(new StringRedisSerializer()); + redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class)); + + return redisTemplate; + } +} diff --git a/src/main/java/com/hyetaekon/hyetaekon/publicservice/dto/mongodb/ServiceSearchCriteriaDto.java b/src/main/java/com/hyetaekon/hyetaekon/publicservice/dto/mongodb/ServiceSearchCriteriaDto.java index ebf0818..4642d69 100644 --- a/src/main/java/com/hyetaekon/hyetaekon/publicservice/dto/mongodb/ServiceSearchCriteriaDto.java +++ b/src/main/java/com/hyetaekon/hyetaekon/publicservice/dto/mongodb/ServiceSearchCriteriaDto.java @@ -36,8 +36,4 @@ public ServiceSearchCriteriaDto withUserInfo( .build(); } - // 검색 조건 유무 확인 - public boolean hasSearchCriteria() { - return StringUtils.hasText(searchTerm); - } } diff --git a/src/main/java/com/hyetaekon/hyetaekon/publicservice/service/mongodb/ServiceSearchService.java b/src/main/java/com/hyetaekon/hyetaekon/publicservice/service/mongodb/ServiceSearchService.java index 6ab2bf8..f38430b 100644 --- a/src/main/java/com/hyetaekon/hyetaekon/publicservice/service/mongodb/ServiceSearchService.java +++ b/src/main/java/com/hyetaekon/hyetaekon/publicservice/service/mongodb/ServiceSearchService.java @@ -1,7 +1,8 @@ package com.hyetaekon.hyetaekon.publicservice.service.mongodb; -import com.hyetaekon.hyetaekon.UserInterest.entity.UserInterest; -import com.hyetaekon.hyetaekon.UserInterest.repository.UserInterestRepository; +import com.hyetaekon.hyetaekon.searchHistory.Service.SearchHistoryService; +import com.hyetaekon.hyetaekon.userInterest.entity.UserInterest; +import com.hyetaekon.hyetaekon.userInterest.repository.UserInterestRepository; import lombok.RequiredArgsConstructor; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; @@ -34,11 +35,12 @@ public class ServiceSearchService { private final UserRepository userRepository; private final UserInterestRepository userInterestRepository; private final IncomeEstimationHandler incomeEstimationHandler; + private final SearchHistoryService searchHistoryService; // 기본 검색 (비로그인) public Page searchServices(ServiceSearchCriteriaDto criteria) { // 검색 조건이 없는 경우 빈 결과 반환 - if (!criteria.hasSearchCriteria()) { + if (!StringUtils.hasText(criteria.getSearchTerm())) { return Page.empty(criteria.getPageable()); } @@ -54,8 +56,10 @@ public Page searchPersonalizedServices( ServiceSearchCriteriaDto criteria, Long userId) { // 검색 조건이 없는 경우 빈 결과 반환 - if (!criteria.hasSearchCriteria()) { + if (!StringUtils.hasText(criteria.getSearchTerm())) { return Page.empty(criteria.getPageable()); + } else { // 검색어가 유효하면 검색 기록 저장 + searchHistoryService.saveSearchHistory(userId, criteria.getSearchTerm()); } // 사용자 정보 가져오기 diff --git a/src/main/java/com/hyetaekon/hyetaekon/searchHistory/Dto/SearchHistoryDto.java b/src/main/java/com/hyetaekon/hyetaekon/searchHistory/Dto/SearchHistoryDto.java new file mode 100644 index 0000000..fd1b954 --- /dev/null +++ b/src/main/java/com/hyetaekon/hyetaekon/searchHistory/Dto/SearchHistoryDto.java @@ -0,0 +1,25 @@ +package com.hyetaekon.hyetaekon.searchHistory.Dto; + +import com.hyetaekon.hyetaekon.searchHistory.entity.SearchHistory; +import lombok.*; + +import java.time.format.DateTimeFormatter; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SearchHistoryDto { + private String id; + private String searchTerm; + private String createdAt; + + // Entity -> DTO 변환 + public static SearchHistoryDto from(SearchHistory entity) { + return SearchHistoryDto.builder() + .id(entity.getId()) + .searchTerm(entity.getSearchTerm()) + .createdAt(entity.getCreatedAt().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))) + .build(); + } +} diff --git a/src/main/java/com/hyetaekon/hyetaekon/searchHistory/Repository/SearchHistoryRepository.java b/src/main/java/com/hyetaekon/hyetaekon/searchHistory/Repository/SearchHistoryRepository.java new file mode 100644 index 0000000..5bbba95 --- /dev/null +++ b/src/main/java/com/hyetaekon/hyetaekon/searchHistory/Repository/SearchHistoryRepository.java @@ -0,0 +1,16 @@ +package com.hyetaekon.hyetaekon.searchHistory.Repository; + +import com.hyetaekon.hyetaekon.searchHistory.entity.SearchHistory; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface SearchHistoryRepository extends CrudRepository { + // 특정 사용자의 모든 검색 기록 조회 + List findByUserId(Long userId); + + // 특정 사용자의 특정 검색어 기록 삭제 + void deleteByUserIdAndId(Long userId, String id); +} diff --git a/src/main/java/com/hyetaekon/hyetaekon/searchHistory/Service/SearchHistoryService.java b/src/main/java/com/hyetaekon/hyetaekon/searchHistory/Service/SearchHistoryService.java new file mode 100644 index 0000000..7788f72 --- /dev/null +++ b/src/main/java/com/hyetaekon/hyetaekon/searchHistory/Service/SearchHistoryService.java @@ -0,0 +1,94 @@ +package com.hyetaekon.hyetaekon.searchHistory.Service; + +import com.hyetaekon.hyetaekon.searchHistory.Dto.SearchHistoryDto; +import com.hyetaekon.hyetaekon.searchHistory.Repository.SearchHistoryRepository; +import com.hyetaekon.hyetaekon.searchHistory.entity.SearchHistory; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +import java.util.List; +import java.util.Comparator; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +public class SearchHistoryService { + private final SearchHistoryRepository searchHistoryRepository; + + private static final int MAX_DISPLAY_COUNT = 6; + + /** + * 검색 기록 저장 + */ + @Transactional + public void saveSearchHistory(Long userId, String searchTerm) { + // 검색어가 없거나 빈 문자열이면 저장하지 않음 + if (!StringUtils.hasText(searchTerm) || userId == 0L) { + return; + } + + // 검색어 중복 제거를 위한 기존 검색 기록 확인 + List existingHistories = searchHistoryRepository.findByUserId(userId); + + // 이미 동일한 검색어가 있으면 삭제 + existingHistories.stream() + .filter(history -> history.getSearchTerm().equals(searchTerm)) + .forEach(history -> searchHistoryRepository.deleteById(history.getId())); + + // 새로운 검색 기록 저장 + SearchHistory newHistory = SearchHistory.of(userId, searchTerm); + searchHistoryRepository.save(newHistory); + log.debug("사용자 {} 검색 기록 저장: {}", userId, searchTerm); + } + + /** + * 사용자의 검색 기록 조회 (최신 6개) + */ + @Transactional(readOnly = true) + public List getUserSearchHistories(Long userId) { + if (userId == 0L) { + return List.of(); + } + + List histories = searchHistoryRepository.findByUserId(userId); + + // 최신 순으로 정렬하여 최대 6개 반환 + return histories.stream() + .sorted(Comparator.comparing(SearchHistory::getCreatedAt).reversed()) + .limit(MAX_DISPLAY_COUNT) + .map(SearchHistoryDto::from) + .collect(Collectors.toList()); + } + + /** + * 개별 검색 기록 삭제 + */ + @Transactional + public void deleteSearchHistory(Long userId, String historyId) { + if (userId == 0L || !StringUtils.hasText(historyId)) { + return; + } + + // 안전하게 사용자의 검색 기록만 삭제하기 위해 사용자 ID도 함께 확인 + searchHistoryRepository.deleteByUserIdAndId(userId, historyId); + log.debug("사용자 {} 검색 기록 삭제: {}", userId, historyId); + } + + /** + * 사용자의 모든 검색 기록 삭제 + */ + @Transactional + public void deleteAllSearchHistories(Long userId) { + if (userId == 0L) { + return; + } + + List histories = searchHistoryRepository.findByUserId(userId); + searchHistoryRepository.deleteAll(histories); + log.debug("사용자 {} 검색 기록 전체 삭제", userId); + } +} diff --git a/src/main/java/com/hyetaekon/hyetaekon/searchHistory/controller/SearchHistoryController.java b/src/main/java/com/hyetaekon/hyetaekon/searchHistory/controller/SearchHistoryController.java new file mode 100644 index 0000000..c2d9c86 --- /dev/null +++ b/src/main/java/com/hyetaekon/hyetaekon/searchHistory/controller/SearchHistoryController.java @@ -0,0 +1,49 @@ +package com.hyetaekon.hyetaekon.searchHistory.controller; + +import com.hyetaekon.hyetaekon.common.jwt.CustomUserDetails; +import com.hyetaekon.hyetaekon.searchHistory.Dto.SearchHistoryDto; +import com.hyetaekon.hyetaekon.searchHistory.Service.SearchHistoryService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/search/history") +@RequiredArgsConstructor +public class SearchHistoryController { + + private final SearchHistoryService searchHistoryService; + + /** + * 현재 로그인한 사용자의 검색 기록 조회 (최신 6개) + */ + @GetMapping + public ResponseEntity> getSearchHistories( + @AuthenticationPrincipal CustomUserDetails userDetails) { + return ResponseEntity.ok(searchHistoryService.getUserSearchHistories(userDetails.getId())); + } + + /** + * 특정 검색 기록 삭제 + */ + @DeleteMapping("/{historyId}") + public ResponseEntity deleteSearchHistory(@PathVariable String historyId + , @AuthenticationPrincipal CustomUserDetails userDetails) { + searchHistoryService.deleteSearchHistory(userDetails.getId(), historyId); + return ResponseEntity.ok().build(); + } + + /** + * 모든 검색 기록 삭제 + */ + @DeleteMapping + public ResponseEntity deleteAllSearchHistories( + @AuthenticationPrincipal CustomUserDetails userDetails + ) { + searchHistoryService.deleteAllSearchHistories(userDetails.getId()); + return ResponseEntity.ok().build(); + } +} diff --git a/src/main/java/com/hyetaekon/hyetaekon/searchHistory/entity/SearchHistory.java b/src/main/java/com/hyetaekon/hyetaekon/searchHistory/entity/SearchHistory.java new file mode 100644 index 0000000..01de45d --- /dev/null +++ b/src/main/java/com/hyetaekon/hyetaekon/searchHistory/entity/SearchHistory.java @@ -0,0 +1,36 @@ +package com.hyetaekon.hyetaekon.searchHistory.entity; + +import lombok.*; +import org.springframework.data.annotation.Id; +import org.springframework.data.redis.core.RedisHash; +import org.springframework.data.redis.core.index.Indexed; + +import java.io.Serializable; +import java.time.LocalDateTime; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@RedisHash(value = "searchHistory", timeToLive = 2592000) // 30일 유지 +public class SearchHistory implements Serializable { + @Id + private String id; // userId:timestamp 형태로 구성 + + @Indexed // 인덱싱으로 특정 사용자의 검색 기록 조회 가능 + private Long userId; + + private String searchTerm; + private LocalDateTime createdAt; + + // 팩토리 메서드 + public static SearchHistory of(Long userId, String searchTerm) { + String id = userId + ":" + System.currentTimeMillis(); + return SearchHistory.builder() + .id(id) + .userId(userId) + .searchTerm(searchTerm) + .createdAt(LocalDateTime.now()) + .build(); + } +} From 610ea32d5627cc7d445281c3a0d36d9dc3529fd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=5BCLOUD4=5D=20=EA=B3=A0=EB=B2=94=EC=84=9D?= Date: Fri, 2 May 2025 02:20:58 +0900 Subject: [PATCH 3/4] =?UTF-8?q?rafactor:=20userId=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EA=B2=80=EC=A6=9D=20=EC=A0=9C=EA=B1=B0=20?= =?UTF-8?q?=EB=B0=8F=20refresh-expired=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../searchHistory/Service/SearchHistoryService.java | 13 ++----------- src/main/resources/application-prod.yml | 2 +- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/hyetaekon/hyetaekon/searchHistory/Service/SearchHistoryService.java b/src/main/java/com/hyetaekon/hyetaekon/searchHistory/Service/SearchHistoryService.java index 7788f72..130b628 100644 --- a/src/main/java/com/hyetaekon/hyetaekon/searchHistory/Service/SearchHistoryService.java +++ b/src/main/java/com/hyetaekon/hyetaekon/searchHistory/Service/SearchHistoryService.java @@ -50,10 +50,6 @@ public void saveSearchHistory(Long userId, String searchTerm) { */ @Transactional(readOnly = true) public List getUserSearchHistories(Long userId) { - if (userId == 0L) { - return List.of(); - } - List histories = searchHistoryRepository.findByUserId(userId); // 최신 순으로 정렬하여 최대 6개 반환 @@ -69,11 +65,10 @@ public List getUserSearchHistories(Long userId) { */ @Transactional public void deleteSearchHistory(Long userId, String historyId) { - if (userId == 0L || !StringUtils.hasText(historyId)) { + if (!StringUtils.hasText(historyId)) { return; } - - // 안전하게 사용자의 검색 기록만 삭제하기 위해 사용자 ID도 함께 확인 + // 사용자의 검색 기록만 삭제하기 위해 사용자 ID도 함께 확인 searchHistoryRepository.deleteByUserIdAndId(userId, historyId); log.debug("사용자 {} 검색 기록 삭제: {}", userId, historyId); } @@ -83,10 +78,6 @@ public void deleteSearchHistory(Long userId, String historyId) { */ @Transactional public void deleteAllSearchHistories(Long userId) { - if (userId == 0L) { - return; - } - List histories = searchHistoryRepository.findByUserId(userId); searchHistoryRepository.deleteAll(histories); log.debug("사용자 {} 검색 기록 전체 삭제", userId); diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 7259009..1982e1d 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -9,4 +9,4 @@ spring: jwt: access-expired: 1800 # 30분 - refresh-expired: 432000 # 5일 \ No newline at end of file + refresh-expired: 86400 # 1일 \ No newline at end of file From 92988b0a2934b26357a84900001604aedde6c7b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=5BCLOUD4=5D=20=EA=B3=A0=EB=B2=94=EC=84=9D?= Date: Fri, 2 May 2025 04:59:06 +0900 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20MongoDB=20Atlas=20Search=EC=9D=98=20?= =?UTF-8?q?$search=20=EB=AC=B8=EB=B2=95=EC=97=90=20=EC=9C=A0=ED=9A=A8?= =?UTF-8?q?=ED=95=9C=20=EC=97=B0=EC=82=B0=EC=9E=90=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mongodb/ServiceSearchClient.java | 43 ++++++++++++++----- .../service/mongodb/ServiceSearchService.java | 2 +- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/hyetaekon/hyetaekon/publicservice/repository/mongodb/ServiceSearchClient.java b/src/main/java/com/hyetaekon/hyetaekon/publicservice/repository/mongodb/ServiceSearchClient.java index 68ae2e4..e8693bd 100644 --- a/src/main/java/com/hyetaekon/hyetaekon/publicservice/repository/mongodb/ServiceSearchClient.java +++ b/src/main/java/com/hyetaekon/hyetaekon/publicservice/repository/mongodb/ServiceSearchClient.java @@ -160,11 +160,19 @@ private String buildUserMustClause(ServiceSearchCriteriaDto criteria) { { compound: { should: [ - {exists: {path: "%s", exists: false}}, - {equals: {path: "%s", value: "Y"}} + { + compound: { + mustNot: [{exists: {path: "%s"}}] + } + }, + { + equals: {path: "%s", value: "Y"} + } ] } - }""".formatted(genderField, genderField)); + } + """.formatted(genderField, genderField)); + } // 나이 필수 조건 @@ -176,21 +184,36 @@ private String buildUserMustClause(ServiceSearchCriteriaDto criteria) { { compound: { should: [ - {exists: {path: "targetAgeStart", exists: false}}, - {range: {path: "targetAgeStart", lte: %d}} + { + compound: { + mustNot: [{exists: {path: "targetAgeStart"}}] + } + }, + { + range: {path: "targetAgeStart", lte: %d} + } ] } - }""".formatted(age)); + } + """.formatted(age)); - mustClauses.add(""" + mustClauses.add(""" { compound: { should: [ - {exists: {path: "targetAgeEnd", exists: false}}, - {range: {path: "targetAgeEnd", gte: %d}} + { + compound: { + mustNot: [{exists: {path: "targetAgeEnd"}}] + } + }, + { + range: {path: "targetAgeEnd", gte: %d} + } ] } - }""".formatted(age)); + } + """.formatted(age)); + } // must 조건이 없으면 빈 문자열 반환 diff --git a/src/main/java/com/hyetaekon/hyetaekon/publicservice/service/mongodb/ServiceSearchService.java b/src/main/java/com/hyetaekon/hyetaekon/publicservice/service/mongodb/ServiceSearchService.java index f38430b..241e093 100644 --- a/src/main/java/com/hyetaekon/hyetaekon/publicservice/service/mongodb/ServiceSearchService.java +++ b/src/main/java/com/hyetaekon/hyetaekon/publicservice/service/mongodb/ServiceSearchService.java @@ -58,7 +58,7 @@ public Page searchPersonalizedServices( // 검색 조건이 없는 경우 빈 결과 반환 if (!StringUtils.hasText(criteria.getSearchTerm())) { return Page.empty(criteria.getPageable()); - } else { // 검색어가 유효하면 검색 기록 저장 + } else if(StringUtils.hasText(criteria.getSearchTerm())) { // 검색어가 유효하면 검색 기록 저장 searchHistoryService.saveSearchHistory(userId, criteria.getSearchTerm()); }