From b7a83e40c8e63a9187034458b839c65c85363cdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=AF=E1=86=AB=E1=84=8C=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=89=E1=85=AE=E1=86=AB?= Date: Fri, 20 Dec 2024 16:58:51 +0900 Subject: [PATCH 1/2] =?UTF-8?q?[=E2=9C=A8feat/#185]:=20=EA=B3=B5=EA=B3=A0?= =?UTF-8?q?=20=ED=8E=98=EC=9D=B4=EC=A7=95=20=EB=B0=8F=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 공고 조회 API에 페이징 기능 추가 - Pageable을 활용한 페이징 처리 구현 - `@PageableDefault`를 통해 기본 페이지 크기 설정 - DTO 수정 - HomeAnnouncementsResponseDto에 페이징 관련 필드 추가 (`totalPages`, `hasNext`) - Repository 수정 - 페이징 처리된 공고 데이터를 반환하도록 쿼리 수정 - `Page`를 활용한 페이징 로직 구현 - Service 로직 리팩토링 - 공통 메서드 분리 (`getUserById`, `createEmptyResponse` 등) - Color 값 및 스크랩 여부 처리 메서드 분리 (`isScrapped`, `determineColorValue`) - Swagger 수정 - API 문서에 페이징 파라미터 반영 --- .../controller/HomeController.java | 9 ++- .../controller/swagger/HomeSwagger.java | 4 +- .../HomeAnnouncementsResponseDto.java | 12 +++- .../InternshipRepositoryCustom.java | 3 +- .../InternshipRepositoryImpl.java | 23 +++++-- .../terningserver/service/HomeService.java | 3 +- .../service/HomeServiceImpl.java | 64 +++++++++++++------ 7 files changed, 86 insertions(+), 32 deletions(-) diff --git a/src/main/java/org/terning/terningserver/controller/HomeController.java b/src/main/java/org/terning/terningserver/controller/HomeController.java index 55482bb..a0d0823 100644 --- a/src/main/java/org/terning/terningserver/controller/HomeController.java +++ b/src/main/java/org/terning/terningserver/controller/HomeController.java @@ -1,6 +1,8 @@ package org.terning.terningserver.controller; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; @@ -26,13 +28,14 @@ public class HomeController implements HomeSwagger { @GetMapping("/home") public ResponseEntity> getAnnouncements( @AuthenticationPrincipal Long userId, - @RequestParam(value = "sortBy", required = false, defaultValue = "deadlineSoon") String sortBy - ){ - HomeAnnouncementsResponseDto announcements = homeService.getAnnouncements(userId, sortBy); + @RequestParam(value = "sortBy", required = false, defaultValue = "deadlineSoon") String sortBy, + @PageableDefault(size = 10) Pageable pageable) { + HomeAnnouncementsResponseDto announcements = homeService.getAnnouncements(userId, sortBy, pageable); return ResponseEntity.ok(SuccessResponse.of(SUCCESS_GET_ANNOUNCEMENTS, announcements)); } + @GetMapping("/home/upcoming") public ResponseEntity>> getUpcomingScraps( @AuthenticationPrincipal Long userId diff --git a/src/main/java/org/terning/terningserver/controller/swagger/HomeSwagger.java b/src/main/java/org/terning/terningserver/controller/swagger/HomeSwagger.java index 8b4cdb6..5e8a392 100644 --- a/src/main/java/org/terning/terningserver/controller/swagger/HomeSwagger.java +++ b/src/main/java/org/terning/terningserver/controller/swagger/HomeSwagger.java @@ -2,6 +2,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.data.domain.Pageable; import org.springframework.http.ResponseEntity; import org.terning.terningserver.dto.user.response.HomeAnnouncementsResponseDto; import org.terning.terningserver.dto.user.response.UpcomingScrapResponseDto; @@ -15,7 +16,8 @@ public interface HomeSwagger { @Operation(summary = "홈화면 > 나에게 딱맞는 인턴 공고 조회", description = "특정 사용자에 필터링 조건에 맞는 인턴 공고 정보를 조회하는 API") ResponseEntity> getAnnouncements( Long userId, - String sortBy + String sortBy, + Pageable pageable ); @Operation(summary = "홈화면 > 곧 마감인 스크랩 공고 조회", description = "곧 마감인 스크랩 공고를 조회하는 API") diff --git a/src/main/java/org/terning/terningserver/dto/user/response/HomeAnnouncementsResponseDto.java b/src/main/java/org/terning/terningserver/dto/user/response/HomeAnnouncementsResponseDto.java index 619b1c9..b32f2e7 100644 --- a/src/main/java/org/terning/terningserver/dto/user/response/HomeAnnouncementsResponseDto.java +++ b/src/main/java/org/terning/terningserver/dto/user/response/HomeAnnouncementsResponseDto.java @@ -6,12 +6,20 @@ @Builder public record HomeAnnouncementsResponseDto( - int totalCount, // 필터링 된 공고 총 개수 + int totalPages, + long totalCount, + boolean hasNext, List result ) { - public static HomeAnnouncementsResponseDto of(final int totalCount, final List announcements){ + public static HomeAnnouncementsResponseDto of( + final int totalPages, + final long totalCount, + final boolean hasNext, + final List announcements) { return HomeAnnouncementsResponseDto.builder() + .totalPages(totalPages) .totalCount(totalCount) + .hasNext(hasNext) .result(announcements) .build(); } diff --git a/src/main/java/org/terning/terningserver/repository/internship_announcement/InternshipRepositoryCustom.java b/src/main/java/org/terning/terningserver/repository/internship_announcement/InternshipRepositoryCustom.java index 0e3814b..c6eefcd 100644 --- a/src/main/java/org/terning/terningserver/repository/internship_announcement/InternshipRepositoryCustom.java +++ b/src/main/java/org/terning/terningserver/repository/internship_announcement/InternshipRepositoryCustom.java @@ -15,6 +15,5 @@ public interface InternshipRepositoryCustom { Page searchInternshipAnnouncement(String keyword, String sortBy, Pageable pageable); - List findFilteredInternshipsWithScrapInfo(User user, String sortBy); - + Page findFilteredInternshipsWithScrapInfo(User user, String sortBy, Pageable pageable); } diff --git a/src/main/java/org/terning/terningserver/repository/internship_announcement/InternshipRepositoryImpl.java b/src/main/java/org/terning/terningserver/repository/internship_announcement/InternshipRepositoryImpl.java index aeaf173..d73690f 100644 --- a/src/main/java/org/terning/terningserver/repository/internship_announcement/InternshipRepositoryImpl.java +++ b/src/main/java/org/terning/terningserver/repository/internship_announcement/InternshipRepositoryImpl.java @@ -110,9 +110,9 @@ private OrderSpecifier createOrderSpecifier(String sortBy) { } @Override - public List findFilteredInternshipsWithScrapInfo(User user, String sortBy){ - return jpaQueryFactory - .select(internshipAnnouncement, scrap.id, scrap.color) // tuple -> Scrap 정보 한번에 불러오기 + public Page findFilteredInternshipsWithScrapInfo(User user, String sortBy, Pageable pageable) { + List content = jpaQueryFactory + .select(internshipAnnouncement, scrap.id, scrap.color) .from(internshipAnnouncement) .leftJoin(internshipAnnouncement.scraps, scrap).on(scrap.user.eq(user)) .where( @@ -125,9 +125,24 @@ public List findFilteredInternshipsWithScrapInfo(User user, String sortBy sortAnnouncementsByDeadline().asc(), getSortOrder(sortBy) ) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) .fetch(); + + JPAQuery countQuery = jpaQueryFactory + .select(internshipAnnouncement.count()) + .from(internshipAnnouncement) + .where( + getJobTypeFilter(user), + getGraduatingFilter(user), + getWorkingPeriodFilter(user), + getStartDateFilter(user) + ); + + return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne); } - + + private BooleanExpression getGraduatingFilter(User user){ if(user.getFilter().getGrade() != Grade.SENIOR){ return internshipAnnouncement.isGraduating.isFalse(); diff --git a/src/main/java/org/terning/terningserver/service/HomeService.java b/src/main/java/org/terning/terningserver/service/HomeService.java index c5a3345..65425c9 100644 --- a/src/main/java/org/terning/terningserver/service/HomeService.java +++ b/src/main/java/org/terning/terningserver/service/HomeService.java @@ -1,8 +1,9 @@ package org.terning.terningserver.service; +import org.springframework.data.domain.Pageable; import org.terning.terningserver.dto.user.response.HomeAnnouncementsResponseDto; public interface HomeService { - HomeAnnouncementsResponseDto getAnnouncements(Long userId, String sortBy); + HomeAnnouncementsResponseDto getAnnouncements(Long userId, String sortBy, Pageable pageable); } diff --git a/src/main/java/org/terning/terningserver/service/HomeServiceImpl.java b/src/main/java/org/terning/terningserver/service/HomeServiceImpl.java index 4990d2d..653de0d 100644 --- a/src/main/java/org/terning/terningserver/service/HomeServiceImpl.java +++ b/src/main/java/org/terning/terningserver/service/HomeServiceImpl.java @@ -2,6 +2,8 @@ import com.querydsl.core.Tuple; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.terning.terningserver.domain.InternshipAnnouncement; @@ -22,47 +24,71 @@ @Service @RequiredArgsConstructor @Transactional(readOnly = true) -public class HomeServiceImpl implements HomeService{ +public class HomeServiceImpl implements HomeService { private final InternshipRepository internshipRepository; private final UserRepository userRepository; @Override - public HomeAnnouncementsResponseDto getAnnouncements(Long userId, String sortBy){ - User user = userRepository.findById(userId).orElseThrow( - () -> new CustomException(ErrorMessage.NOT_FOUND_USER_EXCEPTION) - ); + public HomeAnnouncementsResponseDto getAnnouncements(Long userId, String sortBy, Pageable pageable) { + User user = getUserById(userId); - // 유저의 필터 정보가 없는 경우 - if(user.getFilter() == null){ - return HomeAnnouncementsResponseDto.of(0,List.of()); + if (user.getFilter() == null) { + return createEmptyResponse(); } - List announcements = internshipRepository.findFilteredInternshipsWithScrapInfo(user, sortBy); + Page pagedAnnouncements = internshipRepository.findFilteredInternshipsWithScrapInfo(user, sortBy, pageable); - // 해당하는 공고가 없는 경우 - if(announcements.isEmpty()){ - return HomeAnnouncementsResponseDto.of(0, List.of()); + if (pagedAnnouncements.isEmpty()) { + return createEmptyResponse(); } - List responseDtos = announcements.stream() + List responseDtos = mapToHomeResponseDtos(pagedAnnouncements); + + return createResponse(pagedAnnouncements, responseDtos); + } + + private User getUserById(Long userId) { + return userRepository.findById(userId) + .orElseThrow(() -> new CustomException(ErrorMessage.NOT_FOUND_USER_EXCEPTION)); + } + + private HomeAnnouncementsResponseDto createEmptyResponse() { + return HomeAnnouncementsResponseDto.of(0, 0, false, List.of()); + } + + private List mapToHomeResponseDtos(Page pagedAnnouncements) { + return pagedAnnouncements.getContent().stream() .map(tuple -> { InternshipAnnouncement announcement = tuple.get(internshipAnnouncement); Long scrapId = tuple.get(scrap.id); Color color = tuple.get(scrap.color); - boolean isScrapped = (scrapId != null); // 스크랩 여부 - // scrap 하지 않은 경우 color는 지정되지 않아야 한다. - String colorValue = (isScrapped && color != null) ? color.getColorValue() : null; + boolean isScrapped = isScrapped(scrapId); + String colorValue = determineColorValue(isScrapped, color); return HomeResponseDto.of(announcement, isScrapped, colorValue); }) .toList(); - - return HomeAnnouncementsResponseDto.of(responseDtos.size(), responseDtos); } private boolean isScrapped(Long scrapId) { return scrapId != null; } -} + + private String determineColorValue(boolean isScrapped, Color color) { + if (isScrapped && color != null) { + return color.getColorValue(); + } + return null; + } + + private HomeAnnouncementsResponseDto createResponse(Page pagedAnnouncements, List responseDtos) { + return HomeAnnouncementsResponseDto.of( + pagedAnnouncements.getTotalPages(), + pagedAnnouncements.getTotalElements(), + pagedAnnouncements.hasNext(), + responseDtos + ); + } +} \ No newline at end of file From be616ba2d5e3555ee225e408fd87dbd32f02cb79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=AF=E1=86=AB=E1=84=8C=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=89=E1=85=AE=E1=86=AB?= Date: Sat, 21 Dec 2024 21:00:18 +0900 Subject: [PATCH 2/2] =?UTF-8?q?[=E2=99=BB=EF=B8=8F=20refactor/#185]:=20?= =?UTF-8?q?=EA=B3=B5=EB=B0=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/terning/terningserver/controller/HomeController.java | 1 - .../internship_announcement/InternshipRepositoryImpl.java | 1 - 2 files changed, 2 deletions(-) diff --git a/src/main/java/org/terning/terningserver/controller/HomeController.java b/src/main/java/org/terning/terningserver/controller/HomeController.java index a0d0823..8e52221 100644 --- a/src/main/java/org/terning/terningserver/controller/HomeController.java +++ b/src/main/java/org/terning/terningserver/controller/HomeController.java @@ -31,7 +31,6 @@ public ResponseEntity> getAnnounce @RequestParam(value = "sortBy", required = false, defaultValue = "deadlineSoon") String sortBy, @PageableDefault(size = 10) Pageable pageable) { HomeAnnouncementsResponseDto announcements = homeService.getAnnouncements(userId, sortBy, pageable); - return ResponseEntity.ok(SuccessResponse.of(SUCCESS_GET_ANNOUNCEMENTS, announcements)); } diff --git a/src/main/java/org/terning/terningserver/repository/internship_announcement/InternshipRepositoryImpl.java b/src/main/java/org/terning/terningserver/repository/internship_announcement/InternshipRepositoryImpl.java index d73690f..0b108de 100644 --- a/src/main/java/org/terning/terningserver/repository/internship_announcement/InternshipRepositoryImpl.java +++ b/src/main/java/org/terning/terningserver/repository/internship_announcement/InternshipRepositoryImpl.java @@ -142,7 +142,6 @@ public Page findFilteredInternshipsWithScrapInfo(User user, String sortBy return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne); } - private BooleanExpression getGraduatingFilter(User user){ if(user.getFilter().getGrade() != Grade.SENIOR){ return internshipAnnouncement.isGraduating.isFalse();