Spring Projcect/[팀플] In & Out 가계부

회원 이메일 인증 API

계란💕 2022. 10. 28. 14:22

 

  • MailComponent
    • JavaMailSender를 이용하기 위한 Component 클래스를 만든다.
    • @Component 애너테이션을 붙이지 않으면 아래와 같은 오류가 난다. 따로 클래스를 만들고 빈 등록을 꼭 해줘야한다.
<hide/>
Parameter 3 of constructor in com.rezero.inandout.member.service.MemberServiceImpl required a bean of type 'com.rezero.inandout.member.model.MailComponent' that could not be found.

 

 

  • mail
    • Mime: 단순 텍스트 뿐만 아니라 다른 여러 바이너리 파일을 메일에 첨부할 때 쓰인다.
    •  MimeMessage는 디테일하게 메일 전송 가능 <=> SimpleMailMessage는 단순 텍스트만 전송 가능하다.
    •  MimeMessagePreparator메시지 준비를 위해서 필요한 콜백 인터페이스를 제공한다. 
    •  MimeMessageHelper: 메시지 생성을 위해 쓰이는 클래스 (이미지, 파일, 텍스트 등을 html 형식으로 제공)
    • mail 전송에 실패할 수도 있으니 예외처리를 한다.
    • html 형태로 텍스트를 구성하기 위해서 setText()에 true를 꼭 넣어줘야한다.
    • 안 넣어주면 그냥 쌩 텍스트로 이메일이 전송된다.
<hide/>
@Component
@RequiredArgsConstructor
public class MailComponent {

    private final JavaMailSender javaMailSender;
    
    public void send(String to, String subject, String text) {

        MimeMessagePreparator msg = new MimeMessagePreparator() {
            @Override
            public void prepare(MimeMessage mimeMessage) throws Exception {
                MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true,
                    "UTF-8");
                mimeMessageHelper.setTo(to);
                mimeMessageHelper.setSubject(subject);
                mimeMessageHelper.setText(text, true);
            }
        };
        try {
            javaMailSender.send(msg);

        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}

 

 

  • service
    • ex) http://localhost:8080/api/signup/sending?id=fe471ac7-911c-4267-923f-aa9699bd7418
<hide/>
@Override
public void join(JoinMemberInput input) {

    validateInput(input);
    String password = input.getPassword();
    String encPassword = bCryptPasswordEncoder.encode(password);
    String uuid = UUID.randomUUID().toString();

    System.out.println("uuid " + uuid);

    Member member = Member.builder().email(input.getEmail()).address(input.getAddress())
        .birth(input.getBirth()).gender(input.getGender()).password(encPassword)
        .nickName(input.getNickName()).phone(input.getPhone()).status(MemberStatus.REQ)
        .emailAuthKey(uuid).build();
    memberRepository.save(member);

    String subject = "In and Out 회원 가입을 축하드립니다.";
    String text = "<p>안녕하세요. In And Out 입니다.</p><p>아래 링크를 누르시면 회원 가입이 완료됩니다.</p>"
        + "<div><a href='http://localhost:8080/api/signup/sending?id=" + uuid
        + "'>가입 완료</a></div>";

    mailComponent.send(input.getEmail(), subject, text);

}

@Override
public void emailAuth(String uuid) {

    Optional<Member> optionalMember = memberRepository.findByEmailAuthKey(uuid);
    if (!optionalMember.isPresent()) {
        throw new MemberException(EMAIL_AUTH_KEY_NOT_EXIST);
    }
    Member member = optionalMember.get();
    member.setStatus(MemberStatus.ING);
    memberRepository.save(member);
}

 

 

  • controller
    • 링크 ex) http://localhost:8080/api/signup/sending?id=3cf81017-e796-4e65-a7ef-f9c820803873
    • url에서 파라미터인 uuid를 가져와서 emailAuth()에 적용한다. 
    • 생각해보니까 이메일 인증 URL을 띄우면 MemberRepository에서 해당 멤버의 MemberStatus가 "REQ" => "ING"로 활성화된다.
    • 따라서, 이는 클라이언트 => 서버에 데이터를 전송해주는 것에 가깝고 이는 GetMapping 보다는 PostMapping에 가깝다.
    • GetMapping과 PostMapping의 차이는?
      • Get: SELECT 기능에서 우수함
      • Post:UPDATE, CREATE, DELETE 기능으로 사용한다.
<hide/>
@PostMapping("/signup")
@ApiOperation(value = "회원 가입 API", notes = "이메일을 아이디로 사용하여 가입할 수 있다.")
public ResponseEntity<?> signUp(
    @ApiParam(value = "회원 가입 정보 입력") @RequestBody JoinMemberInput memberInput) {
    memberService.join(memberInput);
    String message = "이메일 인증을 하시면 회원가입이 완료됩니다.";
    return new ResponseEntity(message, HttpStatus.OK);
}

@PostMapping("/signup/sending")
@ApiOperation(value = "회원 가입을 위한 이메일 인증 API", notes = "이메일을 인증하여 회원 가입 가능하다.")
public ResponseEntity<?> emailAuth(HttpServletRequest request) {
    String uuid = request.getParameter("id");
    memberService.emailAuth(uuid);
    String message = "회원가입이 완료되었습니다.";
    return new ResponseEntity(message, HttpStatus.OK);
}

 

 

  • test
<hide/>
@Test
@DisplayName("회원 가입을 위한 이메일 인증")
void emailAuth() throws Exception {

    // given
    String uuid = UUID.randomUUID().toString();

    // when
    mockMvc.perform(
            post("/api/signup/sending?id=" + uuid)
                .contentType(MediaType.APPLICATION_JSON))
        .andExpect(status().isOk()).andDo(print());
    ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);

    // then
    Mockito.verify(memberServiceImpl, times(1)).emailAuth(captor.capture());
    assertEquals(uuid, captor.getValue());

}

 

 

  • test
    • uuid가 존재하지 않는 경우 잘못된 url이므로 예외처리한다.
<hide/>
@Test
@DisplayName("회원가입을 위한 이메일 인증 - 성공")
void emailAuth() {

    // given
    String uuid = UUID.randomUUID().toString();
    Member member = Member.builder()
        .email("egg@naver.com")
        .password("abc123!@")
        .emailAuthKey(uuid)
        .status(MemberStatus.REQ)
        .build();
    given(memberRepository.findByEmailAuthKey(anyString())).willReturn(Optional.of(member));

    // when
    memberService.emailAuth(uuid);

    // then
    assertEquals(MemberStatus.ING, member.getStatus());
}

@Test
@DisplayName("회원가입을 위한 이메일 인증 - 실패")
void emailAuth_fail() {

    // given
    String uuid = UUID.randomUUID().toString();
    given(memberRepository.findByEmailAuthKey(anyString())).willReturn(Optional.empty());

    // when
    MemberException exception = assertThrows(MemberException.class,
        () -> memberService.emailAuth(uuid));

    // then
    assertEquals(MemberErrorCode.EMAIL_AUTH_KEY_NOT_EXIST, exception.getErrorCode());

}

 

'Spring Projcect > [팀플] In & Out 가계부' 카테고리의 다른 글

회원 비밀번호 초기화 API  (0) 2022.10.31
회원 탈퇴 API  (0) 2022.10.30
회원 가입 API  (0) 2022.10.27
회원 로그아웃 API  (0) 2022.10.26
회원 예외 처리 MemberErrorCode, ExceptionHandler  (0) 2022.10.25