diff --git a/src/main/java/site/billbill/apiserver/api/borrowPosts/controller/PostsController.java b/src/main/java/site/billbill/apiserver/api/borrowPosts/controller/PostsController.java index 77b7714..7f8ef76 100644 --- a/src/main/java/site/billbill/apiserver/api/borrowPosts/controller/PostsController.java +++ b/src/main/java/site/billbill/apiserver/api/borrowPosts/controller/PostsController.java @@ -18,6 +18,8 @@ import site.billbill.apiserver.common.response.BaseResponse; import site.billbill.apiserver.common.utils.jwt.JWTUtil; +import java.util.List; + @Slf4j @RestController @Tag(name = "borrowPosts", description = "대여 게시물 관련") @@ -25,6 +27,7 @@ @RequiredArgsConstructor public class PostsController { private final PostsService postsService; + @Operation(summary = "게시물 생성", description = "게시물 생성 API") @PostMapping("") public BaseResponse uploadPostsController(@RequestBody @Valid PostsRequest.UploadRequest request){ @@ -47,12 +50,14 @@ public BaseResponse getPostsController( @Parameter(name = "order", description = "정렬 방향 (asc: 오름차순, desc: 내림차순)", example = "desc", in = ParameterIn.QUERY, required = false) @RequestParam(value ="order",required = false,defaultValue = "desc") String order, @Parameter(name = "sortBy", description = "정렬 기준 (예: price, createdAt, likeCount)", example = "createdAt", in = ParameterIn.QUERY, required = true) - @RequestParam(value="sortBy",required = true,defaultValue = "accuracy") String sortBy){ + @RequestParam(value="sortBy",required = true,defaultValue = "accuracy") String sortBy + ){ Sort.Direction direction = "asc".equalsIgnoreCase(order) ? Sort.Direction.ASC : Sort.Direction.DESC; return new BaseResponse<>(postsService.ViewAllPostService(category,page,direction,sortBy)); } + @Operation(summary = "게시물 검색", description = "게시물 검색 API") @GetMapping("/search") public BaseResponse getSearchPostsController( @Parameter(name = "category", description = "카테고리 필터 (예: entire, camp, sports,tools )", example = "entire", in = ParameterIn.QUERY, required = false) @@ -64,17 +69,24 @@ public BaseResponse getSearchPostsControlle @Parameter(name = "sortBy", description = "정렬 기준 (예: price, createdAt, likeCount)", example = "createdAt", in = ParameterIn.QUERY, required = true) @RequestParam(value="sortBy",required = true,defaultValue = "accuracy") String sortBy, @Parameter(name="keyword",description = "검색 키워드(예: 6인용+텐트)",in = ParameterIn.QUERY, required = true) - @RequestParam(value = "keyword",required = true) String keyword) { - + @RequestParam(value = "keyword",required = true) String keyword, + @Parameter(name = "state",description = "검색어 저장 여부", in = ParameterIn.QUERY, required = true) + @RequestParam(value = "state",required = true,defaultValue = "true") boolean state) { + String userId = ""; + if(MDC.get(JWTUtil.MDC_USER_ID) != null) { + userId= MDC.get(JWTUtil.MDC_USER_ID).toString(); + } Sort.Direction direction = "asc".equalsIgnoreCase(order) ? Sort.Direction.ASC : Sort.Direction.DESC; - return new BaseResponse<>(postsService.ViewSearchPostService(category, page, direction, sortBy,keyword)); + return new BaseResponse<>(postsService.ViewSearchPostService(userId,category, page, direction, sortBy,keyword,state)); } + @Operation(summary = "게시물 조회", description = "게시물 상세 조회") @GetMapping("/{postId}") public BaseResponse getPostController(@PathVariable(value = "postId",required = true)String postId){ return new BaseResponse<>(postsService.ViewPostService(postId)); } + @Operation(summary = "게시물 삭제", description = "게시물 삭제") @DeleteMapping("/{postId}") public BaseResponse deletePostController(@PathVariable(value = "postId",required = true)String postId){ @@ -84,6 +96,21 @@ public BaseResponse deletePostController(@PathVariable(value = "postId", } return new BaseResponse<>(postsService.deletePostService(postId,userId)); } + @Operation(summary = "저장한 검색어 불러오기", description = "저장한 검색어 불러오기") + @GetMapping("/searchHist") + public BaseResponse> getSearchHistController(){ + String userId = ""; + if(MDC.get(JWTUtil.MDC_USER_ID) != null) { + userId= MDC.get(JWTUtil.MDC_USER_ID).toString(); + } + return new BaseResponse<>(postsService.findSearchService(userId)); + } + @Operation(summary = "추천 검색어 불러오기", description = "추천 검색어 주기") + @GetMapping("/recommend") + public BaseResponse> getRecommendController(){ + return new BaseResponse<>(postsService.findRecommandService()); + } + @Operation(summary = "게시물 수정", description = "게시물 수정") @PatchMapping("/{postId}") public BaseResponse updatePostController(@PathVariable(value="postId",required = true)String postId, @RequestBody @Valid PostsRequest.UploadRequest request){ diff --git a/src/main/java/site/billbill/apiserver/api/borrowPosts/converter/PostsConverter.java b/src/main/java/site/billbill/apiserver/api/borrowPosts/converter/PostsConverter.java index d214d29..f36676e 100644 --- a/src/main/java/site/billbill/apiserver/api/borrowPosts/converter/PostsConverter.java +++ b/src/main/java/site/billbill/apiserver/api/borrowPosts/converter/PostsConverter.java @@ -2,11 +2,10 @@ import site.billbill.apiserver.api.borrowPosts.dto.request.PostsRequest; import site.billbill.apiserver.api.borrowPosts.dto.response.PostsResponse; -import site.billbill.apiserver.model.post.ItemsBorrowJpaEntity; -import site.billbill.apiserver.model.post.ItemsBorrowStatusJpaEntity; -import site.billbill.apiserver.model.post.ItemsCategoryJpaEntity; -import site.billbill.apiserver.model.post.ItemsJpaEntity; +import site.billbill.apiserver.model.post.*; import site.billbill.apiserver.model.user.UserJpaEntity; +import site.billbill.apiserver.model.user.UserSearchHistJpaEntity; +import site.billbill.apiserver.repository.borrowPosts.SearchKeywordStatRepository; import java.time.format.DateTimeFormatter; import java.util.List; @@ -88,5 +87,22 @@ public static PostsResponse.NoRentalPeriodResponse toNoRentalPeriod(ItemsBorrowS .endDate(borrowStatus.getEndDate().format(DATE_FORMATTER)) .build(); } + public static UserSearchHistJpaEntity toUserSearch(UserJpaEntity user,String keyword){ + return UserSearchHistJpaEntity.builder() + .keyword(keyword) + .user(user).build(); + } + public static SearchKeywordStatsJpaEntity toSearchKeywordStats(String keyword){ + return SearchKeywordStatsJpaEntity.builder() + .keyword(keyword) + .searchCount(1).build(); + } + public static String toUserSearchHist(UserSearchHistJpaEntity userSearchHist){ + return userSearchHist.getKeyword(); + } + public static String toRecommandSearch(SearchKeywordStatsJpaEntity searchKeywordStats){ + return searchKeywordStats.getKeyword(); + } + } diff --git a/src/main/java/site/billbill/apiserver/api/borrowPosts/service/PostsService.java b/src/main/java/site/billbill/apiserver/api/borrowPosts/service/PostsService.java index 9029484..2cae11f 100644 --- a/src/main/java/site/billbill/apiserver/api/borrowPosts/service/PostsService.java +++ b/src/main/java/site/billbill/apiserver/api/borrowPosts/service/PostsService.java @@ -4,6 +4,8 @@ import site.billbill.apiserver.api.borrowPosts.dto.request.PostsRequest; import site.billbill.apiserver.api.borrowPosts.dto.response.PostsResponse; +import java.util.List; + public interface PostsService { PostsResponse.UploadResponse uploadPostService(PostsRequest.UploadRequest request,String userId); @@ -12,7 +14,13 @@ public interface PostsService { PostsResponse.ViewPostResponse ViewPostService(String postId); String deletePostService(String postId,String userId); + String UpdatePostService(String postId,String userId,PostsRequest.UploadRequest request); - PostsResponse.ViewAllResultResponse ViewSearchPostService(String category, int page, Sort.Direction direction, String orderType,String keyword); + PostsResponse.ViewAllResultResponse ViewSearchPostService(String userId,String category, int page, Sort.Direction direction, String orderType,String keyword,boolean state); + + List findSearchService(String userId); + + List findRecommandService(); + } diff --git a/src/main/java/site/billbill/apiserver/api/borrowPosts/service/PostsServiceImpl.java b/src/main/java/site/billbill/apiserver/api/borrowPosts/service/PostsServiceImpl.java index 67eeb8d..b90070d 100644 --- a/src/main/java/site/billbill/apiserver/api/borrowPosts/service/PostsServiceImpl.java +++ b/src/main/java/site/billbill/apiserver/api/borrowPosts/service/PostsServiceImpl.java @@ -1,37 +1,28 @@ package site.billbill.apiserver.api.borrowPosts.service; import lombok.RequiredArgsConstructor; -import lombok.extern.java.Log; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.http.HttpStatus; import org.springframework.transaction.annotation.Transactional; -import site.billbill.apiserver.api.borrowPosts.controller.PostsController; import site.billbill.apiserver.api.borrowPosts.converter.PostsConverter; import site.billbill.apiserver.api.borrowPosts.dto.request.PostsRequest; import site.billbill.apiserver.api.borrowPosts.dto.response.PostsResponse; import site.billbill.apiserver.common.enums.exception.ErrorCode; import site.billbill.apiserver.common.utils.ULID.ULIDUtil; import site.billbill.apiserver.exception.CustomException; -import site.billbill.apiserver.model.post.ItemsBorrowJpaEntity; -import site.billbill.apiserver.model.post.ItemsBorrowStatusJpaEntity; -import site.billbill.apiserver.model.post.ItemsCategoryJpaEntity; -import site.billbill.apiserver.model.post.ItemsJpaEntity; +import site.billbill.apiserver.model.post.*; import site.billbill.apiserver.model.user.UserJpaEntity; -import site.billbill.apiserver.repository.borrowPosts.ItemsBorrowRepository; -import site.billbill.apiserver.repository.borrowPosts.ItemsBorrowStatusRepository; -import site.billbill.apiserver.repository.borrowPosts.ItemsCategoryRepository; -import site.billbill.apiserver.repository.borrowPosts.ItemsRepository; +import site.billbill.apiserver.model.user.UserSearchHistJpaEntity; +import site.billbill.apiserver.repository.borrowPosts.*; import site.billbill.apiserver.repository.user.UserRepository; +import site.billbill.apiserver.repository.user.UserSearchHistRepository; -import javax.swing.text.html.Option; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; @org.springframework.stereotype.Service @RequiredArgsConstructor @@ -44,6 +35,8 @@ public class PostsServiceImpl implements PostsService { private final ItemsBorrowRepository itemsBorrowRepository; private final ItemsBorrowStatusRepository itemsBorrowStatusRepository; private final ItemsCategoryRepository itemsCategoryRepository; + private final UserSearchHistRepository userSearchHistRepository; + private final SearchKeywordStatRepository searchKeywordStatRepository; public PostsResponse.UploadResponse uploadPostService(PostsRequest.UploadRequest request,String userId){ //먼저 item 생성, Optional isUser=userRepository.findById(userId); @@ -160,14 +153,45 @@ public String UpdatePostService(String postId,String userId,PostsRequest.UploadR } return "success"; } - public PostsResponse.ViewAllResultResponse ViewSearchPostService(String category, int page, Sort.Direction direction, String orderType,String keyword){ + @Transactional + public PostsResponse.ViewAllResultResponse ViewSearchPostService(String userId, String category, int page, Sort.Direction direction, String orderType,String keyword,boolean state){ + UserJpaEntity user = userRepository.findById(userId).orElse(null); + Pageable pageable = createPageable(page, direction, orderType); List items = findAndConvertItems(category, pageable, keyword); + //사용자가 검색어 저장을 허용했을 경우 + String tempKeyword = keyword.replaceAll("\\+", " "); + if(state){ + + UserSearchHistJpaEntity userSearchHist= PostsConverter.toUserSearch(user,tempKeyword); + userSearchHistRepository.save(userSearchHist); + } + //추천 검색어를 위해 검색어 를 저장 + SearchKeywordStatsJpaEntity searchKeywordStats = searchKeywordStatRepository.findByKeyword(tempKeyword); + if(searchKeywordStats!=null){ + int count=searchKeywordStats.getSearchCount()+1; + searchKeywordStats.setSearchCount(count); + }else{ + searchKeywordStats = PostsConverter.toSearchKeywordStats(tempKeyword); + searchKeywordStatRepository.save(searchKeywordStats); + } + return PostsConverter.toViewAllList(items); } + public List findSearchService(String userId){ + UserJpaEntity user = userRepository.findById(userId).orElse(null); + List searchHists=userSearchHistRepository.findByUserAndDelYnOrderByCreatedAtDesc(user,false); + List result= searchHists.stream().map(searchHist-> PostsConverter.toUserSearchHist(searchHist)).toList(); + return result; + } + public List findRecommandService(){ + List searchKeywordStats=searchKeywordStatRepository.findAllByOrderBySearchCountDesc(); + List result =searchKeywordStats.stream().map(searchKeywordStat-> PostsConverter.toRecommandSearch(searchKeywordStat)).toList(); + return result; + } //모듈화 코드 private Pageable createPageable(int page, Sort.Direction direction, String orderType) { diff --git a/src/main/java/site/billbill/apiserver/model/post/SearchKeywordStatsJpaEntity.java b/src/main/java/site/billbill/apiserver/model/post/SearchKeywordStatsJpaEntity.java new file mode 100644 index 0000000..68733a6 --- /dev/null +++ b/src/main/java/site/billbill/apiserver/model/post/SearchKeywordStatsJpaEntity.java @@ -0,0 +1,28 @@ +package site.billbill.apiserver.model.post; + +import jakarta.persistence.*; +import lombok.*; + +import site.billbill.apiserver.model.BaseTime; + +import java.time.LocalDate; + +@Entity +@Table(name = "search_keyword_stats") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class SearchKeywordStatsJpaEntity extends BaseTime { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name="keyword_seq") + private long id; + + @Column(name="keyword",nullable = true) + private String keyword; + @Column(name = "search_count", nullable = false) + private int searchCount; + +} diff --git a/src/main/java/site/billbill/apiserver/model/user/UserSearchHistJpaEntity.java b/src/main/java/site/billbill/apiserver/model/user/UserSearchHistJpaEntity.java new file mode 100644 index 0000000..87804b1 --- /dev/null +++ b/src/main/java/site/billbill/apiserver/model/user/UserSearchHistJpaEntity.java @@ -0,0 +1,30 @@ +package site.billbill.apiserver.model.user; + +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.DynamicUpdate; +import site.billbill.apiserver.common.converter.BooleanConverter; +import site.billbill.apiserver.model.BaseTime; +@DynamicUpdate +@Entity +@Table(name = "users_search_hist") +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class UserSearchHistJpaEntity extends BaseTime { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "search_seq", nullable = false) + private String searchId; + @Column(name = "keyword", nullable = true) + private String keyword; + @Column(name = "del_yn", nullable = false) + @Convert(converter = BooleanConverter.class) + private boolean delYn; + @ManyToOne + @JoinColumn(name="user_id") + private UserJpaEntity user; + +} diff --git a/src/main/java/site/billbill/apiserver/repository/borrowPosts/SearchKeywordStatRepository.java b/src/main/java/site/billbill/apiserver/repository/borrowPosts/SearchKeywordStatRepository.java new file mode 100644 index 0000000..4462093 --- /dev/null +++ b/src/main/java/site/billbill/apiserver/repository/borrowPosts/SearchKeywordStatRepository.java @@ -0,0 +1,11 @@ +package site.billbill.apiserver.repository.borrowPosts; + +import org.springframework.data.jpa.repository.JpaRepository; +import site.billbill.apiserver.model.post.SearchKeywordStatsJpaEntity; + +import java.util.List; + +public interface SearchKeywordStatRepository extends JpaRepository { + SearchKeywordStatsJpaEntity findByKeyword(String keyword); + List findAllByOrderBySearchCountDesc(); +} diff --git a/src/main/java/site/billbill/apiserver/repository/user/UserSearchHistRepository.java b/src/main/java/site/billbill/apiserver/repository/user/UserSearchHistRepository.java new file mode 100644 index 0000000..9e8d1c4 --- /dev/null +++ b/src/main/java/site/billbill/apiserver/repository/user/UserSearchHistRepository.java @@ -0,0 +1,12 @@ +package site.billbill.apiserver.repository.user; + +import org.springframework.data.jpa.repository.JpaRepository; +import site.billbill.apiserver.model.user.UserJpaEntity; +import site.billbill.apiserver.model.user.UserSearchHistJpaEntity; + +import java.util.List; + + +public interface UserSearchHistRepository extends JpaRepository { + List findByUserAndDelYnOrderByCreatedAtDesc(UserJpaEntity user,boolean delYn); +}