사랑하애오
article thumbnail

1. 회원가입 로직

1. 회원 역할(Role) 구분

  • com.shop.constant.Role.java, enum 클래스 생성
  • 일반 유저 / 관리자 
<java />
package com.shop.constant; public enum Role { USER, ADMIN }




2. 회원 가입 정보 DTO

  • 회원 가입 화면으로 부터 넘어오는 가입정보를 담을 DTO 객체
<java />
@Getter @Setter public class MemberFormDto { private String name; private String emial; private String password; private String address; }




3. Member Entity

  • 회원 정보를 저장하는 Member Entity(Model) 생성
  • Member 객체를 생성하기 위해 Member 객체 안에 createMember() static 메소드 생성
<java />
@Entity @Table(name = "member") @Getter @Setter @ToString public class Member { @Id @Column(name = "member_id") @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; @Column(unique = true) private String email; private String password; private String address; @Enumerated(EnumType.STRING) private Role role; public static Member createMember(MemberFormDto memberFormDto, PasswordEncoder passwordEncoder) { Member member = new Member(); member.setName(memberFormDto.getName()); member.setEmail(memberFormDto.getEmail()); member.setAddress(memberFormDto.getAddress()); String password = passwordEncoder.encode(memberFormDto.getPassword()); member.setPassword(password); member.setRole(Role.USER); return member; } }




4. MemberRepository

  • 회원가입 시 중복 여부를 판단하기 위해서 이메일로 회원을 검사하도록 쿼리 메소드 작성
<java />
public interface MemberRepository extends JpaRepository<Member, Long> { Member findByEmail(String email); }




5. MemberService

  • @RequiredArgsConstructor 를 사용하여 memberRepository DI
<java />
@Service @Transactional @RequiredArgsConstructor public class MemberService { private final MemberRepository memberRepository; public Member saveMember(Member member) { validateDuplicateMember(member); return memberRepository.save(member); } public void validateDuplicateMember(Member member) { Member findMember = memberRepository.findByEmail(member.getEmail()); if (findMember != null) { throw new IllegalStateException("이미 가입된 회원입니다.); } } }




6. 회원가입 기능 테스트

  • 테스트 클래스 생성 및 DI
<java />
@SpringBootTest @Transactional @TestPropertySource(locations = "classpath:application-test.properties") class MemberServiceTest { @Autowired MemberSerive memberService; @Autowired PasswordEncoder passwordEncoder; // Controller에서 POST 입력 값을 기반으로 memberFormDto 객체를 생성할 예정 public Member createMember() { MemberFormDto memberFormDto = new MemberFormDto; memberFormDto.setEmail("test@email.com"); memberFormDto.setName("test"); memberFormDto.setAddress("서울시 중랑구 중화동") memberFormDto.setPassword("1234") return Member.createMember(memberFormDto, passwordEncoder); }
  • 회원가입 테스트
  • 회원가입 DTO 객체를 Service 객체를 통해서 저장(save)
<java />
@Test @DisplayName("회원가입 테스트") public void saveMemberTest() { Member member = creatMember(); Member saveMember = memberService.saveMember(member); assertEquals(member.getEmail(), savedMember.getEmail()); assertEquals(member.getEmail(), savedMember.getEmail()); assertEquals(member.getName(), savedMember.getName()); assertEquals(member.getAddress(), savedMember.getAddress()); assertEquals(member.getPassword(), savedMember.getPassword()); assertEquals(member.getRole(), savedMember.getRole()); }
  • 회원가입 중복 테스트
<java />
@Test @DisplayName("중복 회원가입 테스트") void validateDuplicateMember() { // given Member member1 = createMember(); Member member2 = createMember(); memberService.saveMember(member1); // when Throwable e = assertThrows(IllegalStateException.class, () -> { memberService.saveMember(member2); }); // then assertEquals("이미 가입된 회원입니다.", e.getMessage()); }
  • 테스트 결과

 


 

2. 회원가입 페이지

1. MemberController

  • Get 요청으로 회원가입 페이지를 요청할 경우 MemberFormDto 객체를 같이 넘김
  • POST 요청으로 넘어온 회원가입 정보를 MemberFormDto 객체로 받음
<java />
@RequestMapping("/members") @Controller @RequiredArgsConstructor public class MemberController { private final MemberService memberService; private final PasswordEncoder passwordEncoder; @GetMapping(value = "/new") public String memberForm(Model model) { model.addAttribute("memberFormDto", new MemberFormDto()); return "member/memberForm"; } @PostMapping(value = "/new") public String memberForm(MeberFormDto memberFormDto) { Member member = Member.createMember(memberFormDto, passwordEncoder); memberService.saveMember(member); return "redirect:/"; } }




2. 회원 가입 페이지

  • th:object - form submit 할 때, form 의 데이터가 th:object 객체에 매핑되어 보내짐
  • th: field - 해당 필드를 th:object 객체 내부 필드와 매핑, "*{}" 형식으로 th:object 참조
  • th:if="${#fields.hasErrors('필드명')}" - 해당 필드에 에러가 있는 경우
  • th:errors - 해당 값에 에러가 있는 경우 출력
  • csrf 토큰 전송
<code />
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layouts/layout1}"> <!-- 사용자 CSS 추가--> <th:block layout:fragment="css"> <style> .fieldError { color: #bd2130; } </style> </th:block> <!-- 사용자 스크립트 추가--> <th:block layout:fragment="script"> <script th:inline="javascript"> $(document).ready(function(){ var errorMessage = [[${errorMessage}]]; if(errorMessage != null){ alert(errorMessage); } }); </script> </th:block> <div layout:fragment="content"> <form action="/members/new" role="form" method="post" th:object="${memberFormDto}"> <div class="form-group"> <label th:for="name">이름</label> <input type="text" th:field="*{name}" class="form-control" placeholder="이름을 입력해주세요"> <p th:if="${#fields.hasErrors('name')}" th:errors="*{name}" class="fieldError">Incorrect data</p> </div> <div class="form-group"> <label th:for="email">이메일주소</label> <input type="email" th:field="*{email}" class="form-control" placeholder="이메일을 입력해주세요"> <p th:if="${#fields.hasErrors('email')}" th:errors="*{email}" class="fieldError">Incorrect data</p> </div> <div class="form-group"> <label th:for="password">비밀번호</label> <input type="password" th:field="*{password}" class="form-control" placeholder="비밀번호 입력"> <p th:if="${#fields.hasErrors('password')}" th:errors="*{password}" class="fieldError">Incorrect data</p> </div> <div class="form-group"> <label th:for="address">주소</label> <input type="text" th:field="*{address}" class="form-control" placeholder="주소를 입력해주세요"> <p th:if="${#fields.hasErrors('address')}" th:errors="*{address}" class="fieldError">Incorrect data</p> </div> <div style="text-align: center"> <button type="submit" class="btn btn-primary" style="">Submit</button> </div> <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"> </form> </div> </html>




3. 회원 가입 페이지 화면

 


 

3. 회원가입 검증

1. validation 의존성 추가

  • 서버로 넘어오는 값을 검증하기 위한 validation 라이브러리 추가
<code />
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>




2. validation 어노테이션

어노테이션설명

@NotEmpty NULL 체크 및 문자열의 경우 길이 0인지 검사
@NotBlank NULL 체크 및 문자열의 경우 길이 0 및 빈 문자열(" ") 검사
@Length 최소, 최대 길이 검사
@Email 이메일 형식인지 검사
@Max 지정한 값보다 작은지 검사
@Min 지정한 값보다 큰지 검사
@Null 값이 Null인지 검사
@NotNull 값이 Null이 아닌지 검사




3. 회원가입 DTO 유효성 검증

<java />
@Getter @Setter public class MemberFormDto { @NotBlank(message = "이름은 필수 입력 값입니다.") private String name; @NotBlank(message = "이메일은 필수 입력 값입니다.") @Email(message = "이메일 형식으로 입력해주세요.") private String email; @NotBlank(message = "비밀번호는 필수 입력 값입니다.") @Length(min = 8, max = 16, message = "비밀번호는8자 이상, 16자 이하로 입력해주세요") private String password; @NotBlank(message = "주소는 필수 입력 값입니다.") private String address; }




4. 유효성 검증 결과에 따른 Controller

  • 검증하려는 객체 앞에 @Value 어노테이션 지정
  • 검증 결과를 받을 BindingResult 매개변수 추가
  • 입력한 회원가입 데이터에 문제가 있으면 다시 회원가입 페이지로 돌아감
  • 중복된 회원가입이면 지정된 예외가 발생하고 해당 에러 메시지를 다시 회원가입 페이지로 넘김
  • 문제가 없으면 메인 페이지로 redirect
<java />
@PostMapping("/new") public String newMember(@Valid JoinFormDto joinFormDto, BindingResult bindingResult, Model model) { if (bindingResult.hasErrors()) { return "member/memberForm"; } try { Member member = Member.createMember(memberFormDto, passwordEncoder); memberService.saveMember(member); } catch (Exception e) { model.addAttribute("errorMessage", e.getMessage()); return "member/memberForm"; } return "redirect:/"; }




5. 유효성 검증 결과

  • 정상적인 경우 (비밀번호 암호화 적용됨)


  • 입력한 데이터에 문제가 있는 경우

 

  • 중복된 회원가입인 경우
profile

사랑하애오

@사랑하애

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!