항해99/개발일지

20220210 개발일지 #ErrorCode구현하기

paran21 2022. 2. 10. 17:16

오늘은 에러코드를 구현하였다.

Spring security쪽 에러는 처리하지 못하고 그외 다른 exception으로 처리한 부분을 모두 ErrorCode로 정리해주었다.

 

#ErrorCode

다음과 같이 각 에러마다 HttpStatus값을 지정하고 에러코드와 메세지를 입력하였다.

@Getter
public enum ErrorCode {

    // 400 Bad Request
    INVALID_MIN_ORDER_PRICE(HttpStatus.BAD_REQUEST, "400_1", "1,000원 ~ 100,000원 사이를 입력해주세요."),
    TYPE_ERROR_MIN_ORDER_PRICE(HttpStatus.BAD_REQUEST, "400_2", "100원 단위로 입력해주세요."),
    INVALID_DELIVERY_FEE(HttpStatus.BAD_REQUEST, "400_3", "0원 ~ 10,000원 사이를 입력해주세요."),
    TYPE_ERROR_DELIVERY_FEE(HttpStatus.BAD_REQUEST, "400_4", "500원 단위로 입력해주세요."),
    INVALID_X_Y(HttpStatus.BAD_REQUEST, "400_5", "0~99 사이로 입력해주세요."),
    DUPLICATE_FOOD_NAME(HttpStatus.BAD_REQUEST, "400_6", "중복된 이름이 있습니다."),
    DUPLICATE_FOOD(HttpStatus.BAD_REQUEST, "400_7", "이미 등록된 음식입니다."),
    INVALID_FOOD_PRICE(HttpStatus.BAD_REQUEST, "400_8", "가격은 100원 ~ 1,000,000원 사이로 입력하세요."),
    TYPE_ERROR_FOOD_PRICE(HttpStatus.BAD_REQUEST, "400_9", "가격은 100원 단위로 입력하세요."),
    INVALID_ORDER_PRICE(HttpStatus.BAD_REQUEST, "400_10", "주문 금액이 최소 주문 가격을 넘지 않습니다."),
    INVALID_ORDER_QUANTITY(HttpStatus.BAD_REQUEST, "400_11", "1~100 사이로 입력해주세요."),
    DUPLICATE_USER_NAME(HttpStatus.BAD_REQUEST, "400_12", "중복된 사용자 ID 가 존재합니다."),
    INVALID_ADMIN_TOKEN(HttpStatus.BAD_REQUEST, "400_13", "관리자 암호가 틀려 등록이 불가능합니다."),

    // 403 Forbidden
    NOT_ALLOWED(HttpStatus.FORBIDDEN, "403", "인가되지 않은 페이지입니다."),


    // 404 Not Found
    RESTAURANT_NOT_FOUND(HttpStatus.NOT_FOUND, "404_1", "해당 음식점이 존재하지 않습니다."),
    FOOD_NOT_FOUND(HttpStatus.NOT_FOUND, "404_2", "해당 음식이 존재하지 않습니다.");


    private final HttpStatus httpStatus;
    private final String errorCode;
    private final String errorMessage;

    ErrorCode(HttpStatus httpStatus, String errorCode, String errorMessage) {
        this.httpStatus = httpStatus;
        this.errorCode = errorCode;
        this.errorMessage = errorMessage;
    }
}

그리고 이 에러코드를 생성자로 받는 CustomException을 만들었다.

@Getter
@AllArgsConstructor
public class CustomException extends RuntimeException{
    private final ErrorCode errorCode;
}

 

# Error Code 적용

원래는 다른 NullPointerException 등 다른 에러가 있었던 곳에 RuntimeException을 상속받는 CustomException을 던져주고, 해당되는 Error Code이름을 넣어주었다.

@Component
public class FoodValidation {

    public static void validationFoodInput(FoodDto foodDto) {
        //price 유효성 검사
        if(foodDto.getPrice() < 100 || foodDto.getPrice() > 1000000) {
            throw new CustomException(INVALID_FOOD_PRICE);
        }
        if(foodDto.getPrice() % 100 != 0) {
            throw new CustomException(TYPE_ERROR_FOOD_PRICE);
        }

    }
}

RuntimeException(아마 다른 에러도 될 것같다)을 상속받는 클래스를 만들지 않고 바로 ErrorCode를 던져주고 싶었는데 방법을 찾지 못했다.

위에서 던진 CustomException을 이전에 global exception을 위해 만들어놓은 ExceptionHandler에 가서 ErrorResult객체에 담긴다.

여기서 Exception class가 필요해서 중간에 exception을 상속받는 클래스를 거쳐야 했다.

@RestControllerAdvice
public class ExceptionHandler {

    @org.springframework.web.bind.annotation.ExceptionHandler(value = {CustomException.class})
    public ResponseEntity handleCustomException(CustomException ex) {
        ErrorResult errorResult = new ErrorResult();
        errorResult.setErrorCode(ex.getErrorCode().getErrorCode());
        errorResult.setHttpStatus(ex.getErrorCode().getHttpStatus());
        errorResult.setErrorMessage(ex.getErrorCode().getErrorMessage());

        return new ResponseEntity(errorResult, errorResult.getHttpStatus());
    }
 }

결과값을 반환하기 위한 ErrorResult도 전에 만들어두었던 클래스를 조금 변형해서 만들었다.

@Getter
@Setter
public class ErrorResult {
    private String errorCode;
    private String errorMessage;
    private HttpStatus httpStatus;
}

에러가 발생하면 response에 다음과 같이 json 형태로 에러코드, 메시지 등이 담긴다.

{ errorCode: "400_12", errorMessage: "중복된 사용자 ID 가 존재합니다.", httpStatus: "BAD_REQUEST" }

에러코드를 @ExceptionHandler와 연결하는 부분이 어려웠는데 어떻게든 방법을 찾은 것 같다.

Spring security는 해결하지 못했는데, @ExceptionHandler에서 바로 AccessDeniedException을 던지는 코드를 봤다.

그래서 비슷하게 다음과 같이 추가해봤는데 제대로 동작하지는 않는다.

아마 configure에서 설정값을 바꿔주거나 중간 경로를 추가해야하는 것 같다.

만약에 에러코드를 안쓴다면 AccessDeniedHandler를 사용해서 메시지를 넘겨줄 수 있고, 에러페이지로 넘길 수도 있는 것 같다. (참고: https://velog.io/@rudwnd33/Spring-Security-AccessDeniedException)

 

이번주는 아주 만족스럽지는 않지만, 그래도 나름 새로운 기능들을 구현해본 것 같다.

특히 JPA와 데이터베이스 쪽은 계속 공부해서 DB를 잘 설계하고 싶다!!

 

Spring 기본 개념들을 보고 싶어서 Spring관련 책을 하나 샀는데 가볍게 전체 틀 잡는 느낌으로 빠르게 1회독을 해야겠다.

책 초반부에 DI와 @Autowired가 먼저 나오는데, 그래도 한 번 들었던 개념이라서 그런지 무슨 의미인지는 알 것 같다.

Spring 검색하다 나오는 여러 개념들이 잘 이해가 안되서 힘든데 도움이 됬으면 좋겠다.