에러 메세지의 형식을 맞추기 위해 사용자 정의 Exception을 작성
사용자 정의 Exception은 전체적으로 BaseException을 상속
field, code, message 값을 가지고 에러를 처리
@ Getter @ Setter
@ NoArgsConstructor
@ AllArgsConstructor
public class BaseException extends RuntimeException {
private String field ;
private String code ;
private String message ;
}
public class RequiredFieldException extends BaseException {
public RequiredFieldException (String field , String message ) {
super (field , "Required" , message );
}
}
public class NotEnoughPointException extends BaseException {
public NotEnoughPointException () {
super .setField ("point" );
super .setCode ("NotEnough" );
super .setMessage ("포인트가 부족합니다. 포인트를 충전하고 다시 시도해주세요." );
}
}
public class NotValidFileExtensionException extends BaseException {
public NotValidFileExtensionException () {
super .setCode ("NotValid" );
super .setField ("fileExtension" );
super .setMessage ("지원하지 않는 파일확장자 입니다" );
}
}
...
error.properties, MessageSource
error.properties를 MessageBean으로 등록하여 에러 메세지를 관리
errors의 field와 code를 통해 메세지를 등록
@ Bean
public ReloadableResourceBundleMessageSource messageSource () {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource ();
messageSource .setBasename ("classpath:/errors" );
messageSource .setDefaultEncoding ("UTF-8" );
return messageSource ;
}
#회원가입
NotBlank.email = 이메일을 입력해주세요
Duplicate.email = 중복된 이메일 입니다
...
#기프티콘 및 관리자
NotBlank.productName = 상품 이름을 확인해주세요
NotBlank.brandName = 브랜드 이름을 확인해주세요
NotBlank.due = 유효기간을 확인해주세요
NotNull.price 가격을 확인해주세요
NotSelect.category = 카테고리를 선택해주세요
...
#포인트 구매 및 결제
NotEnough.point = 포인트가 부족합니다 포인트를 충전하고 다시 시도해주세요
Empty.totalAmount = 총 가격이 0입니다 상품 추가 후 다시 시도해주세요
Mismatch.paidId = 결제 시도 아이디와 로그인 아이디가 다릅니다 재로그인 후 다시 시도해주세요
NotFound.gifticon = 없는 기프티콘입니다 존재하는 기프티콘을 구매해주세요
...
NotFound.user = 존재하지 않는 유저입니다
...
errorResponse, ExceptionResponse
Client에서 일관된 처리를 위해 Exception 메세지 또한 Errors와 동일한 형식으로 처리
항상 field, code, message 형식으로 Exception과 Errors를 Client에 전달하여 오류를 처리
@ Bean
public ErrorResponse errorResponse (MessageSource messageSource ) {
return ErrorResponse .setMessageSource (messageSource );
}
@ Bean
public ExceptionResponse exceptionResponse () {
ExceptionResponse exceptionResponse = new ExceptionResponse ();
return exceptionResponse ;
}
public class ExceptionResponse {
private Map <String , String > response = new HashMap <>();
public Map <String , String > getException (String field , String code , String message ) {
this .response .put ("field" , field );
if (field == null || field .isEmpty ()) {
this .response .put ("code" , code );
} else {
this .response .put ("code" , code + "." +field );
}
this .response .put ("message" , message );
return response ;
}
}
public ResponseEntity <Object > registerGifticon (@ Valid @ RequestBody GifticonRegisterRequest request ,
BindingResult bindingResult , @ RequestHeader HttpHeaders headers ) {
try {
...
if (userId == null ) {
throw new NotLoginedException ();
}
...
} catch (NotLoginedException e ) {
return ResponseEntity .badRequest ().body (exceptionResponse .getException (null , e .getCode (), e .getMessage ()));
} catch (NotFoundStorageException e ) {
return ResponseEntity .badRequest ().body (exceptionResponse .getException (e .getField (), e .getCode (), e .getMessage ()));
}
if (bindingResult .hasErrors ()) {
return ResponseEntity .badRequest ().body (errorResponse .getErrors (bindingResult ));
} else {
...
}
}
사용자 입력 값을 조건에 따라 검증하기 위해 Validator를 구현하여 사용
@ Component
public class GifticonAppovalValidator implements Validator {
@ Override
public boolean supports (Class <?> clazz ) {
return GifticonAppovalRequest .class .equals (clazz );
}
@ Override
public void validate (Object target , Errors errors ) {
GifticonAppovalRequest request = (GifticonAppovalRequest ) target ;
ValidationUtils .rejectIfEmptyOrWhitespace (errors , "storageId" , "NotBlank" );
ValidationUtils .rejectIfEmptyOrWhitespace (errors , "productName" , "NotBlank" );
ValidationUtils .rejectIfEmptyOrWhitespace (errors , "brandName" , "NotBlank" );
ValidationUtils .rejectIfEmptyOrWhitespace (errors , "due" , "NotBlank" );
ValidationUtils .rejectIfEmptyOrWhitespace (errors , "barcode" , "NotBlank" );
ValidationUtils .rejectIfEmptyOrWhitespace (errors , "price" , "NotNull" );
ValidationUtils .rejectIfEmptyOrWhitespace (errors , "category" , "NotSelect" );
}
}
private final GifticonAppovalValidator gifticonConfirmRegister ;
@ PostMapping ("/gifticon/confirm/register" )
public ResponseEntity <Object > registerGifticon (@ RequestBody GifticonAppovalRequest request , BindingResult bindingResult ) {
...
Boolean isConfirm = request .getIsConfirm (); //관리자 검수 Form의 승인, 거절 선택 여부
try {
if (isConfirm == null ) {
throw new NotSelectConfirmFlagException ();
}
GifticonStorageDto storageDto = gifticonStorageService .getStorageById (storageId );
if (isConfirm ) { //관리자 검수가 승인일 때
gifticonConfirmRegister .validate (request , bindingResult ); //사용자 입력 값을 검증
//기프티콘을 등록
...
}
if (!isConfirm ) { //관리자 검수가 거절일 때
//기프티콘 거절 사유를 반환
String rejectReason = request .getRejectReason ();
storageDto .setApprovalFailReason (RegistrationFailureReason .valueOf (rejectReason ));
gifticonStorageService .adminToStorage (storageDto );
}
} catch (NotSelectConfirmFlagException e ) {
bindingResult .rejectValue ("isConfirm" , e .getCode (), e .getMessage ());
} catch (NotFoundStorageException e ) {
return ResponseEntity .badRequest ().body (exceptionResponse .getException (e .getField (), e .getCode (), e .getMessage ()));
}
if (bindingResult .hasErrors ()) {
return ResponseEntity .badRequest ().body (errorResponse .getErrors (bindingResult ));
}
return ResponseEntity .ok (Collections .singletonMap ("status" , "200" ));
}