회원가입 기능 구현 2

루민 ·2023년 3월 30일
0
post-thumbnail

📖MemberController

1.GetMapping("/members/new")

@Controller
@Slf4j
@RequiredArgsConstructor
public class MemberController {

    private final MemberService memberService;

    /**
     * 회원가입
     */
    @GetMapping("/members/new")
    public String createMemberForm(@ModelAttribute("memberForm") MemberForm memberForm, Model model) {
        List<RoleCode> roleCodes = new ArrayList<>();
        roleCodes.add(new RoleCode("admin", "판매자"));
        roleCodes.add(new RoleCode("user", "구매자"));
        model.addAttribute("roleCodes", roleCodes);

        return "members/createMemberForm";
    }

    @Data
    @AllArgsConstructor
    static class RoleCode {
        private String code;
        private String displayName;
    }

}
  • RequiredArgsConstructor 어노테이션을 사용하여 meberService 의존 관계 주입(Dependency Injection)을 해주었습니다.
  • 판매자, 관리자 역할을 셀렉트 박스로 정하게 하려고 RoleCode 클래스를 하나 만들어서 roleCodes를 모델을 통해 뷰로 전달해 주었습니다.
  • MemberForm(DTO) 또한 모델을 통해 뷰로 전달해 주었습니다. (@ModelAttribute 어노테이션은 자동으로 model.addAttribute를 수행해 줍니다.)
@Getter
@Setter
public class MemberForm {

    @NotEmpty(message = "이름은 필수입니다.")
    private String name;
    @NotEmpty(message = "이메일은 필수입니다.")
    @Email  //이메일 형식 validation
    private String email;
    @NotEmpty(message = "비밀번호는 필수입니다.")
    private String password;

    private String city;
    private String street;
    private String zipcode;

    private String role;

}
  • @NotEmpty, @Email, @Length, @Max 등 validation 어노테이션을 통해 좀 더 간편하게 유효성 검증을 할 수 있습니다.


2.postMapping("members/new")

@PostMapping("/members/new")
    public String createMember(@Valid @ModelAttribute MemberForm memberForm, BindingResult bindingResult, Model model,
                               @RequestParam("role") String role) {

        //이름, 이메일, 패스워드 중 하나라도 입력을 안했을 시
        if (bindingResult.hasErrors()) {
            List<RoleCode> roleCodes = new ArrayList<>();
            roleCodes.add(new RoleCode("admin", "판매자"));
            roleCodes.add(new RoleCode("user", "구매자"));
            model.addAttribute("roleCodes", roleCodes);
            return "/members/createMemberForm";
        }

        Address address = new Address(memberForm.getCity(), memberForm.getStreet(), memberForm.getZipcode());

        try {
            Member member = Member.builder()
                    .name(memberForm.getName())
                    .email(memberForm.getEmail())
                    .password(memberForm.getPassword())
                    .address(address)
                    .build();
                    
            member.changeRole(role);  //사용자에게 권한 설정(판매자 또는 구매자)
            memberService.join(member);
        } catch (IllegalStateException e) {  //예외가 발생하면(회원 이메일이 중복)
            model.addAttribute("errorMessage", e.getMessage());
            return "members/createMemberForm";
        }

        return "redirect:/members";
    }
  • 회원가입 화면에서 Post 요청으로 넘어온 회원가입 정보들을 memberForm 객체로 받고 , 판매자/구매자 정보를 Requestparam("role")을 통해 받습니다.
  • 만약 회원 가입 정보에서 이름, 이메일, 패스워드가 누락된다면 bindingResult로 값이 전달됩니다. 이를 이용해 누락이 발생하면 다시 회원가입 화면으로 넘기도록 하였습니다.
  • 정상적으로 회원 가입 정보가 memberForm 객체를 통해 들어오면 이를 이용해 member 객체를 생성하고 데이터베이스에 저장시켜주기 위해 memberService.join(member)를 호출합니다.
  • 이 때 memberService에서 중복 회원을 검증하게 되고 중복이 있다면 예외를 발생시키고 없다면 정상적으로 redirect 시켜줍니다.



📖회원가입 화면

1.thymeleaf

  • layout : 화면에서 공통으로 사용합니다.
  • 회원가입 페이지, 로그인 페이지, 상품 등록 페이지 등에 공통으로 적용되는 머리(header)와 몸통(body)를 만들기 위한 것입니다.

implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect' 

타임 리프의 레이아웃을 사용하기 위해 build.gradle에 추가해 줍니다.


  • th:block : 동적인 처리가 필요할 때 사용됩니다. layout:fragment 속성에 이름을 지정해서 실제 컨텐츠 페이지의 내용을 채워줍니다.

  • 자바스크립트 인라인 : 자바스크립트에서 타임리프를 편리하게 사용할 수 있는 기능입니다.

<head>
    <th:block layout:fragment="script">
        <script th:inline="javascript">
            var error = [[${errorMessage}]];
            if(error != null){
                alert(error);
            }
        </script>
    </th:block>
</head>


2.화면개발

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      th:replace="~{layout/base :: layout(~{::section})}"
      layout:decorate="~{layout/base}">
<head>
    <th:block layout:fragment="script">
        <script th:inline="javascript">
            var error = [[${errorMessage}]];
            if(error != null){
                alert(error);
            }
        </script>
    </th:block>
</head>

<section>
<div layout:fragnemt="content" style="padding:30px; padding-left:50px"  >
    <form th:action th:object="${memberForm}" method="post">
        <div th:if="${#fields.hasGlobalErrors()}">
            <p class="field-error" th:each="err : ${#fields.globalErrors()}"
               th:text="${err}">전체 오류 메시지</p>
        </div>
        <div>
            <label for="name">&nbsp&nbsp&nbsp 이름 &nbsp&nbsp&nbsp&nbsp</label>
            <input type="text" id="name" th:field="*{name}" class="form-control" placeholder="이름을 입력해주세요">
            <div class="field-error" th:errors="*{name}" />
        </div>
        <br>
        <div>
            <label for="email">&nbsp&nbsp 이메일 &nbsp&nbsp</label>
            <input type="text" id="email" th:field="*{email}" class="form-control" placeholder="이메일 형식으로 입력해주세요">
            <div class="field-error" th:errors="*{email}" />
        </div>
        <div>
            <label for="password">&nbsp 비밀번호 </label>
            <input type="password" id="password" th:field="*{password}" class="form-control"  placeholder="비밀번호를 입력해주세요">
            <div class="field-error" th:errors="*{password}" />
        </div>
        <br>
        <div>
            <label for="city">&nbsp&nbsp  지역명 &nbsp&nbsp</label>
            <input type="text" id="city" th:field="*{city}"
                   class="formcontrol"
            >
        </div>
        <div>
            <label for="street">&nbsp&nbsp  도로명 &nbsp&nbsp</label>
            <input type="text" id="street" th:field="*{street}"
                   class="formcontrol"
            >
        </div>
        <div>
            <label for="zipcode">&nbsp 우편번호 </label>
            <input type="text" id="zipcode" th:field="*{zipcode}"
                   class="formcontrol"
            >
        </div>
        <br>
        &nbsp
        <select name="role" id="role" class="formcontrol">
            <option value="">판매자, 구매자 등록</option>
            <option th:each="roleCode : ${roleCodes}"
                    th:value="${roleCode.code}"
                    th:text="${roleCode.displayName}" />
        </select>
        <hr class="my-4">
        <div class="row">
            <div class="col" style="text-align: center">
                <button class="w-100 btn btn-primary btn-lg" type="submit">
                    회원가입</button>
            </div>
            <div class="col" style="text-align: center">
                <button class="w-100 btn btn-secondary btn-lg"
                        onclick="location.href='home.html'"
                        th:onclick="|location.href='@{/members}'|"
                        type="button">취소</button>
            </div>
        </div>
    </form>
</div> 
</section>
</html>
  • layout을 사용해 header 부분과 footer부분은 통일 시켜주었습니다.
  • th:inline을 통해 중복 회원이 발생하면 alert창을 띄어주도록 하였습니다.



결과 화면

1. 이름, 이메일, 비밀번호 등이 누락되었을 경우

  • MemberForm에 설정해주었던 @NotEmpty 어노테이션으로 인해 유효성 검증을 정상적으로 진행하고 있습니다.


2. 회원 이메일이 중복 되었을 경우

  • alert창을 정상적으로 띄어주고 있습니다.


3. 회원 가입에 성공 하였을 경우

  • 데이터베이스에 회원 정보가 정상적으로 저장되었습니다.

0개의 댓글