ํ๋ก์ ํธ ๊ฐ๋ฐํ๋ ์ค ํ๋ก ํธ์๊ฒ ์์ธ ์ฒ๋ฆฌ ํ๋ ๊ฒ ํ๋ค๋ค๋ ์ฐ๋ฝ์ ๋ฐ์๋ค.
์ง๊ธ๊น์ง ๋ฐฑ์๋ ๊ฐ๋ฐ์ ํ๋ฉฐ ์์ธ ์ฒ๋ฆฌ๋ ์๋์ ๊ฐ์ด ์ฒ๋ฆฌํ๋ค.
- AppException
@Getter public class JikgongException extends RuntimeException { private final HttpStatus status; private final String errorCode; private final String errorMessage; // ErrorCode ์์ฑ์ public JikgongException(ErrorCode errorCode) { super(errorCode.getErrorMessage()); this.status = errorCode.getStatus(); this.errorCode = errorCode.getCode(); this.errorMessage = errorCode.getErrorMessage(); } public JikgongException(HttpStatus status, String errorCode, String errorMessage) { super(errorMessage); this.status = status; this.errorCode = errorCode; this.errorMessage = errorMessage; } }
- ErrorCode
@Getter @RequiredArgsConstructor public enum ErrorCode { /** * member */ MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "ํ์ ์ ๋ณด๊ฐ ์์ต๋๋ค."), ACCESS_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, "๋ง๋ฃ๋ access token ์
๋๋ค."), REFRESH_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, "๋ง๋ฃ๋ refresh token ์
๋๋ค."), REFRESH_TOKEN_NOT_MATCH(HttpStatus.FORBIDDEN, "์ ํจํ์ง ์์ refresh token ์
๋๋ค. ๋ค์ ๋ก๊ทธ์ธํ์ธ์."), MEMBER_PHONE_EXIST(HttpStatus.CONFLICT, "์ด๋ฏธ ๋ฑ๋ก๋ ํธ๋ํฐ ๋ฒํธ์
๋๋ค."), // ... /** * ์๋ฆผ */ NOTIFICATION_NOT_FOUND(HttpStatus.NOT_FOUND, "์๋ฆผ ์ ๋ณด๊ฐ ์์ต๋๋ค."), // ... /** * ์ข์์ */ LIKE_NOT_FOUND(HttpStatus.NOT_FOUND, "์ข์์ ๋๋ฅธ ์ ๋ณด๊ฐ ์์ต๋๋ค."), LIKE_REQUEST_INVALID(HttpStatus.BAD_REQUEST, "์ข์์๋ ๊ธฐ์
์ด ๋
ธ๋์์๊ฒ ๋๋ฅผ ์ ์์ต๋๋ค."), LIKE_ALREADY_EXIST(HttpStatus.CONFLICT, "ํด๋น ํ์์๊ฒ ์ด๋ฏธ ์ข์์๋ฅผ ๋๋ ์ต๋๋ค."), /** * ์ด๋ฏธ์ง, ํ์ผ, ๊ฒฝ๋ ฅ ์ฆ๋ช
์ */ FILE_NOT_FOUND_EXTENSION(HttpStatus.BAD_REQUEST, "ํ์ฅ์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค."), // ... private final HttpStatus status; private final String errorMessage; }
- ExceptionHandler
@ExceptionHandler(JikgongException.class) public ResponseEntity<?> handleCustomException(JikgongException e) { log.error("ํธ๋ค๋งํ ์๋ฌ ๋ฐ์"); ErrorResponse errorResponse = ErrorResponse.builder() .status(e.getStatus()) .code(e.getErrorCode()) .errorMessage(e.getErrorMessage()) .build(); return ResponseEntity.status(e.getStatus()).body(new Response<>(errorResponse, "์ปค์คํ
์์ธ ๋ฐํ")); }
- ErrorResponse
@Builder @Getter public class ErrorResponse { private HttpStatus status; private String errorMessage; }
์ปค์คํ ํ ์์ธ ํด๋์ค๋ฅผ ๋ง๋ค๊ณ , status์ message๋ Enum์ผ๋ก ๊ด๋ฆฌํ๋ ๋ฐฉ์์ ์์ธ ์ฒ๋ฆฌ์ด๋ค. ๋ง์ ๋ถ๋ค์ด ์ด๋ฐ์์ผ๋ก ์์ธ์ฒ๋ฆฌ๋ฅผ ํ๊ณ ๊ณ์ ๋ฐ, ํ ๊ฐ์ง ์์ฌ์ด ์ ์ Http Status Code๊ฐ 400, 401, 403 404, 409, 500 ๋ฑ์ด ์๋๋ฐ, ํ๋์ request์์ ๋์ผํ code์ ์์ธ๊ฐ ๋ฐ์ํ๋ค๋ฉด ์ํ ์ฝ๋๋ง์ผ๋ก ์๋ฌ๋ฅผ ๊ตฌ๋ถํ๊ธฐ๊ฐ ์ฝ์ง ์๋ค.
๋ง์ฝ ํ์์ ๋ฑ๋กํ๊ธฐ ์ํด์ ๊ณ ์ ํ loginId, ๊ณ ์ ํ phone number์ด ํ์ํ๋ค ๊ฐ์ ํด๋ณด์. ์๋ง ์๋ฌ ์๋ต์ ์๋์ ๊ฐ์ด ๋ฐํ๋ ๊ฒ์ด๋ค.
// response { "status" : HttpStatus.CONFLICT "message" : "์ด๋ฏธ ๋ฑ๋ก๋ ํธ๋ํฐ ๋ฒํธ์
๋๋ค." }
์ด๋ status๋ฅผ ํ์ฉํ ์์ธ ์ฒ๋ฆฌ๋ ๋์ผํ status๋ฅผ ๊ฐ๋ ์๋ฌ๊ฐ ์ฌ๋ฌ ๊ฐ ๋ฐ์ํ ์๋ ์๊ธฐ ๋๋ฌธ์ ์๋์ ๊ฐ์ด ์์ธ๋ฅผ ๊ตฌ๋ถํ๋ ๊ฒ์ ์ข์ง ๋ชปํ๋ค.
if (response.errorCode == 409)
๊ทธ๋ ๋ค๋ฉด message๋ก ๊ตฌ๋ถํด์ผ ํ๋๋ฐ message๋ ์ธ์ ๋ ๋ณ๊ฒฝ๋ ์ ์๊ณ ์ ์ง๋ณด์ ์ธก๋ฉด์์ ๋ดค์ ๋๋ ์ข์ง ๋ชปํ๋ค.
์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์๋์ ๊ฐ์ด ์ปค์คํ ์๋ฌ ์ฝ๋๋ฅผ ์ถ๊ฐํด์ฃผ์๋ค.
@Getter @RequiredArgsConstructor public enum ErrorCode { /** * member */ MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "MEMBER-001", "ํ์ ์ ๋ณด๊ฐ ์์ต๋๋ค."), ACCESS_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, "MEMBER-002", "๋ง๋ฃ๋ access token ์
๋๋ค."), REFRESH_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, "MEMBER-003", "๋ง๋ฃ๋ refresh token ์
๋๋ค."). ... private final HttpStatus status; private final String code; private final String errorMessage; }
๋๋ [๋๋ฉ์ธ]-[Code] ๋ฐฉ์์ผ๋ก ์ปค์คํ ํ๊ฒ ์ฝ๋๋ฅผ ๊ตฌ์ฑํ๋๋ฐ, ๊ฐ์ ์ํ๋๋ฐ๋ก ํ๋ฉด ๋ ๊ฒ ๊ฐ๋ค.
์ด๋ ๊ฒ ๋ง๋ Custom Error Code๋ ๋ ธ์ ๋ฑ์ ์ด์ฉํด ๋ฐ๋ก ๋ฌธ์ํ ํด ํด๋ผ์ด์ธํธ ๊ฐ๋ฐ์์ ๊ณต์ ํด์ผํ๋ค. ๋ ์ถ๊ฐ๋๊ฑฐ๋ ์์ ๋ ๊ฒฝ์ฐ์๋ ์ ๋ฐ์ดํธ ํด์ค์ผํ๋ค.
๊ต์ฅํ ๋นํจ์จ์ ์ด๊ธฐ ๋๋ฌธ์ swagger ์ฒ๋ผ ์๋์ผ๋ก ๋ฌธ์ํ ์์ผ์ฃผ๋ ค ์ด๊ฒ์ ๊ฒ ์ฐพ์๋ดค์๋ค.
๋ง์ด ๋ณด์๋ ๋ฐฉ์์ด RestDocs์ ๋ฌธ์ํ๋ฅผ ํ๋ ๋ฐฉ์์ด์๋ค.
Custom .Snippet file, ์๋ต๊ฐ ํ์๊ณผ ํ์์ ๋ง๋ Descriptor ๊ตฌํ, ๊ทธ๋ฆฌ๊ณ Custom .Snippet file๊ณผ Descriptor๋ค์ ์ง์ ํ Custom Response ํด๋์ค ๋ฑ์ด ํ์ํ๋ค.
๋ ์ด๋ ๊ฒ ๋ง๋ค์ด์ง ๋ด์ฉ์ Test์ฝ๋๊น์ง ์์ฑํด์ผํ๋ค.
์ด๋ฏธ RestDocs๋ฅผ ์ฌ์ฉํ๊ณ ์์ผ์๋ค๋ฉด ์ด๋ฐ ๋ฐฉ์๋ ๊ด์ฐฎ์ ๊ฒ ๊ฐ์๋ฐ, ๋๋ Swagger๋ฅผ ์ฌ์ฉํ๊ณ ์์๊ธฐ ๋๋ฌธ์ ใฑ...๊ท์ฐฎ์๋ค.
๊ทธ๋ฅ SSR(ํ์๋ฆฌํ)๋ก ํ๋ฉด์ ๊ฐ๋ฐํ์ฌ ์๋ฌ ์ฝ๋ ๋ฌธ์ํ๋ฅผ ์์ผ์คฌ๋ค.
- ErrorCodeController
@Controller public class ErrorCodeController { /** * ์ปค์คํ
์๋ฌ ์ฝ๋ ๋ฌธ์ํ๋ฅผ ์ํ ์ปจํธ๋กค๋ฌ */ @GetMapping("/error-codes") public String getAllErrorCodes(Model model) { Map<String, List<ErrorCodeResponse>> groupedErrorCodes = Stream.of(ErrorCode.values()) .map(errorCode -> new ErrorCodeResponse( errorCode.getStatus(), errorCode.getCode(), errorCode.getErrorMessage())) .collect(Collectors.groupingBy(errorCodeResponse -> errorCodeResponse.getCode().split("-")[0])); List<ErrorCodeGroup> errorCodeGroups = groupedErrorCodes.entrySet().stream() .map(entry -> new ErrorCodeGroup(entry.getKey(), entry.getValue())) .collect(Collectors.toList()); model.addAttribute("errorCodeGroups", errorCodeGroups); return "errorCodes"; } }
ErrorCode ์ ์ฒด๋ฅผ ์ํํ๋ฉฐ ErrorCodeResponse๋ฅผ ๋ง๋ค์ด์ฃผ์๊ณ , ๋๋ฉ์ธ๋ณ๋ก ๋ฌถ์ด Key, Value ํ์์ Map์ ์์ฑํด์ฃผ์๋ค.
์ฆ Key๊ฐ Member, Value๊ฐ List<ErrorCodeResponse>์ธ Map์ ์์ฑํด์ฃผ์๊ณ , ์ด๋ฅผ model์ ๋ด์ ๋๊ฒจ์ฃผ์๋ค.
์๋ ์ฌ์ง์ ๋ค๋ฅธ ๋ถ์ด RestDocs๋ฅผ ํ์ฉํ์ฌ ๊ตฌํํด๋์ ์ปค์คํ ์๋ฌ ์ฝ๋ ๋ฌธ์ํ ํ์ด์ง์ด๋ค.

์๋๋ ๋ด๊ฐ ํ์๋ฆฌํ๋ก ๊ตฌ์ฑํ ์๋ฌ ์ฝ๋ ๋ฌธ์ ํ์ด์ง์ด๋ค.

์ด์ ๋๋ ์งํผํฐ๊ฐ ๊ต์ฅํ.. ์ํด์ค๋ค๐ค