Spring

[Spring] 스프링 예외처리 통일 | 스프링 HttpStatus 반환 통일 | 스프링 에러 통일

kimslab01 2024. 9. 5. 11:56

 

 

 

협업하다보면 에러 메세지를 작성하는 방식이 모두 다릅니다.

더하여 리턴할 숫자도 정해주어야 하는데요!

이번에 스프링으로 개발하며 사용했던 에러 통일 방법을 알려드리겠습니다.

 

 

 

먼저 ApiPayload 패키지를 만들어줍니다.

com/
└── sparta/
    └── newsfeed/
        ├── ApiPayload/
        │   ├── Code/
        │   └──── dto/
        |   └────── ErrorReasonDto.java
        |   └────── ReasonDto.java
        │   ├──── Status/
        │   ├────── ErrorStatus.java
        │   ├────── SuccessStatus.java
        │   ├──── BaseCode.java
        │   └──── BaseErrorCode.java
        └──────── ApiResponse.java

 

파일 경로입니다.

 

 

 

코드를 넣어줍시다.

 

1. ErrorReasonDto

package com.sparta.nbcampnewsfeed.ApiPayload.Code.dto;

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

@Builder
@Getter
public class ErrorReasonDto {
    private String code;
    private String message;
    private HttpStatus httpStatus;
    private Boolean isSuccess;
}

 

 

2. ReasonDto

package com.sparta.nbcampnewsfeed.ApiPayload.Code.dto;

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

@Builder
@Getter
public class ReasonDto {
    private String code;
    private String message;
    private HttpStatus httpStatus;
    private Boolean isSuccess;
}

 

 

3. ErrorStatus

package com.sparta.nbcampnewsfeed.ApiPayload.Code.Status;

import com.sparta.nbcampnewsfeed.ApiPayload.Code.BaseErrorCode;
import com.sparta.nbcampnewsfeed.ApiPayload.Code.dto.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, "500", "Interval server error"),
    _BAD_REQUEST(HttpStatus.BAD_REQUEST,"400","잘못된 요청입니다."),
    _BAD_REQUEST_EMAIL(HttpStatus.BAD_REQUEST,"400","중복된 이메일입니다."),
    _BAD_REQUEST_USER(HttpStatus.BAD_REQUEST,"400","탈퇴한 회원입니다."),
    _BAD_REQUEST_PASSWORD(HttpStatus.BAD_REQUEST,"400","잘못된 비밀번호입니다."),
    _BAD_REQUEST_SELF_LIKE(HttpStatus.BAD_REQUEST,"400","자신의 게시물에 좋아요를 남길 수 없습니다."),
    _BAD_REQUEST_DOUBLE_LIKE(HttpStatus.BAD_REQUEST,"400","같은 게시물에는 좋아요를 한 번만 남길 수 있습니다."),
    _BAD_REQUEST_SAME_PASSWORD(HttpStatus.BAD_REQUEST,"400","이전 비밀번호와 같은 비밀번호로 설정 불가합니다."),
    _UNAUTHORIZED(HttpStatus.UNAUTHORIZED,"401","인증이 필요합니다."),
    _FORBIDDEN(HttpStatus.FORBIDDEN, "403", "금지된 요청입니다."),
    _NOT_FOUND(HttpStatus.NOT_FOUND, "404", "존재하지 않는 리소스입니다."),
    _NOT_FOUND_USER(HttpStatus.NOT_FOUND, "404", "존재하지 않는 유저입니다."),
    _NOT_FOUND_POST(HttpStatus.NOT_FOUND, "404", "존재하지 않는 게시물입니다."),
    _NOT_FOUND_COMMENT(HttpStatus.NOT_FOUND, "404", "존재하지 않는 댓글입니다."),
    _NOT_FOUND_LIKE(HttpStatus.NOT_FOUND, "404", "존재하지 않는 좋아요입니다."),
    // friend 예외처리
    _BAD_REQUEST_SELF_FRIEND(HttpStatus.BAD_REQUEST, "400","자기 자신에게 친구 신청을 할 수 없습니다."),
    _BAD_REQUEST_FROM_USER(HttpStatus.BAD_REQUEST, "400", "이미 친구이거나 친구 신청을 한 회원입니다."),
    _BAD_REQUEST_TO_USER(HttpStatus.BAD_REQUEST, "400", "이미 해당 회원으로부터 친구 신청을 받은 상태입니다."),
    _NOT_FOUND_FRIEND(HttpStatus.NOT_FOUND, "404", "해당 회원에게 받은 친구 추가 요청이 없습니다."),
    _BAD_REQUEST_ALREADY_FRIEND(HttpStatus.BAD_REQUEST,"400", "이미 친구인 회원입니다."),
    _BAD_REQUEST_NOT_FRIEND(HttpStatus.BAD_REQUEST, "400", "친구가 아닌 회원입니다.");

    private HttpStatus httpStatus;
    private String code;
    private String message;

    @Override
    public ErrorReasonDto getReasonHttpStatus() {
        return ErrorReasonDto.builder()
                .code(code)
                .message(message)
                .isSuccess(false)
                .httpStatus(httpStatus)
                .build();
    }
}

 

이 코드에서 직접 에러에 대해 적을 수 있는데요!

여기서 저는 FriendSerivce 역할을 맡아, friend와 관련된 오류를 추가해주었습니다.

내용에 맞게 반환할 것이 없다면 NOT_FOUND로, 요청이 잘못되었다면 BAD_REQUEST로 작성했습니다!

 

 

4. SuccessStatus

package com.sparta.nbcampnewsfeed.ApiPayload.Code.Status;

import com.sparta.nbcampnewsfeed.ApiPayload.Code.BaseCode;
import com.sparta.nbcampnewsfeed.ApiPayload.Code.dto.ReasonDto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
@AllArgsConstructor
public enum SuccessStatus implements BaseCode {

    _OK(HttpStatus.OK, "200", "Ok");

    private HttpStatus httpStatus;
    private String code;
    private String message;

    @Override
    public ReasonDto getReasonHttpStatus() {
        return ReasonDto.builder()
                .code(code)
                .message(message)
                .isSuccess(true)
                .httpStatus(httpStatus)
                .build();
    }
}

 

 

5. BaseCode

package com.sparta.nbcampnewsfeed.ApiPayload.Code;

import com.sparta.nbcampnewsfeed.ApiPayload.Code.dto.ReasonDto;

public interface BaseCode {
    public ReasonDto getReasonHttpStatus();
}

 

 

6. BaseErrorCode

package com.sparta.nbcampnewsfeed.ApiPayload.Code;

import com.sparta.nbcampnewsfeed.ApiPayload.Code.dto.ErrorReasonDto;

public interface BaseErrorCode {
    public ErrorReasonDto getReasonHttpStatus();
}

 

 

7. ApiResponse

package com.sparta.nbcampnewsfeed.ApiPayload;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.sparta.nbcampnewsfeed.ApiPayload.Code.BaseCode;
import com.sparta.nbcampnewsfeed.ApiPayload.Code.BaseErrorCode;
import com.sparta.nbcampnewsfeed.ApiPayload.Code.Status.SuccessStatus;
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
@JsonPropertyOrder({"isSuccess", "code", "message", "result"})
public class ApiResponse<T> {
    @JsonProperty("isSuccess")
    private final Boolean isSuccess;
    private final String code;
    private final String message;
    @JsonInclude(JsonInclude.Include.NON_NULL)
    private final T result;

    // 일반적인 응답 생성
    public static <T> ApiResponse<T> onSuccess(T result) {
        return new ApiResponse<>(true, SuccessStatus._OK.getCode(), SuccessStatus._OK.getMessage(), result);
    }

    public static <T> ApiResponse<T> of(BaseCode code, T result) {
        return new ApiResponse<>(true, code.getReasonHttpStatus().getCode(), code.getReasonHttpStatus().getMessage(), result);
    }

    // 실패한 경우 응답 생성
    public static <T> ApiResponse<T> onFailure(String code, String message, T data) {
        return new ApiResponse<>(false, code, message, data);
    }

    public static ApiResponse<String> onFailure(BaseErrorCode baseErrorCode) {
        return new ApiResponse<>(false, baseErrorCode.getReasonHttpStatus().getCode(), baseErrorCode.getReasonHttpStatus().getMessage(), "null");
    }
}

 

 

 

 


 

 

 

이 코드들을 추가하여 실제로 원래 Serivce에 있던 예외들을 다 통일하여 다시 처리했습니다.

보기 좋게 깃으로 보여드릴게요!

 

 

ErrorStatus에서 원하는 에러를 추가해준 다음, 원래 있던 코드를 삭제하고 ApiException을 이용하여 만들어주었던 코드를 입력합니다!

이 방법을 통해 팀원들끼리 오류 메시지를 다 맞춰서 높은 디테일의 프로젝트를 가져갈 수 있었습니다!