그동안 여러모로 바빴당..
우선 팀을 이루어 프로젝트를 진행하게 됐다. 이 프로젝트도 진행하고 팀 프로젝트도 진행하려면 지금처럼 얕게 스프링부트 몇 번 만져본 정도로는 안되겠다고 느꼈다.
그래서 부랴부랴 김영한님의 스프링부트 강의를 듣고 있고, mvc까지 들으니 비로소 그동안 왜 이 기능을 이렇게 썼는지, 타임리프가 어떻게 작동하는지가 틀이 보이기 시작했다.
아무래도 복붙만 하기 급급했던 예전과 달리, 조금이라도 원리를 알고 있는 상태에서 다시 개발을 시작하는게 좋을 거 같아 잠시 개발을 중단했었다.
아직 강의는 한참 많이 남았다. 특히 제일 중요한 mvc2 - 예외처리부분까지 들어야 제대로된 예외처리를 할 수 있을 거 같아서 아직 예외처리는 진행하지 않았다.
다만 Validation부분과 타임리프의 코드를 조금 리팩토링하고, 하는 김에 패키지 구조도 깔끔하게 정리했다.
전에는 엔티티별로 user, post, favorite.. 등등으로 먼저 나누고, 각 패키지 안에 domain-dto-repository-service-controller를 나누었다.
근데 개발하다 보니깐 역으로 domain-dto-repository-service-controller로 먼저 패키지를 나누고 그 안에 엔티티별로 나누는게 더 편한거 같아서 바꿔보았당.
UserController
수정 전
/** 이메일 회원가입
* [POST] /user/join/email
* @Body emailRequestDto : nickname,email, password
*/
@PostMapping("/user/join/email")
public String joinByEmail(@Valid EmailJoinRequestDto emailJoinRequestDto, BindingResult bindingResult, Model model){
if(bindingResult.hasErrors())
return "join";
if(userService.isDuplicatedEmail(emailJoinRequestDto.getEmail())){
model.addAttribute("msg","중복된 이메일입니다.");
return "join";
}
// 이메일 인증 실패 시
if(!emailAuthService.verifyAuthCode(emailJoinRequestDto.getEmail(), emailJoinRequestDto.getCode())) {
model.addAttribute("msg", "이메일 인증에 실패하였습니다.");
return "join";
}
userService.emailSignUp(emailJoinRequestDto);
return "redirect:/login";
}
수정 후
@PostMapping("/user/join/email")
public String joinByEmail(@Valid EmailJoinRequestDto emailJoinRequestDto, BindingResult bindingResult, Model model){
// 필드 에러
if(bindingResult.hasErrors()) {
log.info("errors={}", bindingResult);
return "join";
}
// 글로벌 에러
if(userService.isDuplicatedEmail(emailJoinRequestDto.getEmail())){
bindingResult.reject("duplicated");
return "join";
}
// 이메일 인증 실패 시
if(!emailAuthService.verifyAuthCode(emailJoinRequestDto.getEmail(), emailJoinRequestDto.getCode())) {
bindingResult.reject("failedAuthentication");
return "join";
}
userService.emailSignUp(emailJoinRequestDto);
return "redirect:/login";
}
이전에는 model에 오류 메세지를 직접 적어서 담고, 메세지를 타임리프에서 보여주는 방식이었다.
김영한님의 강의를 들으면서 BindingResult를 좀 더 잘 사용하는 방법을 알게 됐다. 에러 메세지 코드를 작성하고 이를 bindingResult.reject()를 통해 넣었다. 글로벌 오류 메세지를 작성할 때는 reject를 사용한다.
이렇게 하면 개발자가 직접 fieldError 객체를 생성할 필요 없이 fieldError 객체를 생성해주고 fieldError에 입력했던 값들과 에러메세지가 담기면서 타임리프에 전달된다.
join.html
수정 전
<div class="form-group">
<label class="h5" th:for="nickname">닉네임 </label>
<input type="text" th:field="*{nickname}" class="form-control" placeholder="닉네임을 입력하세요."
th:class="${#fields.hasErrors('nickname')}? 'form-control fieldError' : 'form-control'"/>
<p class="error-message" th:if="${#fields.hasErrors('nickname')}" th:errors="*{nickname}">INPUT ERROR</p>
</div>
수정 후
<div th:if="${#fields.hasGlobalErrors()}">
<p class="field-error" th:each="err : ${#fields.globalErrors()}" th:text="${err}">전체 오류 메시지</p>
</div>
<div class="form-group">
<label class="h5" th:for="nickname">닉네임 </label>
<input type="text" th:field="*{nickname}" class="form-control" placeholder="닉네임을 입력하세요." th:errorclass="field-error"/>
<div class="fieldError" th:errors="*{nickname}">
닉네임 오류
</div>
</div>
전에는 막 th:if를 사용해서 해당 에러가 존재하면 fielderror 클래스를 추가하고..이런 복잡한 방식으로 타임리프를 구현했다.
th:errorclass와 th:errors를 사용해서 깔끔하게 코드를 단축시켰다.
/resources/errors.properties
#==ObjectError==
#LEVEL 2
#User
duplicated = 중복된 이메일입니다.
failedAuthentication =인증코드가 일치하지 않습니다.
failedLogin = 이메일과 비밀번호를 확인해주세요.
#==FieldError==
#LEVEL 2
#User
NotBlank.email=이메일을 입력해주세요.
NotBlank.password=비밀번호를 입력해주세요.
Size.nickname=닉네임은 최소 두 글자 이상입니다.
#LEVEL 4
typeMismatch.java.lang.Integer=숫자를 입력해주세요.
typeMismatch=타입 오류입니다.
NotBlank=공백을 입력할 수 없습니다.
Range={0}, {2} ~ {1} 사이의 값을 입력해주세요.
Max={0}, 최대 {1} 까지 입력할 수 있습니다.
errors.properties를 추가해서 에러 메세지 코드를 작성했다.
여기서 ObjectError는 글로벌 에러 느낌이고, FieldError는 각 필드와 관련된 에러이다.
application.properties
#메세지, 검증
spring.messages.basename=messages,errors
이거를 넣어줘야 인식할 수 있다.
EmailJoinRequestDto.java
public class EmailJoinRequestDto {
@NotBlank
@Email
private String email;
@NotBlank
@Pattern(regexp = "[a-zA-Z1-9]{8,20}",
message = "비밀번호는 영어와 숫자를 포함해서 8~20자리 이내로 입력해주세요")
private String password;
@NotBlank(message = "인증코드를 입력해주세요.")
private String code;
@Size(min=2)
private String nickname;
private String profileImage;
@Builder
public User toEntity(){
return User.builder()
.email(email)
.nickname(nickname)
.password(password)
.role(Role.ROLE_USER)
.userType(UserType.EMAIL)
.build();
}
}
이렇게 해주면 됩니당.
흠 아직 예외처리 부분을 강의를 안 들어서 잘 모르겠지만..아무래도 인증코드 에러나 중복된 메세지 부분은 나중에 예외처리하게 되면서 사라지지 않을까 생각도 든다.