diff --git a/src/main/java/friend/spring/config/RedisRepositoryConfig.java b/src/main/java/friend/spring/config/RedisRepositoryConfig.java index e4380d0..9bb2b6a 100644 --- a/src/main/java/friend/spring/config/RedisRepositoryConfig.java +++ b/src/main/java/friend/spring/config/RedisRepositoryConfig.java @@ -1,5 +1,11 @@ package friend.spring.config; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator; +import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; @@ -8,6 +14,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @RequiredArgsConstructor @@ -38,4 +45,23 @@ public RedisTemplate redisTemplate() { redisTemplate.setConnectionFactory(redisConnectionFactory()); return redisTemplate; } -} \ No newline at end of file + + @Bean + public RedisTemplate objectRedisTemplate(RedisConnectionFactory redisConnectionFactory){ + PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator + .builder() + .allowIfSubType(Object.class) + .build(); + ObjectMapper objectMapper = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.NON_FINAL) + .registerModule(new JavaTimeModule()) + .disable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS); + + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(redisConnectionFactory()); + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(new GenericJackson2JsonRedisSerializer(objectMapper)); + return template; + } +} diff --git a/src/main/java/friend/spring/converter/PostConverter.java b/src/main/java/friend/spring/converter/PostConverter.java index 0b53787..d6ab6ef 100644 --- a/src/main/java/friend/spring/converter/PostConverter.java +++ b/src/main/java/friend/spring/converter/PostConverter.java @@ -1,6 +1,7 @@ package friend.spring.converter; import friend.spring.domain.*; +import friend.spring.domain.Redis.SearchLog; import friend.spring.domain.enums.PostState; import friend.spring.domain.enums.PostType; import friend.spring.domain.enums.PostVoteType; @@ -1047,6 +1048,20 @@ public static PostResponseDTO.PostSearchList PostSearchListDTO(Page postLi .isEnd(postList.isLast()) .build(); } + + public static PostResponseDTO.RecentSearchRes toRecentSearchRes(List recentSearchLogs){ + List searchLogList = recentSearchLogs.stream().map(PostConverter::toSearchLog).collect(Collectors.toList()); + return PostResponseDTO.RecentSearchRes.builder() + .recentSearchList(searchLogList) + .build(); + } + + public static PostResponseDTO.SearchLog toSearchLog(SearchLog searchLog){ + return PostResponseDTO.SearchLog.builder() + .name(searchLog.getName()) + .createdAt(searchLog.getCreatedAt()) + .build(); + } } diff --git a/src/main/java/friend/spring/domain/Redis/SearchLog.java b/src/main/java/friend/spring/domain/Redis/SearchLog.java new file mode 100644 index 0000000..ec276ad --- /dev/null +++ b/src/main/java/friend/spring/domain/Redis/SearchLog.java @@ -0,0 +1,15 @@ +package friend.spring.domain.Redis; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Getter +public class SearchLog { + private String name; + private String createdAt; +} diff --git a/src/main/java/friend/spring/service/PostQueryService.java b/src/main/java/friend/spring/service/PostQueryService.java index 1c5822b..8260614 100644 --- a/src/main/java/friend/spring/service/PostQueryService.java +++ b/src/main/java/friend/spring/service/PostQueryService.java @@ -1,11 +1,11 @@ package friend.spring.service; import friend.spring.domain.Post; -import friend.spring.web.dto.PostRequestDTO; -import friend.spring.web.dto.PostResponseDTO; +import friend.spring.domain.Redis.SearchLog; import org.springframework.data.domain.Page; import javax.servlet.http.HttpServletRequest; +import java.util.List; import java.util.Optional; public interface PostQueryService { @@ -22,6 +22,7 @@ public interface PostQueryService { Page getReviewList(Integer page, Integer size, Integer arrange); Page getParentPostList(Integer page, Integer size, HttpServletRequest request); - Page getPostSearch(Integer page, Integer size, String search); + Page getPostSearch(Long userId,Integer page, Integer size, String search); + List getRecentSearchLogs(Long userId); } diff --git a/src/main/java/friend/spring/service/PostQueryServiceImpl.java b/src/main/java/friend/spring/service/PostQueryServiceImpl.java index 69a0115..cd8b90c 100644 --- a/src/main/java/friend/spring/service/PostQueryServiceImpl.java +++ b/src/main/java/friend/spring/service/PostQueryServiceImpl.java @@ -4,6 +4,7 @@ import friend.spring.apiPayload.code.status.ErrorStatus; import friend.spring.converter.PostConverter; import friend.spring.domain.*; +import friend.spring.domain.Redis.SearchLog; import friend.spring.domain.enums.PostState; import friend.spring.domain.enums.PostType; import friend.spring.domain.enums.PostVoteType; @@ -14,6 +15,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.data.domain.*; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -24,6 +26,8 @@ import java.util.Optional; import java.util.stream.Collectors; +import static friend.spring.apiPayload.code.status.ErrorStatus.USER_NOT_FOUND; + @Service @RequiredArgsConstructor public class PostQueryServiceImpl implements PostQueryService { @@ -34,6 +38,7 @@ public class PostQueryServiceImpl implements PostQueryService { private final Card_VoteRepository cardVoteRepository; private final CategoryRepository categoryRepository; private final JwtTokenProvider jwtTokenProvider; + private final RedisTemplate objectRedisTemplate; @Override @Transactional @@ -145,8 +150,31 @@ public Page getParentPostList(Integer page, Integer size, HttpServletReque @Override @Transactional - public Page getPostSearch(Integer page, Integer size, String search) { + public Page getPostSearch(Long userId,Integer page, Integer size, String search) { Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); + User user = userRepository.findById(userId).orElseThrow(() -> new GeneralException(USER_NOT_FOUND)); + String now = LocalDateTime.now().toString(); + String key = "CurrentSearch" + user.getId(); + SearchLog value = SearchLog.builder() + .name(search) + .createdAt(now) + .build(); + Long redisSize = objectRedisTemplate.opsForList().size(key); + if(redisSize == 10){ + objectRedisTemplate.opsForList().rightPop(key); + } + objectRedisTemplate.opsForList().leftPush(key, value); return postRepository.findByKeyWord(search, pageable); } + + public List getRecentSearchLogs(Long userId) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new GeneralException(USER_NOT_FOUND)); + + String key = "CurrentSearch" + user.getId(); + List objLogs = objectRedisTemplate.opsForList(). + range(key, 0, 10); + List searchLogs = objLogs.stream().map(i -> (SearchLog) i).collect(Collectors.toList()); + return searchLogs; + } } diff --git a/src/main/java/friend/spring/service/PostService.java b/src/main/java/friend/spring/service/PostService.java index 249f4db..063bdb2 100644 --- a/src/main/java/friend/spring/service/PostService.java +++ b/src/main/java/friend/spring/service/PostService.java @@ -9,6 +9,7 @@ import friend.spring.web.dto.CandidateRequestDTO; import friend.spring.web.dto.PostRequestDTO; import friend.spring.web.dto.PostResponseDTO; +import friend.spring.web.dto.UserRequestDTO; import org.springframework.data.domain.Page; import javax.servlet.http.HttpServletRequest; @@ -49,6 +50,7 @@ public interface PostService { void deleteScrapPost(Long postId, HttpServletRequest request); + PostResponseDTO.ReportResult createReportPost(Long postId, PostRequestDTO.PostReportReq request, HttpServletRequest request2); } diff --git a/src/main/java/friend/spring/service/PostServiceImpl.java b/src/main/java/friend/spring/service/PostServiceImpl.java index 2731ef5..325fa71 100644 --- a/src/main/java/friend/spring/service/PostServiceImpl.java +++ b/src/main/java/friend/spring/service/PostServiceImpl.java @@ -6,6 +6,7 @@ import friend.spring.apiPayload.handler.UserHandler; import friend.spring.converter.PostConverter; import friend.spring.domain.*; +import friend.spring.domain.Redis.SearchLog; import friend.spring.domain.enums.PostState; import friend.spring.domain.enums.PostType; import friend.spring.domain.enums.ReportType; @@ -17,6 +18,7 @@ import friend.spring.web.dto.*; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.*; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -459,7 +461,6 @@ public Post_scrap createScrapPost(Long postId, HttpServletRequest request) { @Override public void deleteScrapPost(Long postId, HttpServletRequest request) { Long userId = jwtTokenProvider.getCurrentUser(request); - Optional optionalPost = postRepository.findById(postId); if (optionalPost.isEmpty()) { this.checkPost(false); diff --git a/src/main/java/friend/spring/web/controller/PostRestController.java b/src/main/java/friend/spring/web/controller/PostRestController.java index 6c8f519..c091e97 100644 --- a/src/main/java/friend/spring/web/controller/PostRestController.java +++ b/src/main/java/friend/spring/web/controller/PostRestController.java @@ -5,6 +5,7 @@ import friend.spring.converter.PostConverter; import friend.spring.domain.Candidate; import friend.spring.domain.Post; +import friend.spring.domain.Redis.SearchLog; import friend.spring.repository.PostRepository; import friend.spring.service.JwtTokenService; import friend.spring.service.PostQueryService; @@ -25,6 +26,7 @@ import javax.servlet.http.HttpServletRequest; import javax.validation.Valid; import java.io.IOException; +import java.util.List; @RestController @RequiredArgsConstructor @@ -334,15 +336,28 @@ public ApiResponse createReportPost( @Parameter(name = "page", description = "query string(RequestParam) - 몇번째 페이지인지 가리키는 page 변수 입니다! (0부터 시작)"), @Parameter(name = "size", description = "query string(RequestParam) - 몇 개씩 불러올지 개수를 세는 변수입니다. (1 이상 자연수로 설정)"), @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken"), - @Parameter(name = "search", description = "query string(RequestParam) - 검색어.") + @Parameter(name = "keyword", description = "query string(RequestParam) - 검색어.") }) public ApiResponse getPostSearch(@RequestParam(name = "page", defaultValue = "0") Integer page, @RequestParam(name = "size", defaultValue = "15") Integer size, - @RequestParam(name = "search") String search, + @RequestParam(name = "keyword") String keyword, @RequestHeader("atk") String atk, HttpServletRequest request2) { Long userId = jwtTokenService.JwtToId(request2); - Page postPage = postQueryService.getPostSearch(page, size, search); + Page postPage = postQueryService.getPostSearch(userId,page, size, keyword); return ApiResponse.onSuccess(PostConverter.PostSearchListDTO(postPage, userId)); } + + @GetMapping("/post/search/recent-log") + @Operation(summary = "최근 검색어 목록 조회 API", description = "최신순으로 검색어 목록을 최대 10개까지 조회합니다.") + @Parameters({ + @Parameter(name = "atk", description = "RequestHeader - 로그인한 사용자의 accessToken"), + }) + public ApiResponse getRecentSearch(@RequestHeader("atk") String atk, + HttpServletRequest request){ + Long userId = jwtTokenService.JwtToId(request); + List recentSearchLogs = postQueryService.getRecentSearchLogs(userId); + return ApiResponse.onSuccess(PostConverter.toRecentSearchRes(recentSearchLogs)); + + } } diff --git a/src/main/java/friend/spring/web/dto/PostResponseDTO.java b/src/main/java/friend/spring/web/dto/PostResponseDTO.java index b53260f..ac55e49 100644 --- a/src/main/java/friend/spring/web/dto/PostResponseDTO.java +++ b/src/main/java/friend/spring/web/dto/PostResponseDTO.java @@ -184,7 +184,15 @@ public static class PostSearchRes{ Boolean isLike; Boolean isComment; } - + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class RecentSearchRes { + List recentSearchList; + } + @Builder @Getter @NoArgsConstructor @@ -199,7 +207,16 @@ public static class PostReportRes { @Getter @NoArgsConstructor @AllArgsConstructor - public static class ReportResult { + public static class SearchLog{ + private String name; + private String createdAt; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class ReportResult{ Report report; Boolean duplicatedReport; } diff --git a/src/main/java/friend/spring/web/dto/UserRequestDTO.java b/src/main/java/friend/spring/web/dto/UserRequestDTO.java index 7bc997e..acd92a4 100644 --- a/src/main/java/friend/spring/web/dto/UserRequestDTO.java +++ b/src/main/java/friend/spring/web/dto/UserRequestDTO.java @@ -142,4 +142,12 @@ public static class PasswordUpdateReq { public static class Delete { private String email; } + @NoArgsConstructor + @AllArgsConstructor + @Getter + @Setter + public static class SearchReq{ + private String name; + private String createdAt; + } } \ No newline at end of file