ํ๋ก์ ํธ ๊ฐ๋ฐํ๋ ์ค ํ๋ก ํธ์๊ฒ ์์ธ ์ฒ๋ฆฌ ํ๋ ๊ฒ ํ๋ค๋ค๋ ์ฐ๋ฝ์ ๋ฐ์๋ค.
์ง๊ธ๊น์ง ๋ฐฑ์๋ ๊ฐ๋ฐ์ ํ๋ฉฐ ์์ธ ์ฒ๋ฆฌ๋ ์๋์ ๊ฐ์ด ์ฒ๋ฆฌํ๋ค.
- 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๋ฅผ ํ์ฉํ์ฌ ๊ตฌํํด๋์ ์ปค์คํ ์๋ฌ ์ฝ๋ ๋ฌธ์ํ ํ์ด์ง์ด๋ค.
์๋๋ ๋ด๊ฐ ํ์๋ฆฌํ๋ก ๊ตฌ์ฑํ ์๋ฌ ์ฝ๋ ๋ฌธ์ ํ์ด์ง์ด๋ค.
์ด์ ๋๋ ์งํผํฐ๊ฐ ๊ต์ฅํ.. ์ํด์ค๋ค๐ค