회원 가입 : 인증 메일 확인
GET "/check-email-token" token = ${token} email = ${email} 요청 처리
> 이메일이 정확하지 않은 경우에 대한 에러 처리
> 토큰이 정확하지 않은 경우에 대한 에러 처리
> 이메일과 토큰이 정확한 경우 가입 완료 처리
>> 가입 일시 설정
>> 이메일 인증 여부 true로 설정
인증 확인 뷰
> 입력값에 오류가 있는 경우 적절한 메세지 출력
> 인증이 완료된 경우, 환영 문구와 함께 몇 번째 사용자인지 보여줄 것
백엔드 로직 작성
AccountController.class
@Slf4j
@Controller
@RequiredArgsConstructor
public class AccountController {
private final SignUpFormValidator signUpFormValidator;
private final AccountService accountService;
private final AccountRepository accountRepository;
repository를 domain계층으로 보느냐 아니면 layer, controller, service, dao 이렇게 볼거냐에 따라서 논의가 있겠지만 지금은 repository를 domain(Account)으로 보겠다. (DDD)
→ 그래서 여러 군데에서 참조해도 괜찮다는 가정하에 실습 진행
AccountController.class
@Slf4j
@Controller
@RequiredArgsConstructor
public class AccountController {
private final SignUpFormValidator signUpFormValidator;
private final AccountService accountService;
private final AccountRepository accountRepository;
...
@GetMapping("/check-email-token")
public String checkEmailToken(String token, String email, Model model){
Account account = accountRepository.findByEmail(email);
String view = "account/checked-email";
if(account == null) {
model.addAttribute("error", "wrong.email");
return view;
}
if(!account.getEmailCheckToken().equals(token)){
model.addAttribute("error", "wrong.token");
return view;
}
account.setEmailVerified(true);
account.setJoinedAt(LocalDateTime.now());
model.addAttribute("numberOfUser", accountRepository.count());
model.addAttribute("nickname", account.getNickname());
return view;
}
}
뷰 작성
checked-email.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Dot Study</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
<style>
.container{
max-width: 100%;
}
</style>
</head>
<body class="bg-light">
<nav th:fragment="main-nav" class="navbar navbar-expand-sm navbar-dark bg-dark">
<a class="navbar-brand" href="/" th:href="@{/}">
<img src="/images/logo.png" width="30" height="30">
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<form th:action="@{/search/study}" class="form-inline" method="get">
<input class="form-control mr-sm-2" name="keyword" type="search" placeholder="스터디 찾기" aria-label="Search" />
</form>
</li>
</ul>
<ul class="navbar-nav justify-content-end">
<li class="nav-item" >
<a class="nav-link" th:href="@{/login}">로그인</a>
</li>
<li class="nav-item" >
<a class="nav-link" th:href="@{/sign-up}">가입</a>
</li>
</ul>
</div>
</nav>
<div class = "py-5 text-center" th:if="${error}">
<p class="lead">닷스터디 이메일 확인</p>
<div class="alert alert-danger" role="alert">
이메일 확인 링크가 정확하지 않습니다.
</div>
</div>
<div class = "py-5 text-center" th:if="${error == null}">
<p class="lead">닷스터디 이메일 확인</p>
<h2>
이메일을 확인했습니다. <span th:text="${numberOfUser}">10</span>번째 회원,
<span th:text="${nickname}">이종원</span>님 가입을 축하합니다.
</h2>
<small class="text-info">이제부터 가입할 때 사용한 이메일 또는 닉네임과 패스워드로 로그인 할 수 있습니다.</small>
</div>
</body>
</html>
getEmailCheckToken : null값 에러 발생
@Service
@RequiredArgsConstructor
public class AccountService {
private final AccountRepository accountRepository;
private final JavaMailSender javaMailSender;
private final PasswordEncoder passwordEncoder;
@Transactional
public void processNewAccount(SignUpForm signUpForm) {
Account newAccount = saveNewAccount(signUpForm);
//Null값 에러 발생
newAccount.generateEmailCheckToken();
sendSignUpConfirmEmail(newAccount);
}
private Account saveNewAccount(@Valid SignUpForm signUpForm) {
Account account = Account.builder()
.email(signUpForm.getEmail())
.nickname(signUpForm.getNickname())
.password(passwordEncoder.encode(signUpForm.getPassword()))
.studyCreatedByWeb(true)
.studyEnrollmentResultByWeb(true)
.studyUpdatedByWeb(true)
.build();
return accountRepository.save(account);
}
private void sendSignUpConfirmEmail(Account newAccount) {
//이메일 전송
SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setTo(newAccount.getEmail());
mailMessage.setSubject("닷 스터디, 회원 가입 인증");
mailMessage.setText("/check-email-token?token="+ newAccount.getEmailCheckToken() +
"&email=" + newAccount.getEmail());
javaMailSender.send(mailMessage);
}
}
saveNewAccount메소드 안에서만 accountRepository.save()로 Transaction이 일어났기 때문에 해당하는 엔티티는 persist상태
그러나 그 메소드를 벗어난 상태에서는 processNewAccount메소드에 있는 newAccount에서는 detached상태이다.
(왜냐? save를 벗어났기 때문에)
따라서, processNewAccount메소드에 @Transactional을 명시해주어야 한다.
회원가입 후 해당 토큰을 복붙하면 끝
결과화면
출처
인프런 강의 - 스프링과 JPA 기반 웹 애플리케이션 개발
'Dot Programming > Spring Clone' 카테고리의 다른 글
[스프링 웹앱 프로젝트 #11]회원 가입 완료 후 자동 로그인 (0) | 2020.11.26 |
---|---|
[스프링 웹앱 프로젝트 #10]회원 가입 인증 메일 확인 테스트 및 리팩토링 (0) | 2020.11.19 |
[스프링 웹앱 프로젝트 #8]회원 가입 패스워드 인코딩 (0) | 2020.11.19 |
[스프링 웹앱 프로젝트 #7]회원가입 : 리팩토링 및 테스트 (0) | 2020.11.18 |
[스프링 웹앱 프로젝트 #6]회원가입 폼 서브밋 처리 (0) | 2020.11.05 |