diff --git a/src/main/java/com/umc/dream/apiPayload/ApiResponse.java b/src/main/java/com/umc/dream/apiPayload/ApiResponse.java new file mode 100644 index 0000000..f02bec2 --- /dev/null +++ b/src/main/java/com/umc/dream/apiPayload/ApiResponse.java @@ -0,0 +1,36 @@ +package com.umc.dream.apiPayload; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import lombok.AllArgsConstructor; +import lombok.Getter; +import umc.spring.apiPayload.code.status.SuccessStatus; + +@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/umc/dream/apiPayload/code/BaseCode.java b/src/main/java/com/umc/dream/apiPayload/code/BaseCode.java new file mode 100644 index 0000000..63b16ee --- /dev/null +++ b/src/main/java/com/umc/dream/apiPayload/code/BaseCode.java @@ -0,0 +1,9 @@ +package com.umc.dream.apiPayload.code; + +public interface BaseCode { + + public ReasonDTO getReason(); + + public ReasonDTO getReasonHttpStatus(); + +} diff --git a/src/main/java/com/umc/dream/apiPayload/code/BaseErrorCode.java b/src/main/java/com/umc/dream/apiPayload/code/BaseErrorCode.java new file mode 100644 index 0000000..63f943b --- /dev/null +++ b/src/main/java/com/umc/dream/apiPayload/code/BaseErrorCode.java @@ -0,0 +1,8 @@ +package com.umc.dream.apiPayload.code; + +public interface BaseErrorCode { + + public ErrorReasonDTO getReason(); + + public ErrorReasonDTO getReasonHttpStatus(); +} diff --git a/src/main/java/com/umc/dream/apiPayload/code/ErrorReasonDTO.java b/src/main/java/com/umc/dream/apiPayload/code/ErrorReasonDTO.java new file mode 100644 index 0000000..5a5badf --- /dev/null +++ b/src/main/java/com/umc/dream/apiPayload/code/ErrorReasonDTO.java @@ -0,0 +1,20 @@ +package com.umc.dream.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/umc/dream/apiPayload/code/ReasonDTO.java b/src/main/java/com/umc/dream/apiPayload/code/ReasonDTO.java new file mode 100644 index 0000000..a0e1b74 --- /dev/null +++ b/src/main/java/com/umc/dream/apiPayload/code/ReasonDTO.java @@ -0,0 +1,20 @@ +package com.umc.dream.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/umc/dream/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/umc/dream/apiPayload/code/status/ErrorStatus.java new file mode 100644 index 0000000..e008b33 --- /dev/null +++ b/src/main/java/com/umc/dream/apiPayload/code/status/ErrorStatus.java @@ -0,0 +1,70 @@ +package com.umc.dream.apiPayload.code.status; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; +import com.umc.dream.apiPayload.code.BaseErrorCode; +import com.umc.dream.apiPayload.code.ErrorReasonDTO; + +@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", "닉네임은 필수 입니다."), + + // FoodCategory Error + FOOD_CATEGORY_NOT_FOUND(HttpStatus.NOT_FOUND, "FOOD_CATEGORY4001", "음식 카테고리가 없습니다."), + + // Store Error + STORE_NOT_FOUND(HttpStatus.NOT_FOUND, "STORE4001", "가게가 없습니다."), + + // Region Error + REGION_NOT_FOUND(HttpStatus.NOT_FOUND, "REGION4001", "해당 지역이 없습니다."), + + // Mission Error + MISSION_NOT_FOUND(HttpStatus.NOT_FOUND, "MISSION4001", "해당 미션이 없습니다."), + ALREADY_CHALLENGING(HttpStatus.NOT_FOUND, "MISSION4002", "이미 도전중인 미션입니다."), + + // Paging Error + PAGE_NOT_FOUND(HttpStatus.NOT_FOUND, "PAGING4001", "잘못된 페이지 범위입니다."), + + // 예시 + ARTICLE_NOT_FOUND(HttpStatus.NOT_FOUND, "ARTICLE4001", "게시글이 없습니다."), + + // 테스트 + TEMP_EXCEPTION(HttpStatus.BAD_REQUEST, "TEMP4001", "이거는 테스트"); + + + + 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/umc/dream/apiPayload/code/status/SuccessStatus.java b/src/main/java/com/umc/dream/apiPayload/code/status/SuccessStatus.java new file mode 100644 index 0000000..f25f9bb --- /dev/null +++ b/src/main/java/com/umc/dream/apiPayload/code/status/SuccessStatus.java @@ -0,0 +1,43 @@ +package com.umc.dream.apiPayload.code.status; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; +import umc.spring.apiPayload.code.BaseCode; +import umc.spring.apiPayload.code.ReasonDTO; + +@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/umc/dream/apiPayload/exception/ExceptionAdvice.java b/src/main/java/com/umc/dream/apiPayload/exception/ExceptionAdvice.java new file mode 100644 index 0000000..ed7845c --- /dev/null +++ b/src/main/java/com/umc/dream/apiPayload/exception/ExceptionAdvice.java @@ -0,0 +1,119 @@ +package com.umc.dream.apiPayload.exception; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.ConstraintViolationException; +import lombok.extern.slf4j.Slf4j; +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 com.umc.dream.apiPayload.ApiResponse; +import com.umc.dream.apiPayload.code.ErrorReasonDTO; +import com.umc.dream.apiPayload.code.status.ErrorStatus; + +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 + protected 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/umc/dream/apiPayload/exception/GeneralException.java b/src/main/java/com/umc/dream/apiPayload/exception/GeneralException.java new file mode 100644 index 0000000..4131603 --- /dev/null +++ b/src/main/java/com/umc/dream/apiPayload/exception/GeneralException.java @@ -0,0 +1,21 @@ +package com.umc.dream.apiPayload.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import com.umc.dream.apiPayload.code.BaseErrorCode; +import com.umc.dream.apiPayload.code.ErrorReasonDTO; + +@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/umc/dream/apiPayload/exception/handler/TempHandler.java b/src/main/java/com/umc/dream/apiPayload/exception/handler/TempHandler.java new file mode 100644 index 0000000..b4e6c3d --- /dev/null +++ b/src/main/java/com/umc/dream/apiPayload/exception/handler/TempHandler.java @@ -0,0 +1,11 @@ +package com.umc.dream.apiPayload.exception.handler; + +import com.umc.dream.apiPayload.code.BaseErrorCode; +import com.umc.dream.apiPayload.exception.GeneralException; + +public class TempHandler extends GeneralException { + + public TempHandler(BaseErrorCode errorCode) { + super(errorCode); + } +}