예외 처리 및 응답 형식을 맞추기 위한 공통 모듈

ResponseDto

package com.common.demo.module.response;

import com.common.demo.module.status.StatusCode;
import lombok.Getter;
import org.springframework.http.ResponseEntity;

import java.time.LocalDateTime;

@Getter
public class ResponseDto {

    private final Object data;
    private final String message;
    private final LocalDateTime timestamp = LocalDateTime.now();
    private final int httpStatus;
    private final int serviceStatus;

    public ResponseDto(StatusCode statusCode, Object data) {
        this.httpStatus = statusCode.getHttpStatus().value();
        this.serviceStatus = statusCode.getServiceStatus();
        this.message = statusCode.getMessage();
        this.data = data;
    }

    public static ResponseEntity<ResponseDto> response(StatusCode statusCode, Object data) {
        return ResponseEntity
                .status(statusCode.getHttpStatus())
                .body(new ResponseDto(statusCode, data));
    }
}

<aside> 💡

응답 형식 클래스

</aside>

StatusCode

package com.common.demo.module.status;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;

@Getter
@RequiredArgsConstructor

public enum StatusCode {

    SUCCESS(HttpStatus.OK, 200, "정상적으로 요청이 완료되었습니다."),

    BAD_REQUEST(HttpStatus.BAD_REQUEST, 400, "잘못된 요청입니다."),
    NO_SUCH_ELEMENT(HttpStatus.OK,401,"정상적으로 요청이 완료되었지만, 요청 정보를 찾을 수 없습니다."),
    UNAUTHORIZED_REQUEST(HttpStatus.BAD_REQUEST, 402, "로그인되지 않은 사용자입니다."),
    FORBIDDEN_ACCESS(HttpStatus.BAD_REQUEST, 403, "권한이 없는 사용자입니다."),
    NOT_FOUND(HttpStatus.NOT_FOUND, 404, "요청 정보를 찾을 수 없습니다."),
    DUPLICATE_EMAIL(HttpStatus.BAD_REQUEST, 405, "중복된 이메일 입니다."),
    DUPLICATE_ID(HttpStatus.BAD_REQUEST, 406, "중복된 아이디 입니다."),
    INVALID_ID_OR_PASSWORD(HttpStatus.BAD_REQUEST, 407, "아이디/비밀번호가 일치하지 않습니다."),

    INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, 500, "서버에서 처리 중 에러가 발생했습니다."),
    SERVICE_STOP(HttpStatus.INTERNAL_SERVER_ERROR, 501, "현재 서버가 이용 불가능 상태입니다.");

    private final HttpStatus httpStatus;
    private final int serviceStatus;
    private final String message;
}

<aside> 💡

상태 코드 명명

</aside>

RestApiException

package com.common.demo.module.exception;

import com.common.demo.module.status.StatusCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
public class RestApiException extends RuntimeException {
    private final StatusCode statusCode;
    private final Object data;

    public RestApiException(final StatusCode statusCode, final Object data) {
        this.statusCode = statusCode;
        this.data = data;
    }

    public RestApiException(final StatusCode statusCode) {
        this(statusCode, null);
    }
}

GlobalExceptionHandler

package com.common.demo.module.exception;

import com.common.demo.module.response.ResponseDto;
import com.common.demo.module.status.StatusCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    /**
     * @ExceptionHandler(감지 할 예외)
     * INTERNAL_SERVER_ERROR 예외의 상태 코드와 메시지를 ResponseEntity<ResponseDto> 만들어준다.
     * return handleExceptionInternal(ErrorCode.INTERNAL_SERVER_ERROR);
     */
    @ExceptionHandler({Exception.class})
    public ResponseEntity<ResponseDto> handleAllException(Exception ex) {
        log.error("[exceptionHandle] ex", ex);
        return handleExceptionInternal(StatusCode.INTERNAL_SERVER_ERROR);
    }

    @ExceptionHandler(RestApiException.class)
    public ResponseEntity<ResponseDto> handleRestApiException(RestApiException ex) {
        return handleExceptionInternal(ex.getStatusCode());
    }

    private ResponseEntity<ResponseDto> handleExceptionInternal(StatusCode errorCode) {
        return ResponseEntity
                .status(errorCode.getHttpStatus())
                .body(new ResponseDto(errorCode,null));
    }

}

테스트 컨트롤러 (테스트해보고 싶을 때 사용)

package com.common.demo.module;

import com.common.demo.module.exception.RestApiException;
import com.common.demo.module.response.ResponseDto;
import com.common.demo.module.status.StatusCode;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/exception")
public class ExceptionTestController {

    // 정상 처리 시
    @GetMapping("/ok")
    public ResponseEntity<ResponseDto> ok(){
        String data = "전송할 아무 객체";
        return ResponseDto.response(StatusCode.SUCCESS, data);
    }

    // 로그인 안된 경우 예시
    @GetMapping("/login")
    public ResponseEntity<ResponseDto> login(){
        String data = "전송할 아무 객체";
        return ResponseDto.response(StatusCode.INVALID_ID_OR_PASSWORD, data);
    }

    // 예외 발생 예시
    @GetMapping("/error")
    public ResponseEntity<ResponseDto> error(){
        throw new RestApiException(StatusCode.INTERNAL_SERVER_ERROR);
    }

}

응답 예시