From bf77f8cea8be24342736397a119b9c62cee7863a Mon Sep 17 00:00:00 2001 From: ajwoong Date: Fri, 20 Dec 2024 17:57:01 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20apiPayload=20=EC=84=B8=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + .../phrasebe/apiPayload/ApiResponse.java | 39 ++++++ .../phrasebe/apiPayload/code/BaseCode.java | 9 ++ .../apiPayload/code/BaseErrorCode.java | 9 ++ .../apiPayload/code/ErrorReasonDTO.java | 18 +++ .../phrasebe/apiPayload/code/ReasonDTO.java | 18 +++ .../apiPayload/code/status/ErrorStatus.java | 47 +++++++ .../apiPayload/code/status/SuccessStatus.java | 41 ++++++ .../apiPayload/exception/ExceptionAdvice.java | 119 ++++++++++++++++++ .../exception/GeneralException.java | 21 ++++ .../exception/handler/ExceptionHandler.java | 10 ++ 11 files changed, 332 insertions(+) create mode 100644 src/main/java/com/example/phrasebe/apiPayload/ApiResponse.java create mode 100644 src/main/java/com/example/phrasebe/apiPayload/code/BaseCode.java create mode 100644 src/main/java/com/example/phrasebe/apiPayload/code/BaseErrorCode.java create mode 100644 src/main/java/com/example/phrasebe/apiPayload/code/ErrorReasonDTO.java create mode 100644 src/main/java/com/example/phrasebe/apiPayload/code/ReasonDTO.java create mode 100644 src/main/java/com/example/phrasebe/apiPayload/code/status/ErrorStatus.java create mode 100644 src/main/java/com/example/phrasebe/apiPayload/code/status/SuccessStatus.java create mode 100644 src/main/java/com/example/phrasebe/apiPayload/exception/ExceptionAdvice.java create mode 100644 src/main/java/com/example/phrasebe/apiPayload/exception/GeneralException.java create mode 100644 src/main/java/com/example/phrasebe/apiPayload/exception/handler/ExceptionHandler.java diff --git a/build.gradle b/build.gradle index 8ba4755..a99fb9b 100644 --- a/build.gradle +++ b/build.gradle @@ -26,6 +26,7 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'jakarta.validation:jakarta.validation-api:3.0.2' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'com.mysql:mysql-connector-j' diff --git a/src/main/java/com/example/phrasebe/apiPayload/ApiResponse.java b/src/main/java/com/example/phrasebe/apiPayload/ApiResponse.java new file mode 100644 index 0000000..72c4105 --- /dev/null +++ b/src/main/java/com/example/phrasebe/apiPayload/ApiResponse.java @@ -0,0 +1,39 @@ +package com.example.phrasebe.apiPayload; + +import com.example.phrasebe.apiPayload.code.BaseCode; +import com.example.phrasebe.apiPayload.code.status.SuccessStatus; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@JsonPropertyOrder({"isSuccess", "code", "message", "result"}) +public class ApiResponse { + + @JsonProperty("isSuccess") + private final Boolean isSuccess; + private final String code; + private final String message; + @JsonInclude(JsonInclude.Include.NON_NULL) + private T result; + + + // 성공한 경우 응답 생성 + + public static ApiResponse onSuccess(T result){ + return new ApiResponse<>(true, SuccessStatus._OK.getCode() , SuccessStatus._OK.getMessage(), result); + } + + public static ApiResponse of(BaseCode code, T result){ + return new ApiResponse<>(true, code.getReasonHttpStatus().getCode() , code.getReasonHttpStatus().getMessage(), result); + } + + + // 실패한 경우 응답 생성 + public static ApiResponse onFailure(String code, String message, T data){ + return new ApiResponse<>(false, code, message, data); + } +} diff --git a/src/main/java/com/example/phrasebe/apiPayload/code/BaseCode.java b/src/main/java/com/example/phrasebe/apiPayload/code/BaseCode.java new file mode 100644 index 0000000..867d808 --- /dev/null +++ b/src/main/java/com/example/phrasebe/apiPayload/code/BaseCode.java @@ -0,0 +1,9 @@ +package com.example.phrasebe.apiPayload.code; + +public interface BaseCode { + + ReasonDTO getReason(); + + ReasonDTO getReasonHttpStatus(); + +} \ No newline at end of file diff --git a/src/main/java/com/example/phrasebe/apiPayload/code/BaseErrorCode.java b/src/main/java/com/example/phrasebe/apiPayload/code/BaseErrorCode.java new file mode 100644 index 0000000..170afdf --- /dev/null +++ b/src/main/java/com/example/phrasebe/apiPayload/code/BaseErrorCode.java @@ -0,0 +1,9 @@ +package com.example.phrasebe.apiPayload.code; + +public interface BaseErrorCode { + + ErrorReasonDTO getReason(); + + ErrorReasonDTO getReasonHttpStatus(); + +} diff --git a/src/main/java/com/example/phrasebe/apiPayload/code/ErrorReasonDTO.java b/src/main/java/com/example/phrasebe/apiPayload/code/ErrorReasonDTO.java new file mode 100644 index 0000000..137b1d7 --- /dev/null +++ b/src/main/java/com/example/phrasebe/apiPayload/code/ErrorReasonDTO.java @@ -0,0 +1,18 @@ +package com.example.phrasebe.apiPayload.code; + +import lombok.Builder; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@Builder +public class ErrorReasonDTO { + + private HttpStatus httpStatus; + + private final boolean isSuccess; + private final String code; + private final String message; + + public boolean getIsSuccess(){return isSuccess;} +} diff --git a/src/main/java/com/example/phrasebe/apiPayload/code/ReasonDTO.java b/src/main/java/com/example/phrasebe/apiPayload/code/ReasonDTO.java new file mode 100644 index 0000000..c851f3a --- /dev/null +++ b/src/main/java/com/example/phrasebe/apiPayload/code/ReasonDTO.java @@ -0,0 +1,18 @@ +package com.example.phrasebe.apiPayload.code; + +import lombok.Builder; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@Builder +public class ReasonDTO { + + private HttpStatus httpStatus; + + private final boolean isSuccess; + private final String code; + private final String message; + + public boolean getIsSuccess(){return isSuccess;} +} diff --git a/src/main/java/com/example/phrasebe/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/example/phrasebe/apiPayload/code/status/ErrorStatus.java new file mode 100644 index 0000000..71b37f4 --- /dev/null +++ b/src/main/java/com/example/phrasebe/apiPayload/code/status/ErrorStatus.java @@ -0,0 +1,47 @@ +package com.example.phrasebe.apiPayload.code.status; + +import com.example.phrasebe.apiPayload.code.BaseErrorCode; +import com.example.phrasebe.apiPayload.code.ErrorReasonDTO; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum ErrorStatus implements BaseErrorCode { + + // 가장 일반적인 응답 + _INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "COMMON500", "서버 에러, 관리자에게 문의 바랍니다."), + _BAD_REQUEST(HttpStatus.BAD_REQUEST,"COMMON400","잘못된 요청입니다."), + _UNAUTHORIZED(HttpStatus.UNAUTHORIZED,"COMMON401","인증이 필요합니다."), + _FORBIDDEN(HttpStatus.FORBIDDEN, "COMMON403", "금지된 요청입니다."), + + + // 멤버 관려 에러 + MEMBER_NOT_FOUND(HttpStatus.BAD_REQUEST, "MEMBER4001", "사용자가 없습니다."), + NICKNAME_NOT_EXIST(HttpStatus.BAD_REQUEST, "MEMBER4002", "닉네임은 필수 입니다."); + + private final HttpStatus httpStatus; + private final String code; + private final String message; + + @Override + public ErrorReasonDTO getReason() { + return ErrorReasonDTO.builder() + .message(message) + .code(code) + .isSuccess(false) + .build(); + } + + @Override + public ErrorReasonDTO getReasonHttpStatus() { + return ErrorReasonDTO.builder() + .message(message) + .code(code) + .isSuccess(false) + .httpStatus(httpStatus) + .build() + ; + } +} diff --git a/src/main/java/com/example/phrasebe/apiPayload/code/status/SuccessStatus.java b/src/main/java/com/example/phrasebe/apiPayload/code/status/SuccessStatus.java new file mode 100644 index 0000000..44cb5b7 --- /dev/null +++ b/src/main/java/com/example/phrasebe/apiPayload/code/status/SuccessStatus.java @@ -0,0 +1,41 @@ +package com.example.phrasebe.apiPayload.code.status; + +import com.example.phrasebe.apiPayload.code.BaseCode; +import com.example.phrasebe.apiPayload.code.ReasonDTO; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum SuccessStatus implements BaseCode { + + // 일반적인 응답 + _OK(HttpStatus.OK, "COMMON200", "성공입니다."); + + private final HttpStatus httpStatus; + private final String code; + private final String message; + + + @Override + public ReasonDTO getReason() { + return ReasonDTO.builder() + .message(message) + .code(code) + .isSuccess(true) + .build(); + } + + @Override + public ReasonDTO getReasonHttpStatus() { + return ReasonDTO.builder() + .message(message) + .code(code) + .isSuccess(true) + .httpStatus(httpStatus) + .build() + ; + + } +} diff --git a/src/main/java/com/example/phrasebe/apiPayload/exception/ExceptionAdvice.java b/src/main/java/com/example/phrasebe/apiPayload/exception/ExceptionAdvice.java new file mode 100644 index 0000000..2e1d1ea --- /dev/null +++ b/src/main/java/com/example/phrasebe/apiPayload/exception/ExceptionAdvice.java @@ -0,0 +1,119 @@ +package com.example.phrasebe.apiPayload.exception; + +import com.example.phrasebe.apiPayload.ApiResponse; +import com.example.phrasebe.apiPayload.code.ErrorReasonDTO; +import com.example.phrasebe.apiPayload.code.status.ErrorStatus; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import jakarta.validation.ConstraintViolationException; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.context.request.ServletWebRequest; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; + +@Slf4j +@RestControllerAdvice(annotations = {RestController.class}) +public class ExceptionAdvice extends ResponseEntityExceptionHandler { + + + @ExceptionHandler + public ResponseEntity validation(ConstraintViolationException e, WebRequest request) { + String errorMessage = e.getConstraintViolations().stream() + .map(constraintViolation -> constraintViolation.getMessage()) + .findFirst() + .orElseThrow(() -> new RuntimeException("ConstraintViolationException 추출 도중 에러 발생")); + + return handleExceptionInternalConstraint(e, ErrorStatus.valueOf(errorMessage), HttpHeaders.EMPTY,request); + } + + @Override + public ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException e, HttpHeaders headers, HttpStatusCode status, WebRequest request) { + + Map errors = new LinkedHashMap<>(); + + e.getBindingResult().getFieldErrors().stream() + .forEach(fieldError -> { + String fieldName = fieldError.getField(); + String errorMessage = Optional.ofNullable(fieldError.getDefaultMessage()).orElse(""); + errors.merge(fieldName, errorMessage, (existingErrorMessage, newErrorMessage) -> existingErrorMessage + ", " + newErrorMessage); + }); + + return handleExceptionInternalArgs(e,HttpHeaders.EMPTY, ErrorStatus.valueOf("_BAD_REQUEST"),request,errors); + } + + @ExceptionHandler + public ResponseEntity exception(Exception e, WebRequest request) { + e.printStackTrace(); + + return handleExceptionInternalFalse(e, ErrorStatus._INTERNAL_SERVER_ERROR, HttpHeaders.EMPTY, ErrorStatus._INTERNAL_SERVER_ERROR.getHttpStatus(),request, e.getMessage()); + } + + @ExceptionHandler(value = GeneralException.class) + public ResponseEntity onThrowException(GeneralException generalException, HttpServletRequest request) { + ErrorReasonDTO errorReasonHttpStatus = generalException.getErrorReasonHttpStatus(); + return handleExceptionInternal(generalException,errorReasonHttpStatus,null,request); + } + + private ResponseEntity handleExceptionInternal(Exception e, ErrorReasonDTO reason, + HttpHeaders headers, HttpServletRequest request) { + + ApiResponse body = ApiResponse.onFailure(reason.getCode(),reason.getMessage(),null); +// e.printStackTrace(); + + WebRequest webRequest = new ServletWebRequest(request); + return super.handleExceptionInternal( + e, + body, + headers, + reason.getHttpStatus(), + webRequest + ); + } + + private ResponseEntity handleExceptionInternalFalse(Exception e, ErrorStatus errorCommonStatus, + HttpHeaders headers, HttpStatus status, WebRequest request, String errorPoint) { + ApiResponse body = ApiResponse.onFailure(errorCommonStatus.getCode(),errorCommonStatus.getMessage(),errorPoint); + return super.handleExceptionInternal( + e, + body, + headers, + status, + request + ); + } + + private ResponseEntity handleExceptionInternalArgs(Exception e, HttpHeaders headers, ErrorStatus errorCommonStatus, + WebRequest request, Map errorArgs) { + ApiResponse body = ApiResponse.onFailure(errorCommonStatus.getCode(),errorCommonStatus.getMessage(),errorArgs); + return super.handleExceptionInternal( + e, + body, + headers, + errorCommonStatus.getHttpStatus(), + request + ); + } + + private ResponseEntity handleExceptionInternalConstraint(Exception e, ErrorStatus errorCommonStatus, + HttpHeaders headers, WebRequest request) { + ApiResponse body = ApiResponse.onFailure(errorCommonStatus.getCode(), errorCommonStatus.getMessage(), null); + return super.handleExceptionInternal( + e, + body, + headers, + errorCommonStatus.getHttpStatus(), + request + ); + } +} diff --git a/src/main/java/com/example/phrasebe/apiPayload/exception/GeneralException.java b/src/main/java/com/example/phrasebe/apiPayload/exception/GeneralException.java new file mode 100644 index 0000000..5ace4ac --- /dev/null +++ b/src/main/java/com/example/phrasebe/apiPayload/exception/GeneralException.java @@ -0,0 +1,21 @@ +package com.example.phrasebe.apiPayload.exception; + +import com.example.phrasebe.apiPayload.code.BaseErrorCode; +import com.example.phrasebe.apiPayload.code.ErrorReasonDTO; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class GeneralException extends RuntimeException{ + + private BaseErrorCode code; + + public ErrorReasonDTO getErrorReason() { + return this.code.getReason(); + } + + public ErrorReasonDTO getErrorReasonHttpStatus(){ + return this.code.getReasonHttpStatus(); + } +} diff --git a/src/main/java/com/example/phrasebe/apiPayload/exception/handler/ExceptionHandler.java b/src/main/java/com/example/phrasebe/apiPayload/exception/handler/ExceptionHandler.java new file mode 100644 index 0000000..85489c5 --- /dev/null +++ b/src/main/java/com/example/phrasebe/apiPayload/exception/handler/ExceptionHandler.java @@ -0,0 +1,10 @@ +package com.example.phrasebe.apiPayload.exception.handler; + +import com.example.phrasebe.apiPayload.code.BaseErrorCode; +import com.example.phrasebe.apiPayload.exception.GeneralException; + +public class ExceptionHandler extends GeneralException { + public ExceptionHandler(BaseErrorCode code) { + super(code); + } +} From 3768f6f28ddae921ce9c1ea0e399a891c71028bb Mon Sep 17 00:00:00 2001 From: OSSU Date: Mon, 6 Jan 2025 04:19:54 +0900 Subject: [PATCH 2/5] =?UTF-8?q?:sparkles:=20feat:=20=EA=B8=B0=EB=B3=B8=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=ED=98=95=ED=83=9C=20=ED=86=B5=EC=9D=BC,?= =?UTF-8?q?=20=EC=98=88=EC=99=B8=20=ED=95=B8=EB=93=A4=EB=9F=AC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../phrasebe/apiPayload/ApiResponse.java | 39 ------ .../phrasebe/apiPayload/code/BaseCode.java | 9 -- .../apiPayload/code/BaseErrorCode.java | 9 -- .../apiPayload/code/ErrorReasonDTO.java | 18 --- .../phrasebe/apiPayload/code/ReasonDTO.java | 18 --- .../apiPayload/code/status/SuccessStatus.java | 41 ------ .../apiPayload/exception/ExceptionAdvice.java | 119 ------------------ .../exception/GeneralException.java | 21 ---- .../exception/handler/ExceptionHandler.java | 10 -- .../common/exception/ExceptionAdvice.java | 80 ++++++++++++ .../common/exception/GeneralException.java | 32 +++++ .../phrasebe/common/response/ApiResponse.java | 73 +++++++++++ .../phrasebe/common/response/PageInfo.java | 12 ++ .../code => common}/status/ErrorStatus.java | 35 ++---- .../phrasebe/common/status/SuccessStatus.java | 17 +++ 15 files changed, 227 insertions(+), 306 deletions(-) delete mode 100644 src/main/java/com/example/phrasebe/apiPayload/ApiResponse.java delete mode 100644 src/main/java/com/example/phrasebe/apiPayload/code/BaseCode.java delete mode 100644 src/main/java/com/example/phrasebe/apiPayload/code/BaseErrorCode.java delete mode 100644 src/main/java/com/example/phrasebe/apiPayload/code/ErrorReasonDTO.java delete mode 100644 src/main/java/com/example/phrasebe/apiPayload/code/ReasonDTO.java delete mode 100644 src/main/java/com/example/phrasebe/apiPayload/code/status/SuccessStatus.java delete mode 100644 src/main/java/com/example/phrasebe/apiPayload/exception/ExceptionAdvice.java delete mode 100644 src/main/java/com/example/phrasebe/apiPayload/exception/GeneralException.java delete mode 100644 src/main/java/com/example/phrasebe/apiPayload/exception/handler/ExceptionHandler.java create mode 100644 src/main/java/com/example/phrasebe/common/exception/ExceptionAdvice.java create mode 100644 src/main/java/com/example/phrasebe/common/exception/GeneralException.java create mode 100644 src/main/java/com/example/phrasebe/common/response/ApiResponse.java create mode 100644 src/main/java/com/example/phrasebe/common/response/PageInfo.java rename src/main/java/com/example/phrasebe/{apiPayload/code => common}/status/ErrorStatus.java (54%) create mode 100644 src/main/java/com/example/phrasebe/common/status/SuccessStatus.java diff --git a/src/main/java/com/example/phrasebe/apiPayload/ApiResponse.java b/src/main/java/com/example/phrasebe/apiPayload/ApiResponse.java deleted file mode 100644 index 72c4105..0000000 --- a/src/main/java/com/example/phrasebe/apiPayload/ApiResponse.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.example.phrasebe.apiPayload; - -import com.example.phrasebe.apiPayload.code.BaseCode; -import com.example.phrasebe.apiPayload.code.status.SuccessStatus; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import lombok.AllArgsConstructor; -import lombok.Getter; - -@Getter -@AllArgsConstructor -@JsonPropertyOrder({"isSuccess", "code", "message", "result"}) -public class ApiResponse { - - @JsonProperty("isSuccess") - private final Boolean isSuccess; - private final String code; - private final String message; - @JsonInclude(JsonInclude.Include.NON_NULL) - private T result; - - - // 성공한 경우 응답 생성 - - public static ApiResponse onSuccess(T result){ - return new ApiResponse<>(true, SuccessStatus._OK.getCode() , SuccessStatus._OK.getMessage(), result); - } - - public static ApiResponse of(BaseCode code, T result){ - return new ApiResponse<>(true, code.getReasonHttpStatus().getCode() , code.getReasonHttpStatus().getMessage(), result); - } - - - // 실패한 경우 응답 생성 - public static ApiResponse onFailure(String code, String message, T data){ - return new ApiResponse<>(false, code, message, data); - } -} diff --git a/src/main/java/com/example/phrasebe/apiPayload/code/BaseCode.java b/src/main/java/com/example/phrasebe/apiPayload/code/BaseCode.java deleted file mode 100644 index 867d808..0000000 --- a/src/main/java/com/example/phrasebe/apiPayload/code/BaseCode.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.example.phrasebe.apiPayload.code; - -public interface BaseCode { - - ReasonDTO getReason(); - - ReasonDTO getReasonHttpStatus(); - -} \ No newline at end of file diff --git a/src/main/java/com/example/phrasebe/apiPayload/code/BaseErrorCode.java b/src/main/java/com/example/phrasebe/apiPayload/code/BaseErrorCode.java deleted file mode 100644 index 170afdf..0000000 --- a/src/main/java/com/example/phrasebe/apiPayload/code/BaseErrorCode.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.example.phrasebe.apiPayload.code; - -public interface BaseErrorCode { - - ErrorReasonDTO getReason(); - - ErrorReasonDTO getReasonHttpStatus(); - -} diff --git a/src/main/java/com/example/phrasebe/apiPayload/code/ErrorReasonDTO.java b/src/main/java/com/example/phrasebe/apiPayload/code/ErrorReasonDTO.java deleted file mode 100644 index 137b1d7..0000000 --- a/src/main/java/com/example/phrasebe/apiPayload/code/ErrorReasonDTO.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.example.phrasebe.apiPayload.code; - -import lombok.Builder; -import lombok.Getter; -import org.springframework.http.HttpStatus; - -@Getter -@Builder -public class ErrorReasonDTO { - - private HttpStatus httpStatus; - - private final boolean isSuccess; - private final String code; - private final String message; - - public boolean getIsSuccess(){return isSuccess;} -} diff --git a/src/main/java/com/example/phrasebe/apiPayload/code/ReasonDTO.java b/src/main/java/com/example/phrasebe/apiPayload/code/ReasonDTO.java deleted file mode 100644 index c851f3a..0000000 --- a/src/main/java/com/example/phrasebe/apiPayload/code/ReasonDTO.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.example.phrasebe.apiPayload.code; - -import lombok.Builder; -import lombok.Getter; -import org.springframework.http.HttpStatus; - -@Getter -@Builder -public class ReasonDTO { - - private HttpStatus httpStatus; - - private final boolean isSuccess; - private final String code; - private final String message; - - public boolean getIsSuccess(){return isSuccess;} -} diff --git a/src/main/java/com/example/phrasebe/apiPayload/code/status/SuccessStatus.java b/src/main/java/com/example/phrasebe/apiPayload/code/status/SuccessStatus.java deleted file mode 100644 index 44cb5b7..0000000 --- a/src/main/java/com/example/phrasebe/apiPayload/code/status/SuccessStatus.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.example.phrasebe.apiPayload.code.status; - -import com.example.phrasebe.apiPayload.code.BaseCode; -import com.example.phrasebe.apiPayload.code.ReasonDTO; -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.springframework.http.HttpStatus; - -@Getter -@AllArgsConstructor -public enum SuccessStatus implements BaseCode { - - // 일반적인 응답 - _OK(HttpStatus.OK, "COMMON200", "성공입니다."); - - private final HttpStatus httpStatus; - private final String code; - private final String message; - - - @Override - public ReasonDTO getReason() { - return ReasonDTO.builder() - .message(message) - .code(code) - .isSuccess(true) - .build(); - } - - @Override - public ReasonDTO getReasonHttpStatus() { - return ReasonDTO.builder() - .message(message) - .code(code) - .isSuccess(true) - .httpStatus(httpStatus) - .build() - ; - - } -} diff --git a/src/main/java/com/example/phrasebe/apiPayload/exception/ExceptionAdvice.java b/src/main/java/com/example/phrasebe/apiPayload/exception/ExceptionAdvice.java deleted file mode 100644 index 2e1d1ea..0000000 --- a/src/main/java/com/example/phrasebe/apiPayload/exception/ExceptionAdvice.java +++ /dev/null @@ -1,119 +0,0 @@ -package com.example.phrasebe.apiPayload.exception; - -import com.example.phrasebe.apiPayload.ApiResponse; -import com.example.phrasebe.apiPayload.code.ErrorReasonDTO; -import com.example.phrasebe.apiPayload.code.status.ErrorStatus; -import jakarta.servlet.http.HttpServletRequest; -import lombok.extern.slf4j.Slf4j; -import jakarta.validation.ConstraintViolationException; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.HttpStatusCode; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.bind.annotation.RestControllerAdvice; -import org.springframework.web.context.request.ServletWebRequest; -import org.springframework.web.context.request.WebRequest; -import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; - -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Optional; - -@Slf4j -@RestControllerAdvice(annotations = {RestController.class}) -public class ExceptionAdvice extends ResponseEntityExceptionHandler { - - - @ExceptionHandler - public ResponseEntity validation(ConstraintViolationException e, WebRequest request) { - String errorMessage = e.getConstraintViolations().stream() - .map(constraintViolation -> constraintViolation.getMessage()) - .findFirst() - .orElseThrow(() -> new RuntimeException("ConstraintViolationException 추출 도중 에러 발생")); - - return handleExceptionInternalConstraint(e, ErrorStatus.valueOf(errorMessage), HttpHeaders.EMPTY,request); - } - - @Override - public ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException e, HttpHeaders headers, HttpStatusCode status, WebRequest request) { - - Map errors = new LinkedHashMap<>(); - - e.getBindingResult().getFieldErrors().stream() - .forEach(fieldError -> { - String fieldName = fieldError.getField(); - String errorMessage = Optional.ofNullable(fieldError.getDefaultMessage()).orElse(""); - errors.merge(fieldName, errorMessage, (existingErrorMessage, newErrorMessage) -> existingErrorMessage + ", " + newErrorMessage); - }); - - return handleExceptionInternalArgs(e,HttpHeaders.EMPTY, ErrorStatus.valueOf("_BAD_REQUEST"),request,errors); - } - - @ExceptionHandler - public ResponseEntity exception(Exception e, WebRequest request) { - e.printStackTrace(); - - return handleExceptionInternalFalse(e, ErrorStatus._INTERNAL_SERVER_ERROR, HttpHeaders.EMPTY, ErrorStatus._INTERNAL_SERVER_ERROR.getHttpStatus(),request, e.getMessage()); - } - - @ExceptionHandler(value = GeneralException.class) - public ResponseEntity onThrowException(GeneralException generalException, HttpServletRequest request) { - ErrorReasonDTO errorReasonHttpStatus = generalException.getErrorReasonHttpStatus(); - return handleExceptionInternal(generalException,errorReasonHttpStatus,null,request); - } - - private ResponseEntity handleExceptionInternal(Exception e, ErrorReasonDTO reason, - HttpHeaders headers, HttpServletRequest request) { - - ApiResponse body = ApiResponse.onFailure(reason.getCode(),reason.getMessage(),null); -// e.printStackTrace(); - - WebRequest webRequest = new ServletWebRequest(request); - return super.handleExceptionInternal( - e, - body, - headers, - reason.getHttpStatus(), - webRequest - ); - } - - private ResponseEntity handleExceptionInternalFalse(Exception e, ErrorStatus errorCommonStatus, - HttpHeaders headers, HttpStatus status, WebRequest request, String errorPoint) { - ApiResponse body = ApiResponse.onFailure(errorCommonStatus.getCode(),errorCommonStatus.getMessage(),errorPoint); - return super.handleExceptionInternal( - e, - body, - headers, - status, - request - ); - } - - private ResponseEntity handleExceptionInternalArgs(Exception e, HttpHeaders headers, ErrorStatus errorCommonStatus, - WebRequest request, Map errorArgs) { - ApiResponse body = ApiResponse.onFailure(errorCommonStatus.getCode(),errorCommonStatus.getMessage(),errorArgs); - return super.handleExceptionInternal( - e, - body, - headers, - errorCommonStatus.getHttpStatus(), - request - ); - } - - private ResponseEntity handleExceptionInternalConstraint(Exception e, ErrorStatus errorCommonStatus, - HttpHeaders headers, WebRequest request) { - ApiResponse body = ApiResponse.onFailure(errorCommonStatus.getCode(), errorCommonStatus.getMessage(), null); - return super.handleExceptionInternal( - e, - body, - headers, - errorCommonStatus.getHttpStatus(), - request - ); - } -} diff --git a/src/main/java/com/example/phrasebe/apiPayload/exception/GeneralException.java b/src/main/java/com/example/phrasebe/apiPayload/exception/GeneralException.java deleted file mode 100644 index 5ace4ac..0000000 --- a/src/main/java/com/example/phrasebe/apiPayload/exception/GeneralException.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.example.phrasebe.apiPayload.exception; - -import com.example.phrasebe.apiPayload.code.BaseErrorCode; -import com.example.phrasebe.apiPayload.code.ErrorReasonDTO; -import lombok.AllArgsConstructor; -import lombok.Getter; - -@Getter -@AllArgsConstructor -public class GeneralException extends RuntimeException{ - - private BaseErrorCode code; - - public ErrorReasonDTO getErrorReason() { - return this.code.getReason(); - } - - public ErrorReasonDTO getErrorReasonHttpStatus(){ - return this.code.getReasonHttpStatus(); - } -} diff --git a/src/main/java/com/example/phrasebe/apiPayload/exception/handler/ExceptionHandler.java b/src/main/java/com/example/phrasebe/apiPayload/exception/handler/ExceptionHandler.java deleted file mode 100644 index 85489c5..0000000 --- a/src/main/java/com/example/phrasebe/apiPayload/exception/handler/ExceptionHandler.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.example.phrasebe.apiPayload.exception.handler; - -import com.example.phrasebe.apiPayload.code.BaseErrorCode; -import com.example.phrasebe.apiPayload.exception.GeneralException; - -public class ExceptionHandler extends GeneralException { - public ExceptionHandler(BaseErrorCode code) { - super(code); - } -} diff --git a/src/main/java/com/example/phrasebe/common/exception/ExceptionAdvice.java b/src/main/java/com/example/phrasebe/common/exception/ExceptionAdvice.java new file mode 100644 index 0000000..4f89021 --- /dev/null +++ b/src/main/java/com/example/phrasebe/common/exception/ExceptionAdvice.java @@ -0,0 +1,80 @@ +package com.example.phrasebe.common.exception; + +import com.example.phrasebe.common.response.ApiResponse; +import com.example.phrasebe.common.status.ErrorStatus; +import jakarta.validation.ConstraintViolationException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingServletRequestParameterException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; +import org.springframework.web.servlet.resource.NoResourceFoundException; + +@Slf4j +@RestControllerAdvice +public class ExceptionAdvice extends ResponseEntityExceptionHandler { + @ExceptionHandler + public ResponseEntity validation(ConstraintViolationException e, WebRequest request) { + String errorMessage = e.getConstraintViolations().stream() + .map(constraintViolation -> constraintViolation.getMessage()) + .findFirst() + .orElseThrow(() -> new RuntimeException("ConstraintViolationException 추출 도중 에러 발생")); + + return handleExceptionInternalConstraint(e, ErrorStatus.valueOf(errorMessage), HttpHeaders.EMPTY, request); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException e) { + e.printStackTrace(); + + String errorMessage = e.getBindingResult().getFieldError().getDefaultMessage(); + return ApiResponse.onFailure(ErrorStatus.VALIDATION_ERROR, errorMessage); + } + + @ExceptionHandler(MissingServletRequestParameterException.class) + public ResponseEntity handleMissingServletRequestParameterException( + MissingServletRequestParameterException e + ) { + e.printStackTrace(); + + String errorMessage = e.getParameterType() + " 타입의 " + e.getParameterName() + " 파라미터가 없습니다."; + return ApiResponse.onFailure(ErrorStatus.VALIDATION_ERROR, errorMessage); + } + + @ExceptionHandler(NoResourceFoundException.class) + public ResponseEntity handleNoResourceFoundException(NoResourceFoundException e) { + e.printStackTrace(); + + return ApiResponse.onFailure(ErrorStatus._NOT_FOUND); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleException(Exception e) { + e.printStackTrace(); + + return ApiResponse.onFailure(ErrorStatus._INTERNAL_SERVER_ERROR); + } + + @ExceptionHandler(GeneralException.class) + public ResponseEntity handleGeneralException(GeneralException e) { + e.printStackTrace(); + + return ApiResponse.onFailure(e.getErrorStatus(), e.getMessage()); + } + + private ResponseEntity handleExceptionInternalConstraint(Exception e, ErrorStatus errorCommonStatus, + HttpHeaders headers, WebRequest request) { + ResponseEntity body = ApiResponse.onFailure(errorCommonStatus); + return super.handleExceptionInternal( + e, + body, + headers, + errorCommonStatus.getHttpStatus(), + request + ); + } +} diff --git a/src/main/java/com/example/phrasebe/common/exception/GeneralException.java b/src/main/java/com/example/phrasebe/common/exception/GeneralException.java new file mode 100644 index 0000000..12510a9 --- /dev/null +++ b/src/main/java/com/example/phrasebe/common/exception/GeneralException.java @@ -0,0 +1,32 @@ +package com.example.phrasebe.common.exception; + +import com.example.phrasebe.common.status.ErrorStatus; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class GeneralException extends RuntimeException{ + + private ErrorStatus errorStatus; + + public GeneralException() { + super(ErrorStatus._INTERNAL_SERVER_ERROR.getMessage()); + this.errorStatus = ErrorStatus._INTERNAL_SERVER_ERROR; + } + + public GeneralException(String message) { + super(message); + this.errorStatus = ErrorStatus._INTERNAL_SERVER_ERROR; + } + + public GeneralException(String message, Throwable cause) { + super(message, cause); + this.errorStatus = ErrorStatus._INTERNAL_SERVER_ERROR; + } + + public GeneralException(Throwable cause) { + super(cause.getMessage(), cause); + this.errorStatus = ErrorStatus._INTERNAL_SERVER_ERROR; + } +} diff --git a/src/main/java/com/example/phrasebe/common/response/ApiResponse.java b/src/main/java/com/example/phrasebe/common/response/ApiResponse.java new file mode 100644 index 0000000..0959712 --- /dev/null +++ b/src/main/java/com/example/phrasebe/common/response/ApiResponse.java @@ -0,0 +1,73 @@ +package com.example.phrasebe.common.response; + +import com.example.phrasebe.common.status.ErrorStatus; +import com.example.phrasebe.common.status.SuccessStatus; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Slice; +import org.springframework.http.ResponseEntity; + +@Getter +@RequiredArgsConstructor +@JsonPropertyOrder({"isSuccess", "code", "message", "result"}) +public class ApiResponse { + + private final Boolean isSuccess; + private final String code; + private final String message; + + @JsonInclude(Include.NON_NULL) + private final PageInfo pageInfo; + @JsonInclude(Include.NON_NULL) + private final Object result; + + + // 성공한 경우 응답 생성 + public static ResponseEntity onSuccess(SuccessStatus status, PageInfo pageInfo, Object result) { + return new ResponseEntity<>( + new ApiResponse(true, status.getCode(), status.getMessage(), pageInfo, result), + status.getHttpStatus() + ); + } + + // 성공 - 기본 응답 + public static ResponseEntity onSuccess(SuccessStatus status) { + return onSuccess(status, null, null); + } + + // 성공 - 데이터가 포함된 응답 + public static ResponseEntity onSuccess(SuccessStatus status, Object result) { + return onSuccess(status, null, result); + } + + // 성공 - 페이지네이션에 대한 응답 + public static ResponseEntity onSuccess(SuccessStatus status, Page page) { + PageInfo pageInfo = new PageInfo(page.getNumber(), page.getSize(), page.hasNext()); + return onSuccess(status, pageInfo, page.getContent()); + } + + public static ResponseEntity onSuccess(SuccessStatus status, Slice page) { + PageInfo pageInfo = new PageInfo(page.getNumber(), page.getSize(), page.hasNext()); + return onSuccess(status, pageInfo, page.getContent()); + } + + + // 실패한 경우 응답 생성 + public static ResponseEntity onFailure(ErrorStatus error) { + return new ResponseEntity<>( + new ApiResponse(false, error.getCode(), error.getMessage(), null, null), + error.getHttpStatus() + ); + } + + public static ResponseEntity onFailure(ErrorStatus error, String message) { + return new ResponseEntity<>( + new ApiResponse(false, error.getCode(), error.getMessage(message), null, null), + error.getHttpStatus() + ); + } +} diff --git a/src/main/java/com/example/phrasebe/common/response/PageInfo.java b/src/main/java/com/example/phrasebe/common/response/PageInfo.java new file mode 100644 index 0000000..50b6205 --- /dev/null +++ b/src/main/java/com/example/phrasebe/common/response/PageInfo.java @@ -0,0 +1,12 @@ +package com.example.phrasebe.common.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class PageInfo { + private Integer page; + private Integer size; + private Boolean hasNext; +} diff --git a/src/main/java/com/example/phrasebe/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/example/phrasebe/common/status/ErrorStatus.java similarity index 54% rename from src/main/java/com/example/phrasebe/apiPayload/code/status/ErrorStatus.java rename to src/main/java/com/example/phrasebe/common/status/ErrorStatus.java index 71b37f4..d3c36b6 100644 --- a/src/main/java/com/example/phrasebe/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/example/phrasebe/common/status/ErrorStatus.java @@ -1,20 +1,25 @@ -package com.example.phrasebe.apiPayload.code.status; +package com.example.phrasebe.common.status; -import com.example.phrasebe.apiPayload.code.BaseErrorCode; -import com.example.phrasebe.apiPayload.code.ErrorReasonDTO; import lombok.AllArgsConstructor; import lombok.Getter; import org.springframework.http.HttpStatus; +import java.util.Optional; +import java.util.function.Predicate; + @Getter @AllArgsConstructor -public enum ErrorStatus implements BaseErrorCode { +public enum ErrorStatus { // 가장 일반적인 응답 _INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "COMMON500", "서버 에러, 관리자에게 문의 바랍니다."), _BAD_REQUEST(HttpStatus.BAD_REQUEST,"COMMON400","잘못된 요청입니다."), _UNAUTHORIZED(HttpStatus.UNAUTHORIZED,"COMMON401","인증이 필요합니다."), _FORBIDDEN(HttpStatus.FORBIDDEN, "COMMON403", "금지된 요청입니다."), + _NOT_FOUND(HttpStatus.NOT_FOUND, "COMMON404", "페이지를 찾을 수 없습니다."), + + // 입력값 검증 관련 에러 + VALIDATION_ERROR(HttpStatus.BAD_REQUEST, "VALID401", "입력값이 올바르지 않습니다."), // 멤버 관려 에러 @@ -25,23 +30,9 @@ public enum ErrorStatus implements BaseErrorCode { private final String code; private final String message; - @Override - public ErrorReasonDTO getReason() { - return ErrorReasonDTO.builder() - .message(message) - .code(code) - .isSuccess(false) - .build(); - } - - @Override - public ErrorReasonDTO getReasonHttpStatus() { - return ErrorReasonDTO.builder() - .message(message) - .code(code) - .isSuccess(false) - .httpStatus(httpStatus) - .build() - ; + public String getMessage(String message) { + return Optional.ofNullable(message) + .filter(Predicate.not(String::isBlank)) + .orElse(this.getMessage()); } } diff --git a/src/main/java/com/example/phrasebe/common/status/SuccessStatus.java b/src/main/java/com/example/phrasebe/common/status/SuccessStatus.java new file mode 100644 index 0000000..d92666f --- /dev/null +++ b/src/main/java/com/example/phrasebe/common/status/SuccessStatus.java @@ -0,0 +1,17 @@ +package com.example.phrasebe.common.status; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum SuccessStatus { + + // 일반적인 응답 + _OK(HttpStatus.OK, "COMMON200", "성공입니다."); + + private final HttpStatus httpStatus; + private final String code; + private final String message; +} From 8e2c9ae108f9e332f36498e8035f2adde8eaa715 Mon Sep 17 00:00:00 2001 From: OSSU Date: Mon, 6 Jan 2025 05:36:30 +0900 Subject: [PATCH 3/5] =?UTF-8?q?:sparkles:=20feat:=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EC=9D=91=EB=8B=B5,=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=EA=B4=80=EB=A0=A8=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/phrasebe/test/TestController.java | 26 ++++++++++ .../example/phrasebe/test/TestService.java | 16 +++++++ .../phrasebe/test/TestControllerTest.java | 47 +++++++++++++++++++ 3 files changed, 89 insertions(+) create mode 100644 src/main/java/com/example/phrasebe/test/TestController.java create mode 100644 src/main/java/com/example/phrasebe/test/TestService.java create mode 100644 src/test/java/com/example/phrasebe/test/TestControllerTest.java diff --git a/src/main/java/com/example/phrasebe/test/TestController.java b/src/main/java/com/example/phrasebe/test/TestController.java new file mode 100644 index 0000000..dd999e9 --- /dev/null +++ b/src/main/java/com/example/phrasebe/test/TestController.java @@ -0,0 +1,26 @@ +package com.example.phrasebe.test; + +import com.example.phrasebe.common.response.ApiResponse; +import com.example.phrasebe.common.status.SuccessStatus; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class TestController { + @Autowired + private TestService testService; + + @GetMapping("/data") + public ResponseEntity getDataTest() { + String result = testService.testData(); + return ApiResponse.onSuccess(SuccessStatus._OK, result); + } + + @GetMapping("/error") + public ResponseEntity errorTest() { + testService.testError(); + return ApiResponse.onSuccess(SuccessStatus._OK); + } +} diff --git a/src/main/java/com/example/phrasebe/test/TestService.java b/src/main/java/com/example/phrasebe/test/TestService.java new file mode 100644 index 0000000..14bd596 --- /dev/null +++ b/src/main/java/com/example/phrasebe/test/TestService.java @@ -0,0 +1,16 @@ +package com.example.phrasebe.test; + +import com.example.phrasebe.common.exception.GeneralException; +import com.example.phrasebe.common.status.ErrorStatus; +import org.springframework.stereotype.Service; + +@Service +public class TestService { + public String testData() { + return "테스트 성공!"; + } + + public void testError() { + throw new GeneralException(ErrorStatus.MEMBER_NOT_FOUND); + } +} diff --git a/src/test/java/com/example/phrasebe/test/TestControllerTest.java b/src/test/java/com/example/phrasebe/test/TestControllerTest.java new file mode 100644 index 0000000..1c94832 --- /dev/null +++ b/src/test/java/com/example/phrasebe/test/TestControllerTest.java @@ -0,0 +1,47 @@ +package com.example.phrasebe.test; + +import com.example.phrasebe.common.status.ErrorStatus; +import com.example.phrasebe.common.status.SuccessStatus; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@AutoConfigureMockMvc +class TestControllerTest { + @Autowired + private MockMvc mockMvc; + + @Test + @DisplayName("getData(): 데이터가 포함된 ApiResponse 객체가 Http Response body로 설정된다.") + void getDataTest() throws Exception { + String expectResult = "테스트 성공!"; + + mockMvc.perform(MockMvcRequestBuilders.get("/data") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.isSuccess").value(true)) + .andExpect(jsonPath("$.code").value(SuccessStatus._OK.getCode())) + .andExpect(jsonPath("$.message").value(SuccessStatus._OK.getMessage())) + .andExpect(jsonPath("$.result").value(expectResult)); + } + + @Test + @DisplayName("errorTest(): GeneralException 발생 시 에러 응답이 반환된다.") + void errorTest() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/error") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().is4xxClientError()) + .andExpect(jsonPath("$.isSuccess").value(false)) + .andExpect(jsonPath("$.code").value(ErrorStatus.MEMBER_NOT_FOUND.getCode())) + .andExpect(jsonPath("$.message").value(ErrorStatus.MEMBER_NOT_FOUND.getMessage())); + } +} \ No newline at end of file From ff2f31ec9a3659beb608929fa7cf1d875b3a5546 Mon Sep 17 00:00:00 2001 From: OSSU Date: Mon, 6 Jan 2025 05:39:00 +0900 Subject: [PATCH 4/5] =?UTF-8?q?:heavy=5Fminus=5Fsign:=20chore:=20DB=20?= =?UTF-8?q?=EC=86=8C=EC=8A=A4=20=EC=84=B8=ED=8C=85=20=EC=A0=84=EC=9D=B4?= =?UTF-8?q?=EB=9D=BC=20=EA=B4=80=EB=A0=A8=20dependency=20=EA=B0=81?= =?UTF-8?q?=EC=A3=BC=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 6 ++--- .../phrasebe/common/response/ApiResponse.java | 22 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/build.gradle b/build.gradle index a99fb9b..855fde4 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ version = '0.0.1-SNAPSHOT' java { toolchain { - languageVersion = JavaLanguageVersion.of(21) + languageVersion = JavaLanguageVersion.of(17) } } @@ -24,12 +24,12 @@ repositories { } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' +// implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'jakarta.validation:jakarta.validation-api:3.0.2' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' - runtimeOnly 'com.mysql:mysql-connector-j' +// runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.assertj:assertj-core' diff --git a/src/main/java/com/example/phrasebe/common/response/ApiResponse.java b/src/main/java/com/example/phrasebe/common/response/ApiResponse.java index 0959712..938ef49 100644 --- a/src/main/java/com/example/phrasebe/common/response/ApiResponse.java +++ b/src/main/java/com/example/phrasebe/common/response/ApiResponse.java @@ -7,8 +7,8 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder; import lombok.Getter; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Slice; +//import org.springframework.data.domain.Page; +//import org.springframework.data.domain.Slice; import org.springframework.http.ResponseEntity; @Getter @@ -45,15 +45,15 @@ public static ResponseEntity onSuccess(SuccessStatus status, Object } // 성공 - 페이지네이션에 대한 응답 - public static ResponseEntity onSuccess(SuccessStatus status, Page page) { - PageInfo pageInfo = new PageInfo(page.getNumber(), page.getSize(), page.hasNext()); - return onSuccess(status, pageInfo, page.getContent()); - } - - public static ResponseEntity onSuccess(SuccessStatus status, Slice page) { - PageInfo pageInfo = new PageInfo(page.getNumber(), page.getSize(), page.hasNext()); - return onSuccess(status, pageInfo, page.getContent()); - } +// public static ResponseEntity onSuccess(SuccessStatus status, Page page) { +// PageInfo pageInfo = new PageInfo(page.getNumber(), page.getSize(), page.hasNext()); +// return onSuccess(status, pageInfo, page.getContent()); +// } +// +// public static ResponseEntity onSuccess(SuccessStatus status, Slice page) { +// PageInfo pageInfo = new PageInfo(page.getNumber(), page.getSize(), page.hasNext()); +// return onSuccess(status, pageInfo, page.getContent()); +// } // 실패한 경우 응답 생성 From 736b5ed3a6dfa8c8f507ad9e3eba673ebac39d1e Mon Sep 17 00:00:00 2001 From: OSSU Date: Mon, 6 Jan 2025 05:41:01 +0900 Subject: [PATCH 5/5] =?UTF-8?q?:bug:=20fix:=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=A0=9C=EA=B1=B0=EB=A5=BC=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20ResponseEntityExceptionHandler=20=EC=83=81?= =?UTF-8?q?=EC=86=8D=20=ED=95=B4=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/exception/ExceptionAdvice.java | 25 ++++--------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/example/phrasebe/common/exception/ExceptionAdvice.java b/src/main/java/com/example/phrasebe/common/exception/ExceptionAdvice.java index 4f89021..ca32f9c 100644 --- a/src/main/java/com/example/phrasebe/common/exception/ExceptionAdvice.java +++ b/src/main/java/com/example/phrasebe/common/exception/ExceptionAdvice.java @@ -2,29 +2,26 @@ import com.example.phrasebe.common.response.ApiResponse; import com.example.phrasebe.common.status.ErrorStatus; +import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolationException; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; -import org.springframework.web.context.request.WebRequest; -import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; import org.springframework.web.servlet.resource.NoResourceFoundException; @Slf4j @RestControllerAdvice -public class ExceptionAdvice extends ResponseEntityExceptionHandler { +public class ExceptionAdvice { @ExceptionHandler - public ResponseEntity validation(ConstraintViolationException e, WebRequest request) { + public ResponseEntity validation(ConstraintViolationException e) { String errorMessage = e.getConstraintViolations().stream() - .map(constraintViolation -> constraintViolation.getMessage()) + .map(ConstraintViolation::getMessage) .findFirst() .orElseThrow(() -> new RuntimeException("ConstraintViolationException 추출 도중 에러 발생")); - - return handleExceptionInternalConstraint(e, ErrorStatus.valueOf(errorMessage), HttpHeaders.EMPTY, request); + return ApiResponse.onFailure(ErrorStatus.VALIDATION_ERROR, errorMessage); } @ExceptionHandler(MethodArgumentNotValidException.class) @@ -65,16 +62,4 @@ public ResponseEntity handleGeneralException(GeneralException e) { return ApiResponse.onFailure(e.getErrorStatus(), e.getMessage()); } - - private ResponseEntity handleExceptionInternalConstraint(Exception e, ErrorStatus errorCommonStatus, - HttpHeaders headers, WebRequest request) { - ResponseEntity body = ApiResponse.onFailure(errorCommonStatus); - return super.handleExceptionInternal( - e, - body, - headers, - errorCommonStatus.getHttpStatus(), - request - ); - } }