From 93521923bff959bf8b74783e106829ac6d7d5467 Mon Sep 17 00:00:00 2001 From: SeungkiHwang Date: Sun, 10 Aug 2025 13:14:58 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feature(SCRUM-236)=20:=20=EC=B5=9C=EA=B7=BC?= =?UTF-8?q?=20=EA=B2=80=EC=83=89=EC=96=B4=20=EA=B8=B0=EB=8A=A5=20=EC=83=88?= =?UTF-8?q?=EB=A1=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/star/api/StarV2Controller.java | 8 ++++++ .../domain/star/service/StarQueryService.java | 1 + .../star/service/StarQueryServiceImpl.java | 25 ++++++++++++++++--- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/team_nebula/nebula/domain/star/api/StarV2Controller.java b/src/main/java/com/team_nebula/nebula/domain/star/api/StarV2Controller.java index 3dcd2a9..39902bd 100644 --- a/src/main/java/com/team_nebula/nebula/domain/star/api/StarV2Controller.java +++ b/src/main/java/com/team_nebula/nebula/domain/star/api/StarV2Controller.java @@ -80,6 +80,7 @@ public ApiResponse searchStars( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size ) { + starCommandService.saveRecentSearches(userId, query); SearchResultResponseDTO result = starQueryService.searchStarsV2(query, userId, page, size); return ApiResponse.onSuccess(result); } @@ -93,4 +94,11 @@ public ApiResponse> getAutoComplete( List suggestions = starQueryService.getAutoComplete(query, userId, 5); return ApiResponse.onSuccess(suggestions); } + + @Operation(summary = "최근 검색어 조회", description = "사용자 최근 검색어 10개 조회") + @GetMapping("/recent-searches") + public ApiResponse> getRecentSearches(@AuthUser Long userId) { + List recentSearches = starQueryService.getRecentSearches(userId); + return ApiResponse.onSuccess(recentSearches); + } } diff --git a/src/main/java/com/team_nebula/nebula/domain/star/service/StarQueryService.java b/src/main/java/com/team_nebula/nebula/domain/star/service/StarQueryService.java index df3491e..cf6d640 100644 --- a/src/main/java/com/team_nebula/nebula/domain/star/service/StarQueryService.java +++ b/src/main/java/com/team_nebula/nebula/domain/star/service/StarQueryService.java @@ -38,4 +38,5 @@ public interface StarQueryService { public List getAutoComplete(String query, Long userId, int size); + public List getRecentSearches(Long userId); } diff --git a/src/main/java/com/team_nebula/nebula/domain/star/service/StarQueryServiceImpl.java b/src/main/java/com/team_nebula/nebula/domain/star/service/StarQueryServiceImpl.java index 4fd2f22..679d0c7 100644 --- a/src/main/java/com/team_nebula/nebula/domain/star/service/StarQueryServiceImpl.java +++ b/src/main/java/com/team_nebula/nebula/domain/star/service/StarQueryServiceImpl.java @@ -5,11 +5,11 @@ import com.team_nebula.nebula.domain.star.dto.response.*; import com.team_nebula.nebula.domain.star.repository.StarNeo4jRepositoryCustom; -import com.team_nebula.nebula.domain.star.search.document.StarSearchDocument; import com.team_nebula.nebula.domain.star.search.dto.response.SearchResultResponseDTO; import com.team_nebula.nebula.domain.star.search.dto.response.SearchStarResponseDTO; import com.team_nebula.nebula.domain.star.search.service.ElasticsearchService; import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -21,6 +21,8 @@ import lombok.RequiredArgsConstructor; +import static org.springframework.data.neo4j.core.ReactiveNeo4jClient.log; + @Service @RequiredArgsConstructor @Transactional(readOnly = true) @@ -31,6 +33,9 @@ public class StarQueryServiceImpl implements StarQueryService { private final StarNeo4jRepositoryCustom starNeo4jRepositoryCustom; private final ElasticsearchService elasticsearchService; + private final RedisTemplate redisTemplate; + private static final int MAX_RECENT_SEARCHES = 10; + // 스타 + 링크 전체 조회 @Override public GetStarListResponseDTO getStarList(Long userId) { @@ -164,8 +169,6 @@ public SearchResultResponseDTO searchStarsV2(String keyword, Long userId, int pa .build(); } - - @Override public List getAutoComplete(String query, Long userId, int size) { @@ -181,4 +184,20 @@ public List getAutoComplete(String query, Long userId, int size) { return elasticsearchService.getAutoComplete(query, userId, size); } + + @Override + @Cacheable(value = "recent_searches", key = "#userId") + public List getRecentSearches(Long userId) { + try { + String key = "recent_searches"+userId.toString(); + List searches = redisTemplate.opsForList().range(key, 0, MAX_RECENT_SEARCHES - 1); + return searches.stream() + .map(Object::toString) + .collect(Collectors.toList()); + } + catch (Exception e) { + log.error(e, "Failed to get recent searches"); + return Collections.emptyList(); + } + } } From 8e3eef6d23393fab749d1006636e0e3eda9b10c1 Mon Sep 17 00:00:00 2001 From: SeungkiHwang Date: Sun, 10 Aug 2025 13:15:58 +0900 Subject: [PATCH 2/2] =?UTF-8?q?refactor(SCRUM-236)=20:=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=20=EC=8B=A4=ED=96=89=20=ED=9B=84=20=EC=B5=9C=EA=B7=BC?= =?UTF-8?q?=20=EA=B2=80=EC=83=89=EC=96=B4=20=EC=BA=90=EC=8B=9C=EC=97=90=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../search/service/ElasticsearchService.java | 2 +- .../star/service/StarCommandService.java | 6 ++++ .../star/service/StarCommandServiceImpl.java | 36 +++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/team_nebula/nebula/domain/star/search/service/ElasticsearchService.java b/src/main/java/com/team_nebula/nebula/domain/star/search/service/ElasticsearchService.java index 5dd8881..d2cd91d 100644 --- a/src/main/java/com/team_nebula/nebula/domain/star/search/service/ElasticsearchService.java +++ b/src/main/java/com/team_nebula/nebula/domain/star/search/service/ElasticsearchService.java @@ -177,7 +177,7 @@ public List getAutoComplete(String query, Long userId, int size) { } } - // 새로운 검색 메서드 (다른 용도로 사용) + // 검색 메서드 (더 넓은 범위 검색) @Cacheable(value = "starSearch", key = "#userId + '_' + #keyword + '_' + #page + '_' + #size") public List searchStarsSimple(Long userId, String keyword, int page, int size) { try { diff --git a/src/main/java/com/team_nebula/nebula/domain/star/service/StarCommandService.java b/src/main/java/com/team_nebula/nebula/domain/star/service/StarCommandService.java index 8e0acb5..c411ee5 100644 --- a/src/main/java/com/team_nebula/nebula/domain/star/service/StarCommandService.java +++ b/src/main/java/com/team_nebula/nebula/domain/star/service/StarCommandService.java @@ -6,6 +6,7 @@ import com.team_nebula.nebula.domain.star.dto.response.*; import org.springframework.web.multipart.MultipartFile; +import java.util.List; import java.util.UUID; public interface StarCommandService { @@ -23,4 +24,9 @@ public interface StarCommandService { public AddBookMarkResponseDTO addBookMark(Long userId, MultipartFile htmlFile, String title, String siteUrl); public CreateStarResponseDTO createStar(Long userId, CreateStarRequestDTO requestDTO); + + public void saveRecentSearches(Long userId, String keyword); + + public void deleteRecentSearch(Long userId, String keyword); + } diff --git a/src/main/java/com/team_nebula/nebula/domain/star/service/StarCommandServiceImpl.java b/src/main/java/com/team_nebula/nebula/domain/star/service/StarCommandServiceImpl.java index 233aaf4..f814808 100644 --- a/src/main/java/com/team_nebula/nebula/domain/star/service/StarCommandServiceImpl.java +++ b/src/main/java/com/team_nebula/nebula/domain/star/service/StarCommandServiceImpl.java @@ -29,18 +29,24 @@ import com.team_nebula.nebula.global.apipayload.exception.GeneralException; import lombok.RequiredArgsConstructor; import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; +import java.time.Duration; import java.time.LocalDateTime; import java.time.OffsetDateTime; +import java.util.List; import java.util.Optional; import java.util.UUID; import java.util.stream.Collectors; +import static org.springframework.data.neo4j.core.ReactiveNeo4jClient.log; + @Service @RequiredArgsConstructor @Transactional @@ -59,6 +65,9 @@ public class StarCommandServiceImpl implements StarCommandService { private final FaviconRepository faviconRepository; private final ApplicationEventPublisher eventPublisher; + private final RedisTemplate redisTemplate; + private static final int MAX_RECENT_SEARCHES = 10; + @Override public CreateStarResponseDTO createFirstStar(Long userId, MultipartFile htmlFile, String title, String siteUrl){ @@ -314,5 +323,32 @@ public CreateStarResponseDTO createStar(Long userId, CreateStarRequestDTO reques .build(); } + @Override + @Async + public void saveRecentSearches(Long userId, String keyword){ + try { + String key = "recent_search:" + userId; + String normalizedKeyword = keyword.trim().toLowerCase(); + + // 중복 제거 + redisTemplate.opsForList().remove(key, 0, normalizedKeyword); + redisTemplate.opsForList().leftPush(key, normalizedKeyword); + redisTemplate.opsForList().trim(key, 0, MAX_RECENT_SEARCHES - 1); + redisTemplate.expire(key, Duration.ofDays(30)); + } + catch (Exception e) { + log.error(e, "Failed to save recent search"); + } + } + + @Override + public void deleteRecentSearch(Long userId, String keyword) { + try { + String key = "recent_search:" + userId; + redisTemplate.opsForList().remove(key, 0, keyword.trim().toLowerCase()); + } catch (Exception e) { + log.error(e, "Failed to delete recent search"); + } + } }