계정관련 서비스를 따로 분리하는 작업을 하면서 처음 만들 때보다 시간이 흘렀으니 더 나은 방식으로 코드를 짜보고 싶기도하고 계정 쪽은 처음이라 프론트 쪽만 가져오면서 다시 짜보고 있다.
지금까지 거의 게시판 위주의 작업만 해봤고 계정관련 서비스는 처음이었다. 게시판 쪽을 다룰 땐 최대 크기, xss 방어코드 정도만 생각했던 것 같은데 (물론 내가 모르는 부분이 더 많겠지만) 계정은 유효성 검증이 필수적이고 매우 중요한 부분이었다. (Spring Security는 나중에 도전😶)
유저 쪽을 맡았던 친구의 코드를 다시 살펴보면서
@Valid
어노테이션을 사용해서 @Requestbody
로 들어온 객체의 유효성 검증을 하고 있다는 것@NotBlank
@Size
Pattern
등을 사용MethodArgumentNotValidException
을 발생시킨다는 것@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable{
private static final long serialVersionUID = 1L;
@NotBlank(message = "아이디를 입력해주세요.")
@Size(min = 4, max = 30, message = "아이디는 4글자에서 30글자 사이로 입력해주세요.")
@Pattern(regexp = "^([a-z가-힣0-9]){4,30}$", message = "대문자, 특수문자는 입력할 수 없습니다.")
private String userId;
@NotBlank(message = "비밀번호를 입력해주세요.")
@Size(min = 8, max = 20, message = "최소 8자에서 최대 20자까지 입력 가능합니다.")
@Pattern(regexp="(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,20}", message = "영문, 숫자, 특수문자가 적어도 1개 이상 조합되게 입력해주세요.")
private String password;
@Email(message = "이메일 형식에 맞게 입력해주세요.")
@NotBlank(message = "이메일을 입력해주세요.")
private String email;
이런 내용들을 알게되었고 구글링을 통해
MethodArgumentNotValidException
에 대한 에러 내용들을 깔끔하게 커스텀해서 사용하는 법을 알게 되었다. @ControllerAdvice
와 @ExceptionHandler
를 사용하여 error와 함께 body에 메세지를 담아 보내게 설정했다. function checkUserId(){
var userId = $('#userId').val();
var userIdLength = $('#userId').val().length;
$.ajax({
url:'/users/check/id',
type:'post',
data:{userId:userId},
success:function(cnt){
if(4 > userIdLength) {
$('.userId_fail').css("display", "inline-block");
$('.userId_success').css("display", "none");
$('.userId_fail').text("너무 짧습니다.");
}
if(userIdLength > 30) {
$('.userId_fail').css("display", "inline-block");
$('.userId_success').css("display", "none");
$('.userId_fail').text("너무 깁니다.");
}
if(cnt!=0){
$('.userId_fail').css("display", "inline-block");
$('.userId_success').css("display", "none");
$('.userId_fail').text("중복된 아이디입니다.");
}
if(idCheck.test(userId) === false) {
$('.userId_fail').css("display", "inline-block");
$('.userId_success').css("display", "none");
$('.userId_fail').text("형식에 맞게 작성해주세요.");
}
if(cnt == 0 && 3 < userIdLength && userIdLength < 31 && idCheck.test(userId) === true) {
$('.userId_success').css("display", "inline-block");
$('.userId_fail').css("display", "none");
}
},
error:function(){
}
.
.
클라이언트 쪽에서 한번, 서버 쪽에서 한번. 총 2번 검사하는 건 OK.
서버 쪽 메세지를 body에 담았으나 그 메세지를 활용하지 못해서 클라이언트에게 전달해주지 못함.
메세지를 javascript로 일일이 다 설정해주어야함.
🔍 해결하고 싶은 것 : dto에 설정한 메세지를 활용할 수 있으면 좋겠다.
아까 @ControllerAdvice
와 @ExceptionHandler
를 사용하여 dto에 설정해둔 메세지를 body에 담아보냈다. 이걸 활용하는 방법은
error : function(xhr){
alert(xhr.responseText);
}
이런 식으로 써주면 모두 d라는 값을 입력해서 예외를 발생시킬 때
이렇게 뜬다.
❗❗❗ 개인적인 생각 ( 확실하지 않음) ❗❗❗
responseText는 body에 String으로 보냈을 때 사용하는 것 같고
error:function(xhr){
alert(xhr.responseJSON.message);
}
이런 식으로 사용하면 JSON 객체를 쓸 수 있는 것 같다.
지금 중복확인하는 것을 DB에서 값이 있는지 (0개인지, 1개인지) 확인해서 count 값을 넘겨주고 그 값을 확인해서 처리를 하는데, 그렇게 하는 것보다 에러를 던져주는 것이 효율적일 것 같다는 코드 리뷰를 받았다.
@Service
@RequiredArgsConstructor
public class DuplicateChecker {
private final UserMapper userMapper;
@Transactional
public int checkDuplicateId(String userId) {
int count = userMapper.checkDuplicateId(userId);
return count;
}
원래는 아이디 개수를 리턴해주었지만 우리는 아이디 중복이라는 상황을 만나면 의도적으로 예외를 발생시켜 막으려고 한다. 이 때 사용할 수 있는 것이 RuntimeException
이다.
RuntimeException
은 unchecked exception으로 말 그대로 실행 중에 발생하며 의도적으로 개발자가 잡아내기 위한 조건 등에 부합할 때 발생 시키도록 만들 수 있다.
RuntimeException
을 그대로 사용할 수도 있지만 좀 더 세밀하게 사용하기 위해 AccountApiException
이라는 추상화 클래스를 사용하여 여러 상황에 부합한 메세지를 전달할 수 있도록 작성했다.
추상클래스를 사용하면
AccountApiException
을 상속하는 모든 exception들은 메세지를 갖고 있도록 설정했다.@Getter
public abstract class AccountApiException extends RuntimeException {
private final String message;
protected AccountApiException(String message) {
this.message = message;
}
}
⭐ 상속을 통해 같은 기능이지만 다른 결과를 도출하기 ---> 다형성
AccountApiException
을 상속 받아서 오버라이딩하여 메세지만 넣어주면 된다.@ResponseStatus(HttpStatus.BAD_REQUEST)
public class DuplicatedIdException extends AccountApiException {
public DuplicatedIdException() {
super("중복된 아이디입니다.");
}
}
DuplicatedIdException
이 발생, super를 사용했기 때문에 부모 생성자에 DuplicatedIdException
의 내용이 매개변수로 설정됨 if(count > 0)
throw new DuplicatedIdException();
@RestControllerAdvice
에 @ExceptionHandler
를 사용한다.
super()
를 통해 DuplicatedIdException
의 message를 AccountApiException
message에 넣어서ErrorDto
에 getMessage() 메서드를 사용하여 리턴한다.AccountApiException
이 발생하면 BAD_REQUEST
400 에러로 뿌려준다.@RestControllerAdvice
@RestController
public class ExceptionController {
.
.
.
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(AccountApiException.class)
public ErrorDto invalidIdError(AccountApiException ex) {
return new ErrorDto(ex.getMessage());
}
}
이렇게 하면 아이디가 중복되었을 때 예외 발생하여 메세지를 클라이언트에 전달할 수 있다.
error:function(xhr){
alert(xhr.responseJSON.message);
}
https://comboong.com/entry/JAVA-Java-%EC%83%81%EC%86%8D%EA%B3%BC-Protected-%EC%A0%91%EA%B7%BC%EC%A0%9C%EC%96%B4%EC%9E%90
https://programmers.co.kr/learn/courses/5/lessons/192