Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,14 @@
import com.example.solidconnection.admin.dto.ScoreSearchCondition;
import com.example.solidconnection.admin.service.AdminGpaScoreService;
import com.example.solidconnection.admin.service.AdminLanguageTestScoreService;
import com.example.solidconnection.custom.request.CustomPageRequest;
import com.example.solidconnection.custom.response.PageResponse;
import com.example.solidconnection.util.PagingUtils;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
Expand All @@ -35,15 +31,15 @@ public class AdminScoreController {
private final AdminGpaScoreService adminGpaScoreService;
private final AdminLanguageTestScoreService adminLanguageTestScoreService;

// todo: 추후 커스텀 페이지 객체 & argumentResolver를 적용 필요
@GetMapping("/gpas")
public ResponseEntity<PageResponse<GpaScoreSearchResponse>> searchGpaScores(
@Valid @ModelAttribute ScoreSearchCondition scoreSearchCondition,
@PageableDefault(page = 1) Pageable pageable
CustomPageRequest customPageRequest
) {
PagingUtils.validatePage(pageable.getPageNumber(), pageable.getPageSize());
Pageable internalPageable = PageRequest.of(pageable.getPageNumber() - 1, pageable.getPageSize());
Page<GpaScoreSearchResponse> page = adminGpaScoreService.searchGpaScores(scoreSearchCondition, internalPageable);
Page<GpaScoreSearchResponse> page = adminGpaScoreService.searchGpaScores(
scoreSearchCondition,
customPageRequest.toPageable()
);
return ResponseEntity.ok(PageResponse.of(page));
}

Expand All @@ -56,15 +52,15 @@ public ResponseEntity<GpaScoreResponse> updateGpaScore(
return ResponseEntity.ok(response);
}

// todo: 추후 커스텀 페이지 객체 & argumentResolver를 적용 필요
@GetMapping("/language-tests")
public ResponseEntity<PageResponse<LanguageTestScoreSearchResponse>> searchLanguageTestScores(
@Valid @ModelAttribute ScoreSearchCondition scoreSearchCondition,
@PageableDefault(page = 1) Pageable pageable
CustomPageRequest customPageRequest
) {
PagingUtils.validatePage(pageable.getPageNumber(), pageable.getPageSize());
Pageable internalPageable = PageRequest.of(pageable.getPageNumber() - 1, pageable.getPageSize());
Page<LanguageTestScoreSearchResponse> page = adminLanguageTestScoreService.searchLanguageTestScores(scoreSearchCondition, internalPageable);
Page<LanguageTestScoreSearchResponse> page = adminLanguageTestScoreService.searchLanguageTestScores(
scoreSearchCondition,
customPageRequest.toPageable()
);
return ResponseEntity.ok(PageResponse.of(page));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@


import com.example.solidconnection.custom.resolver.AuthorizedUserResolver;
import com.example.solidconnection.custom.resolver.CustomPageRequestArgumentResolver;
import com.example.solidconnection.custom.resolver.ExpiredTokenResolver;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
Expand All @@ -16,12 +17,14 @@ public class WebMvcConfig implements WebMvcConfigurer {

private final AuthorizedUserResolver authorizedUserResolver;
private final ExpiredTokenResolver expiredTokenResolver;
private final CustomPageRequestArgumentResolver customPageRequestArgumentResolver;

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.addAll(List.of(
authorizedUserResolver,
expiredTokenResolver
expiredTokenResolver,
customPageRequestArgumentResolver
));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
import org.springframework.http.HttpStatus;

import static com.example.solidconnection.application.service.ApplicationSubmissionService.APPLICATION_UPDATE_COUNT_LIMIT;
import static com.example.solidconnection.custom.request.CustomPageRequest.MAX_SIZE;
import static com.example.solidconnection.custom.request.CustomPageRequest.MIN_PAGE;
import static com.example.solidconnection.custom.request.CustomPageRequest.MIN_SIZE;
import static com.example.solidconnection.siteuser.service.SiteUserService.MIN_DAYS_BETWEEN_NICKNAME_CHANGES;

@Getter
Expand Down Expand Up @@ -97,8 +100,8 @@ public enum ErrorCode {
REJECTED_REASON_REQUIRED(HttpStatus.BAD_REQUEST.value(), "거절 사유가 필요합니다."),

// page
INVALID_PAGE(HttpStatus.BAD_REQUEST.value(), "페이지 번호는 1 이상 50 이하만 가능합니다."),
INVALID_SIZE(HttpStatus.BAD_REQUEST.value(), "페이지 크기는 1 이상 50 이하만 가능합니다."),
INVALID_PAGE(HttpStatus.BAD_REQUEST.value(), "페이지 번호는 " + MIN_PAGE + " 이상만 가능합니다."),
INVALID_SIZE(HttpStatus.BAD_REQUEST.value(), "페이지 크기는 " + MIN_SIZE + " 이상 " + MAX_SIZE + " 이하만 가능합니다."),

// general
JSON_PARSING_FAILED(HttpStatus.BAD_REQUEST.value(), "JSON 파싱을 할 수 없습니다."),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.example.solidconnection.custom.request;

import com.example.solidconnection.custom.exception.CustomException;
import lombok.Getter;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_PAGE;
import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_SIZE;

@Getter
public class CustomPageRequest {

public static final int MIN_PAGE = 1;
public static final int MIN_SIZE = 1;
public static final int MAX_SIZE = 50;

private final int page;
private final int size;
private Sort sort = Sort.unsorted();

public CustomPageRequest(int page, int size) {
validatePageParameters(page, size);
this.page = page;
this.size = size;
}

public CustomPageRequest(int page, int size, Sort sort) {
this(page, size);
this.sort = sort;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1/
일반적으로 생성자 체이닝(=this나 super를 사용하여 생성자 내부에서 다른 생성자를 호출하는 기법)에서는
모든 인자를 받는 '기본 생성자'가 기준이 됩니다.

지금처럼 CustomPageRequest(int page, int size, Sort sort)가
CustomPageRequest(int page, int size)를 호출하는게 아닌, CustomPageRequest(int page, int size)가
CustomPageRequest(int page, int size, Sort sort) 를 호출하도록 변경되면 좋을 것 같아요.

개발자들끼리 암묵적으로 정한 규칙들을 지키면, 다른 개발자들이 이해하기 더 쉬운 코드가 될것 같습니다!

ref. https://koonsland.tistory.com/270

2/
'기본 생성자'를 사용하도록 코드를 바꾸면,
CustomPageRequest(int page, int size) 생성자를 사용하는 곳은 없다는게 보일거예요.
안쓰는 생성자는 삭제하는게 좋겠습니다~

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 이해했습니다!


// 1-based -> 0-based 변환
public Pageable toPageable() {
return PageRequest.of(page - 1, size, sort);
}

private static void validatePageParameters(int page, int size) {
if (page < MIN_PAGE) {
throw new CustomException(INVALID_PAGE);
}
if (size < MIN_SIZE || size > MAX_SIZE) {
throw new CustomException(INVALID_SIZE);
}
}

public static CustomPageRequest of(int page, int size, Sort sort) {
return new CustomPageRequest(page, size, sort);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.example.solidconnection.custom.resolver;

import com.example.solidconnection.custom.request.CustomPageRequest;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.core.MethodParameter;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import java.util.ArrayList;
import java.util.List;

@Component
@RequiredArgsConstructor
public class CustomPageRequestArgumentResolver implements HandlerMethodArgumentResolver {

private static final String PAGE_PARAMETER = "page";
private static final String SIZE_PARAMETER = "size";
private static final String SORT_PARAMETER = "sort";
private static final String DESC = "desc";
private static final String COMMA = ",";
private static final int DEFAULT_PAGE = 1;
private static final int DEFAULT_SIZE = 10;

@Override
public boolean supportsParameter(MethodParameter parameter) {
return CustomPageRequest.class.isAssignableFrom(parameter.getParameterType());
}

@Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) {
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
int page = extractIntParameter(request, PAGE_PARAMETER, DEFAULT_PAGE);
int size = extractIntParameter(request, SIZE_PARAMETER, DEFAULT_SIZE);
Sort sort = extractSortParameter(request);
return CustomPageRequest.of(page, size, sort);
}

private int extractIntParameter(HttpServletRequest request, String paramName, int defaultValue) {
String paramValue = request.getParameter(paramName);
if (paramValue != null && !paramValue.isEmpty()) {
try {
return Integer.parseInt(paramValue);
} catch (NumberFormatException e) {
// 숫자로 변환할 수 없는 경우 기본값 사용
}
}
return defaultValue;
}

private Sort extractSortParameter(HttpServletRequest request) {
String[] sortParams = request.getParameterValues(SORT_PARAMETER);
if (sortParams == null || sortParams.length == 0) {
return Sort.unsorted();
}
List<Sort.Order> orders = new ArrayList<>();
for (String sortParam : sortParams) {
addSortOrder(sortParam, orders);
}
return orders.isEmpty() ? Sort.unsorted() : Sort.by(orders);
}

private void addSortOrder(String sortParam, List<Sort.Order> orders) {
String[] parts = sortParam.split(COMMA);
if (parts.length < 1) {
return;
}
String property = parts[0];
Sort.Direction direction = Sort.Direction.ASC;
if (parts.length >= 2 && DESC.equalsIgnoreCase(parts[1])) {
direction = Sort.Direction.DESC;
}
orders.add(new Sort.Order(direction, property));
}
}
26 changes: 0 additions & 26 deletions src/main/java/com/example/solidconnection/util/PagingUtils.java

This file was deleted.

Loading