사랑하애오
article thumbnail

회원가입 로직

1. 회원 역할(Role) 구분

  • com.shop.constant.Role.java, enum 클래스 생성
  • 일반 유저 / 관리자 
package com.shop.constant;

public enum Role {
	USER, ADMIN
}




2. 회원 가입 정보 DTO

  • 회원 가입 화면으로 부터 넘어오는 가입정보를 담을 DTO 객체
@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 메소드 생성
@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

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




5. MemberService

  • @RequiredArgsConstructor 를 사용하여 memberRepository DI
@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
@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)
@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());
}
  • 회원가입 중복 테스트
@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());
}
  • 테스트 결과

 


 

회원가입 페이지

1. MemberController

  • Get 요청으로 회원가입 페이지를 요청할 경우 MemberFormDto 객체를 같이 넘김
  • POST 요청으로 넘어온 회원가입 정보를 MemberFormDto 객체로 받음
@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 토큰 전송
<!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. 회원 가입 페이지 화면

 


 

회원가입 검증

1. validation 의존성 추가

  • 서버로 넘어오는 값을 검증하기 위한 validation 라이브러리 추가
<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 유효성 검증

@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
@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

사랑하애오

@사랑하애

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