Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -4,34 +4,123 @@

import lombok.Data;

/**
* 공통 APi 응답 DTO 클래스입니다.
*
* <p>REST API의 응답 형식을 표준화하기 위해 사용됩니다. 모든 응답은 성공 여부({@link #success}), 응답 데이터({@link #data}), 응답
* 메시지({@link #message}), 그리고 HTTP 상태 코드({@link #status})를 포함합니다.
*
* <p><b>사용 예시:</b>
*
* <pre>{@code
* // 성공 응답 생성
* ApiResponse<UserDto> response = ApiResponse.success(userDto);
*
* // 메시지를 포함한 성공 응답
* ApiResponse<UserDto> response = ApiResponse.success(userDto, "회원 조회 성공");
*
* // 오류 응답 생성
* ApiResponse<Void> errorResponse = ApiResponse.error("잘못된 요청입니다.", HttpStatus.BAD_REQUEST);
* }</pre>
*
* @param <T> 응답 데이터의 타입
* @author jys01012@gmail.com
* @since v0.0.1-alpha
* @see HttpStatus
* @see lombok.Data
*/
@Data
public class ApiResponse<T> {
/**
* 요청 처리 성공 여부.
*
* <p>true: 요청이 정상적으로 처리됨 false: 요청 처리 중 오류 발생
*/
private boolean success;

/**
* 실제 응답 데이터(payload).
*
* <p>요청이 성공적으로 처리되었을 경우 반환되는 데이터이며, 실패 시에는 {@code null}일 수 있습니다.
*/
private T data;

/**
* 응답 메세지.
*
* <p>성공 또는 오류 상황을 설명하는 메시지를 담습니다. 클라이언트에서 사용자에게 직접 표시할 수도 있습니다.
*/
private String message;

/**
* HTTP 상태 코드.
*
* <p>Spring의 {@link HttpStatus} 열거형을 사용합니다.
*/
private HttpStatus status; // HttpStatus로 변경

/** 기본 생성자입니다. 모든 필드가 기본값으로 초기화됩니다. */
public ApiResponse() {}

/**
* 모든 필드를 초기화하는 생성자.
*
* @param success 요청 성공 여부
* @param data 응답 데이터
* @param message 응답 메시지
* @param status HTTP 상태 코드
*/
public ApiResponse(boolean success, T data, String message, HttpStatus status) {
this.success = success;
this.data = data;
this.message = message;
this.status = status;
}

/**
* 성공 응답을 생성합니다. (기본 메시지: "OK", 상태: 200 OK)
*
* @param data 응답 데이터
* @param <T> 데이터 타입
* @return 성공 응답 객체
*/
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(true, data, "OK", HttpStatus.OK);
}

/**
* 성공 응답을 생성합니다. (상태: 200 OK)
*
* @param data 응답 데이터
* @param message 사용자 정의 메시지
* @param <T> 데이터 타입
* @return 성공 응답 객체
*/
public static <T> ApiResponse<T> success(T data, String message) {
return new ApiResponse<>(true, data, message, HttpStatus.OK);
}

/**
* 성공 응답을 생성합니다.
*
* @param data 응답 데이터
* @param message 사용자 정의 메시지
* @param status 사용자 정의 상태 코드
* @param <T> 데이터 타입
* @return 성공 응답 객체
*/
public static <T> ApiResponse<T> success(T data, String message, HttpStatus status) {
return new ApiResponse<>(true, data, message, status);
}

/**
* 오류 응답을 생성합니다.
*
* @param message 오류 메시지
* @param status HTTP 상태 코드
* @param <T> 데이터 타입
* @return 오류 응답 객체
*/
public static <T> ApiResponse<T> error(String message, HttpStatus status) {
return new ApiResponse<>(false, null, message, status);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,90 @@

import lombok.Data;

/**
* 페이징, 검색, 정렬, 필터링을 위한 공통 매개변수 클래스입니다.
*
* <p>목록 조회 API에서 공통적으로 사용되는 요청 파라미터를 정의합니다. 현재 페이지 번호({@link #current}), 페이지 크기({@link #pageSize}),
* 검색어({@link #search}), 정렬 조건({@link #sorters}), 필터 조건({@link #filters})를 포함합니다.
*
* <p><b>사용 예시:</b>
*
* <pre>{@code
* PageParams params = new PageParams();
* params.setCurrent(2);
* params.setPageSize(20);
* params.setSearch("회원");
*
* int offset = params.getOffset(); // 20
* boolean searchable = params.hasSearch(); // true
* }</pre>
*
* @author jys01012@gmail.com
* @since v0.0.1-alpha
* @see lombok.Data
*/
@Data
public class PageParams {
/**
* 현재 페이지 번호 (1부터 시작).
*
* <p>1부터 시작하며, 기본값은 1입니다. 0 이하의 값은 유효하지 않습니다.
*/
private int current = 1;

/**
* 한 펭지에 표시할 데이터 개수.
*
* <p>한 페이지에 표시할 항목의 개수를 지정합니다. 기본값은 10개이며, 일반적으로 10, 20, 50, 100 등의 값을 사용합니다.
*/
private int pageSize = 10;

/**
* 검색어.
*
* <p>목록에서 특정 조건으로 검색할 때 사용되는 키워드입니다. {@code null}이거나 빈 문자열인 경우 검색 조건이 적용되지 않습니다.
*/
private String search;

/**
* 정렬 조건 배열.
*
* <p>예: {@code ["name:asc", "createdAt:desc"]} API 설계에 따라 "필드명:정렬방향" 형식을 권장합니다. {@code null}이거나 빈
* 배열인 경우 기본 정렬이 적용됩니다.
*/
private String[] sorters;

/**
* 필터링 조건 배열.
*
* <p>예: {@code ["status:active", "role:admin"]} 각 요소는 특정 필드에 대한 필터링 조건을 나타냅니다. 형태는 구현에 따라 다를 수
* 있습니다.
*/
private String[] filters;

// 계산된 offset
/**
* 페이징 처리를 위한 offset(시작 위치)을 계산합니다.
*
* @return (current - 1) * pageSize
*/
public int getOffset() {
return (current - 1) * pageSize;
}

/**
* 검색어가 유효하게 존재하는지 확인합니다.
*
* @return 검색어가 null이 아니고 공백이 아닌 경우 true
*/
public boolean hasSearch() {
return search != null && !search.trim().isEmpty();
}

/**
* 정렬 조건이 존재하는지 확인합니다.
*
* @return 정렬 조건 배열이 null이 아니고, 1개 이상 있는 경우 true
*/
public boolean hasSorters() {
return sorters != null && sorters.length > 0;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,67 @@
import lombok.Data;
import lombok.NoArgsConstructor;

/**
* 페이징 처리된 결과 DTO 클래스.
*
* <p>목록 조회 API에서 페이징된 데이터를 반환할 때 사용됩니다. 실제 데이터 목록({@link #data}), 전체 개수({@link #total}), 현재 페이지
* 번호({@link #current}), 페이지 크기({@link #pageSize}), 전체 페이지 수({@link #totalPages}), 다음/이전 페이지 여부를
* 포함합니다.
*
* <p><b>사용 예시:</b>
*
* <pre>{@code
* PageParams params = new PageParams();
* params.setCurrent(2);
* params.setPageSize(10);
*
* // Repository나 Mapper에서 데이터를 가져와 PageResult 생성
* PageResult<UserDto> pageResult = PageResult.from(
* params,
* () -> userRepository.findUsers(params.getOffset(), params.getPageSize()),
* () -> userRepository.countUsers()
* );
*
* boolean hasNext = pageResult.isHasNext(); // true or false
* }</pre>
*
* @param <T> 데이터 타입
* @author jys01012@gmail.com
* @since v0.0.1-alpha
*/
@Data
@NoArgsConstructor
public class PageResult<T> {

/** 현재 페이지에 포함된 데이터 목록. */
private List<T> data;

/** 전체 데이터 개수. */
private int total;

/** 현재 페이지 번호 (1부터 시작). */
private int current;

/** 한 페이지에 포함되는 데이터 개수. */
private int pageSize;

/** 전체 페이지 수. */
private int totalPages;

/** 다음 페이지가 존재하는지 여부. */
private boolean hasNext;

/** 이전 페이지가 존재하는지 여부. */
private boolean hasPrevious;

/**
* 생성자.
*
* @param data 현재 페이지 데이터
* @param total 전체 데이터 개수
* @param current 현재 페이지 번호
* @param pageSize 페이지 크기
*/
public PageResult(List<T> data, int total, int current, int pageSize) {
this.data = data;
this.total = total;
Expand All @@ -25,52 +75,106 @@ public PageResult(List<T> data, int total, int current, int pageSize) {
calculatePagination();
}

// 페이징 계산 로직 분리
/**
* 페이징 관련 필드를 계산합니다.
*
* <p>totalPages, hasNext, hasPrevious 값을 설정합니다.
*/
private void calculatePagination() {
this.totalPages = total > 0 ? (int) Math.ceil((double) total / pageSize) : 0;
this.hasNext = current < totalPages;
this.hasPrevious = current > 1;
}

// 기존 of 메서드
/**
* PageResult 객체를 생성합니다.
*
* @param data 현재 페이지 데이터
* @param total 전체 데이터 개수
* @param current 현재 페이지 번호
* @param pageSize 페이지 크기
* @param <T> 데이터 타입
* @return PageResult 객체
*/
public static <T> PageResult<T> of(List<T> data, int total, int current, int pageSize) {
return new PageResult<>(data, total, current, pageSize);
}

// PageParams를 받는 of 메서드
/**
* PageParams를 기반으로 PageResult 객체를 생성합니다.
*
* @param data 현재 페이지 데이터
* @param total 전체 데이터 개수
* @param pageParams 요청 파라미터 ({@link PageParams})
* @param <T> 데이터 타입
* @return PageResult 객체
*/
public static <T> PageResult<T> of(List<T> data, int total, PageParams pageParams) {
return new PageResult<>(data, total, pageParams.getCurrent(), pageParams.getPageSize());
}

// 함수형 인터페이스를 활용한 from 메서드 (트랜잭션 내에서 실행)
/**
* 함수형 인터페이스를 활용해 PageResult를 생성합니다.
*
* <p>데이터 조회와 카운트 조회를 별도의 Supplier로 받아 트랜잭션 내에서 실행할 수 있습니다.
*
* @param pageParams 요청 파라미터 ({@link PageParams})
* @param dataSupplier 데이터 조회 함수
* @param countSupplier 전체 개수 조회 함수
* @param <T> 데이터 타입
* @return PageResult 객체
*/
public static <T> PageResult<T> from(
PageParams pageParams, Supplier<List<T>> dataSupplier, Supplier<Integer> countSupplier) {
List<T> data = dataSupplier.get();
int total = countSupplier.get();
return new PageResult<>(data, total, pageParams.getCurrent(), pageParams.getPageSize());
}

// 빈 페이지 결과 생성
/**
* 비어 있는 페이지 결과를 생성합니다.
*
* @param pageParams 요청 파라미터 ({@link PageParams})
* @param <T> 데이터 타입
* @return 빈 PageResult 객체
*/
public static <T> PageResult<T> empty(PageParams pageParams) {
return new PageResult<>(List.of(), 0, pageParams.getCurrent(), pageParams.getPageSize());
}

// 빈 페이지 결과 생성 (기본값)
/**
* 기본값(1페이지, 10개)으로 비어 있는 페이지 결과를 생성합니다.
*
* @param <T> 데이터 타입
* @return 빈 PageResult 객체
*/
public static <T> PageResult<T> empty() {
return new PageResult<>(List.of(), 0, 1, 10);
}

// 데이터가 있는지 확인
/**
* 현재 페이지에 데이터가 있는지 확인합니다.
*
* @return 데이터가 존재하면 true
*/
public boolean hasData() {
return data != null && !data.isEmpty();
}

// 첫 번째 페이지인지 확인
/**
* 현재 페이지가 첫 번째 페이지인지 확인합니다.
*
* @return 첫 번째 페이지면 true
*/
public boolean isFirstPage() {
return current == 1;
}

// 마지막 페이지인지 확인
/**
* 현재 페이지가 마지막 페이지인지 확인합니다.
*
* @return 마지막 페이지면 true
*/
public boolean isLastPage() {
return current == totalPages;
}
Expand Down
Loading
Loading