-
[Spring Boot] 2-2. 세션을 사용하는 회원 가입, 회원 정보 수정프로젝트/도서 관리 시스템 2023. 6. 19. 11:23
📑 회원가입 & 정보 수정 백단
<MeberMapper.xml>
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.wish.library.member.mapper.MemberMapper"> <insert id="insert"> insert into member(email, password, name, nickname, mfCode, cellNo) values (#{email}, #{password}, #{name}, #{nickname}, #{mfCode}, #{cellNo}) </insert> <update id="update"> update member set password = #{password}, nickname = #{nickname}, cellNo = #{cellNo} where email = #{email} </update> </mapper>
<MemberMapper.java>
package com.wish.library.member.mapper; import com.wish.library.member.domain.MemberVO; import org.apache.ibatis.annotations.Mapper; @Mapper public interface MemberMapper { //회원 정보 조회 public MemberVO read(String email); //회원 가입 public int insert(MemberVO member); //회원 수정 public int update(MemberVO member); }
<MemberService.java>
package com.wish.library.member.service; import com.wish.library.member.domain.MemberVO; import com.wish.library.member.mapper.MemberMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.lang.reflect.Member; @Service @RequiredArgsConstructor @Slf4j public class MemberServiceImpl implements MemberService{ private final MemberMapper mapper; @Override public boolean join(MemberVO member) { log.info("============= member join service"); boolean joinResult = mapper.insert(member)==1; return joinResult; } @Override public boolean modify(MemberVO member) { log.info("============member modify service"); boolean updateResult = mapper.update(member) == 1; return updateResult; } }
<MemberController.java>
package com.wish.library.member.comtroller; import com.wish.library.member.domain.MemberVO; import com.wish.library.member.service.MemberService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.mvc.support.RedirectAttributes; import javax.servlet.http.HttpSession; @Controller @Slf4j @RequiredArgsConstructor public class MemberController { private final MemberService service; @GetMapping("/join") public String joinForm(){ return "/member/join"; } @PostMapping("/join") public String join(@ModelAttribute MemberVO member, RedirectAttributes rttr){ log.info("==========joinMember={}", member); if( service.join(member)){ rttr.addFlashAttribute("result", "회원 가입 완료"); }; return "redirect:/login"; } //회원 정보를 수정하려면 회원 아이디를 넘겨 받아야됨. @GetMapping("/modify") public String modifyForm(HttpSession session, Model model) { model.addAttribute("member", service.get((String)session.getAttribute("email"))); return "/member/modify"; } @PostMapping("/modify") public String modify(@ModelAttribute MemberVO member, RedirectAttributes rttr){ log.info("==========modifyMmeber={}", member); if(service.modify(member)){ rttr.addFlashAttribute("result", "회원 정보 수정 완료"); }; return "redirect:/"; } /** * ? 이메일 중복 체크면 이메일, 닉네임 중복 체크면 닉네임 이렇게 하나의 값만 받는데 * MemberVO를 파라미터로 받는게 맞을지 * 아니면 이메일 체크, 닉네임 체크 메서드를 2개 만들어서 각각 파라미터 하나씩 받고 쿼리도 각각 만드는 게 좋을지... */ @GetMapping("/getEmail") @ResponseBody public int getEmail(String email){ log.info("==========duplication EmailCheck member={}", email); MemberVO findMember = service.get(email); if(findMember == null){ //사용자가 입력한 이메일과 동일한 이메일 or 닉네임이 없는 경우. //조회된 값이 0이라는 의미. return 0; } //이메일이나 닉네임이 있는경우 return 1; } @GetMapping("/getNickname") @ResponseBody public int getNickname(String nickname){ log.info("==========duplication NicknameCheck member={}", nickname); MemberVO findMember = service.get(nickname); if(findMember == null){ //사용자가 입력한 이메일과 동일한 이메일 or 닉네임이 없는 경우. //조회된 값이 0이라는 의미. return 0; } //이메일이나 닉네임이 있는경우 return 1; } }
join 과 modify에서 service에서 반환받은 값이 true라면 RedirectAttributes에 값을 저장해서 redirect한다. 만약 값이 false라면 회원 가입 실패라는 메시지를 화면에 전달해야되지만, 따로 유효성 검사를 통해 입력받은 값들을 검열하므로 따로 실패시 로직을 만들지 않았다.
아래 getEmail과 getNickname은 중복 체크를 위한 메서드이다.
📑 회원 가입 화면단 (+유효성 검사)
<%@ page language="java" contentType="text/html; charset=UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <!doctype html> <html lang="en" data-bs-theme="auto"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="description" content=""> <meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors"> <meta name="generator" content="Hugo 0.111.3"> <title>회원가입</title> <link rel="canonical" href="https://getbootstrap.com/docs/5.3/examples/sign-in/"> <link href="assets/dist/css/sign-in.css" rel="stylesheet"> <jsp:include page="../includes/common_includes.jsp"></jsp:include> <script type="text/javascript" src="assets/dist/commonAjax.js"></script> <script type="text/javascript" src="assets/dist/js/memberJoinValidation.js"></script> <style> @media (min-width: 768px) { .bd-placeholder-img-lg { font-size: 3.5rem; } } .nav-scroller .nav { display: flex; flex-wrap: nowrap; padding-bottom: 1rem; margin-top: -1px; overflow-x: auto; text-align: center; white-space: nowrap; -webkit-overflow-scrolling: touch; } </style> </head> <script> </script> <body class="text-center"> <main class="form-signin w-100 m-auto"> <form name="join_form" action="/join" method="post"> <img class="mb-4" src="assets/brand/bootstrap-logo.svg" alt="" width="72" height="57" onclick="location.href='/'"> <h1 class="h3 mb-3 fw-normal">Please join in</h1> <div class="form-floating"> <input type="email" class="form-control" name="email" id="email" placeholder="name@example.com"> <label for="email">Email address</label><button class="btn btn-light rounded-pill px-3" type="button" name="btn" id="btnEmailCheck">Duplicate check</button> <span id="id-check"></span> </div> <div class="form-floating"> <input type="password" class="form-control" name="password" id="password" placeholder="Password" autoComplete="off"> <label for="password">Password</label> </div> <div class="form-floating"> <input type="password" class="form-control" name="passwordConfirm" id="passwordConfirm" placeholder="passwordConfirm" autoComplete="off"> <label for="passwordConfirm">passwordConfirm</label> </div> <div class="form-floating"> <input type="text" class="form-control" name="name" id="name" placeholder="name"> <label for="name">name</label> </div> <div class="form-floating"> <input type="text" class="form-control" name="nickname" id="nickname" placeholder="nickname"> <label for="nickname">nickname</label> <button class="btn btn-light rounded-pill px-3" type="button" name="btn" id="btnNicknameCheck">Duplicate check</button> <span id="nickname-check"></span> </div> <div class="form-floating"> <div class="form-check"> <input id="men" name="mfCode" value="M" type="radio" class="form-check-input" checked> <label class="form-check-label" for="men">Men</label> </div> <div class="form-check"> <input id="female" name="mfCode" value="F" type="radio" class="form-check-input"> <label class="form-check-label" for="female">Female</label> </div> </div> <div class="form-floating"> <input type="text" class="form-control" name="cellNo" id="cellNo" placeholder="cellNo"> <label for="cellNo">cellNo</label> </div> <div class="checkbox mb-3"> <label> <input type="checkbox" value="remember-me"> Remember me </label> </div> <button class="w-50 btn btn-lg btn-dark" type="button" onclick="history.back();" >이전페이지로가기</button> <button class="w-50 btn btn-lg btn-primary" type="button" name="btn" id="btnSingUp" >Sign up</button> </form> </main> </body> </html>
Sign up 버튼을 클릭하면 유효성 검사를 실행하도록 만들어놨다. 따로 memberJoinValidation.js를 만들어서 btnSignUpd이라는 id를 가진 버튼이 클릭된다면 fSingUp() 메서드를 호출한다.
회원가입시 체크해야될 리스트는 다음과 같다.
✔ 이메일 중복 체크 (중복 확인이 되지 않음 회원 가입을 할 수 없다.)
✔ 이메일 형식 체크 ( 알파벳,숫자) @ (알파벳,숫자) . (알파벳, 숫자)
✔ 비밀번호 유효성 검사 (영문,숫자,특수기호 조함 8자리 이상 20자리 이하)
✔ 비밀번호 일치 확인
✔ 이름 유효성 검사 ( 최소 2글자 이상 6글자 이하 한영문자만 가능)
✔ 닉네임 중복 체크 (중복 확인이 되지 않음 회원 가입을 할 수 없다.)
✔ 닉네임 유효성 검사 ( 최소 2글자 이상 6글자 이하 한영문자만 가능)
✔ 전화번호 유효성 검사(숫자만 입력 가능)
📌 모든 input에 값이 들어가야 되며, 값을 입력하지 않은 경우 **을 입력해주세요 라는 alter문이 뜬다.<memberJoinValidation.js - 이메일, 닉네임 중복 체크>
$(document).ready(function (){ registerBtnClickEvent() }) //이메일 중복 확인 체크 여부 0이면 체크 안함, 1이면 체크 //true, false로 생각하자 . let emailCheck = 0; let nicknameCheck =0; //이메일 중복 체크 function registerBtnClickEvent(){ $('button[name=btn]').click(function (e){ e.preventDefault(); const btnId = $(this).attr('id'); switch (btnId){ case 'btnEmailCheck' : fEmailCheck(); break; case 'btnNicknameCheck' : fNicknameCheck(); break; case 'btnSingUp' : fSingUp(); break; case 'btnModify' : fModify(); break; } }) } //닉네임 중복 체크 function fEmailCheck(){ const email = $("#email").val(); console.log("checkEmail : " + email); const param = { email : email } const resultcallback = function(returndata){ console.log("emailCheck returndata : " + returndata); if($('#email').val() !='') { if (returndata == '0') { $('#id-check').text('사용 가능한 이메일입니다.'); emailCheck = 1; } else { $('#id-check').text('이미 사용중인 이메일입니다.'); emailCheck = 0; } }else{ alert('이메일을 입력해주세요.'); $('#email').focus(); } } callAjax("/getEmail", "get", "json", true, param, resultcallback); } function fNicknameCheck(){ const nickname = $("#nickname").val(); console.log("checkNickname : " + nickname); const param = { nickname : nickname } const resultcallback = function(returndata){ console.log("emailCheck returndata : " + returndata); if($('#nickname').val() !='') { if (returndata == '0') { $('#nickname-check').text('사용 가능한 닉네임입니다..'); nicknameCheck = 1; } else { $('#nickname-check').text('이미 사용중인 닉네임입니다.'); nicknameCheck = 0; } }else{ alert('닉네임을 입력해주세요.'); $('#nickname').focus(); } } callAjax("/getNickname", "get", "json", true, param, resultcallback);
이메일 중복 체크는 ajax를 이용해서 비동기 처리하였다. 즉) Duplicate check 버튼 클릭 시 전체 화면을 리로드하는 것이 아니라 해당 로직만 서버에게 보내 값을 반환받도록 하는 REST 방식을 이용하였다.
<commonAjax.js>
callAjax라는 공통 ajax 함수를 만들어서 url, method, dataType, async(동기/비동기), param, callback 값들을 파라미터로 받는다.
function callAjax(url, method, dataType, async, param, callback){ $.ajax({ url : url, method: method, dataType : dataType, async : async, data : param, success : function (data){ callback(data); }, error: function (xhr,status,err){ console.log("xhr : " + xhr); console.log("status : " + status); console.log("err : " + err); } }) }
1.join.jsp에서 이메일 중복 체크 버튼을 클릭한다. (버튼 클릭 전 emailCheck 변수의 값은 0으로 할당되어있다. 이 값이 1로 변경되어야 이메일 중복 체크가 된 것. )
2. memberJoinValidation.js의 fEmailCheck() 메서드가 실행된다.
이 메서드에선 사용자가 입력한 email 값을 파라미터로 담아 컨트롤러의 '/getEmail' uri를 가진 메서드를 get 방식으로 호출하며 데이터 타입은 json으로 넘기고 받는다.
3. MemberController.java에서 getEmail 메서드는 @ResponseBody로 일반적인 jps와 같은 뷰로 전달되는 게 아닌 데이터 자체를 전달하기 위한 용도로 메서드르 사용하기 위한 어노테이션이다. 이 메서드에서 service로 사용자가 입력한 이메일과 동일한 이메일이 있나 check한다. 만약 사용자와 동일한 이메일이 있는 경우(즉 중복될 경우) 1, 사용 가능한 이메일일 경우 0을 return 받는다.
4. callAjax로 return 받은 데이터를 인자로 memberJoinValidation.js 의 fEmailCheck 메서드의 resultcallback 함수를 실행시킨다. 이 함수는 만약 반환받은 값이 0이면 '사용 가능한 이메일 입니다.'를, 반환 받은 값이 1이면 '이미 사용중인 이메일 입니다.'를 text로 출력한다.
또한 emailCheck 값을 사용 가능한 이메일이면 1, 이미 사용중인 이메일이면 0으로 할당한다.위의 화면을 보면 처음 Duplicate check를 했을때는 리턴 값이 1로 '이미 사용중인 이메일입니다.' 라는 메시지가 떴다. 그 후 다른 이메일을 입력 후 버튼 클릭하니 '사용 가능한 이메일 입니다.'를 확인할 수 있다.
<memberJoinValidation.js - 유효성 검사 (정규식 체크) >
//이메일 유효성 검사 function emailValidation(){ const email = $('#email'); //(알파벳,숫자)@(알파벳,숫자).(알파벳,숫자) let emailReg = /^[A-Za-z0-9_\.\-]+@[A-Za-z0-9\-]+\.[A-Za-z0-9\-]+/; if(!emailReg.test(email.val())){ console.log(email.val()); alert("이메일 형식에 맞게 입력해주세요.") email.focus(); return false; } //이메일 중복 검사 하지 않은 경우 if(emailCheck ==0){ alert('이메일 중복 확인을 해주세요.'); email.focus(); return false; //아래 코드부터 아무것도 진행하지 말아라. } } //비밀번호 유효성 검사 function passValidation(){ const password = $('#password'); const passwordConfirm = $('#passwordConfirm'); //jquery는 요소.val()로 값 가져오기 //순수 js는 document.getElementById.value로 값 가져오기 if(password.val() ==""){ alert("비밀번호를 입력해주세요. "); password.focus(); return false; } let pwReg = /^(?=.*[a-zA-Z])(?=.*[!@#$%^*+=-])(?=.*[0-9]).{8,20}$/; if(!pwReg.test(password.val())){ console.log(password.val()); alert("비밀번호는 영문 숫자 특수기호 조합 8자리 이상입니다."); password.focus(); return false; } if(password.val() !== passwordConfirm.val()){ alert("비밀번호가 일치하지 않습니다.") passwordConfirm.focus(); return false; } } function nameValidation(){ const name = $('#name'); if(name.val() =="" ){ alert("이름을 입력해주세요. "); name.focus(); return false; } //let nameReg = /^[가-힣a-zA-Z]{2,6}$/;//한글과 영문을 사용하는 최소 2글자 이상. let nameReg = /^(?=.*[a-z0-9가-힣])[a-z0-9가-힣]{2,6}$/; if(!nameReg.test(name.val())){ console.log(name.val()); alert("이름은 최소 2글자 이상 6글자 이하의 한영문자만 가능합니다. "); name.focus(); return false; } } function nicknameValidation(){ const nickname = $('#nickname'); //닉네임 중복 검사 하지 않은 경우 if(nicknameCheck ==0){ alert('닉네임 중복 확인을 해주세요.'); nickname.focus(); return false; //아래 코드부터 아무것도 진행하지 말아라. } let nicknameReg = /^(?=.*[a-z0-9가-힣])[a-z0-9가-힣]{2,15}$/;//한글과 영문을 사용하는 최소 2글자 이상. if(!nicknameReg.test(nickname.val())){ console.log(nickname.val()); alert("닉네임은 최소 2글자 이상 15글자 이하의 한영문자만 가능합니다. "); name.focus(); return false; } } function cellValidation() { const cellNo = $('#cellNo'); if (cellNo.val() == "") { alert("전화번호를 입력해주세요. "); cellNo.focus(); return false; } /* let cellNoReg = /^[0-9]+/g; //숫자만 입력하는 정규식 if(!cellNoReg.test(cellNo.val())){ console.log(cellNo.val()); alert("전화번호는 숫자만 입력할 수 있습니다.") cellNo.focus(); return false; }*/ if (isNaN(cellNo.val())) { console.log(cellNo.val()); alert("전화번호는 숫자만 입력할 수 있습니다.") cellNo.focus(); return false; } } function fSingUp(){ console.log("회원가입 유효성 검사 중"); emailValidation(); passValidation(); nameValidation(); nicknameValidation(); cellValidation(); document.join_form.submit(); } function fModify() { console.log("회원수정 유효성 검사 중 "); passValidation(); nicknameCheck = 1; nicknameValidation(); cellValidation(); document.modify_form.submit(); }
각각 이메일 , 비밀번호 등등 유효성 검사 함수를 만들어두고 회원 가입/ 회원 수정 버튼 클릭 시 해당 함수들을 호출해서 유효성검사를 하게 만들었다.
처음엔 따로 함수를 만들지 않고 회원 가입과 회원 수정을 하나의 함수에서 처리하려고 했다. 예를 들어 회원 가입과 회원 수정에 버튼에 같은 id 값을 주고, 이 아이디를 가진 버튼이 클릭되면 다음 유효성 검사 로직이 실행된다. 실행되면서 그 안에 만약 name이 join이다 그럼 회원가입 컨트롤러를 호출하고, modify다 그럼 회원수정 컨트롤러를 호출하려고 했는데, 생각해보니 회원 가입과 회원 수정에서 필요한 파라미터들이 다르다. 예를 들어 회원 수정에서는 이메일(아이디)을 변경할 필요가 없기 때문에 이메일 중복성 체크나 이메일 값이 필요하지 않다. 따라서 따로 fSignUp과 fModify라는 함수를 만들어서 각각의 작업들을 호출하게 만들었다.
📑 회원 정보 수정 화면단
회원 정보 수정화면은 회원 가입 화면과 크게 다르지 않지만, 중요한 포인트가 있다. 그것은 바로 로그인 되어있는 회원의 정보를 화면에 출력해줘야되는것. 내가 어떤 식으로 정보가 저장되어 있나 확인을 해야 정보를 변경하던가 그대로 두던가 하기 때문이다.
따라서 위에 MemberCotnroller.java의 modifyForm 메서드에서 세션에 저장되어 있는 email의 값 가져와 service의 get 메서드를 호출한다. (이 메서드는 이메일에 해당하는 회원의 정보를 반환받는다. ) 이 반환받은 값을 화면 Model의 model 객체에 "member"라는 이름으로 저장하는 코드를 구현하였다.
따라서 modify.jsp에서 value='<c:out value="${member.email}"/>'와 같이 정보를 추출해서 화면에 출력할 수 있는 것이다.
<%@ page language="java" contentType="text/html; charset=UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <!doctype html> <html lang="en" data-bs-theme="auto"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="description" content=""> <meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors"> <meta name="generator" content="Hugo 0.111.3"> <title>회원 정보 수정</title> <link rel="canonical" href="https://getbootstrap.com/docs/5.3/examples/sign-in/"> <link href="assets/dist/css/sign-in.css" rel="stylesheet"> <jsp:include page="../includes/common_includes.jsp"></jsp:include> <script type="text/javascript" src="assets/dist/commonAjax.js"></script> <script type="text/javascript" src="assets/dist/js/memberJoinValidation.js"></script> <style> @media (min-width: 768px) { .bd-placeholder-img-lg { font-size: 3.5rem; } } .nav-scroller .nav { display: flex; flex-wrap: nowrap; padding-bottom: 1rem; margin-top: -1px; overflow-x: auto; text-align: center; white-space: nowrap; -webkit-overflow-scrolling: touch; } </style> </head> <script> </script> <body class="text-center"> <main class=" w-100 m-auto"> <form name="modify_form" action="/modify" method="post"> <img class="mb-4" src="assets/brand/bootstrap-logo.svg" alt="" width="72" height="57" onclick="location.href='/'"> <h1 class="h3 mb-3 fw-normal">My Page</h1> <div class="form-floating"> <input type="email" class="form-control" name="email" id="email" value='<c:out value="${member.email}"/>' readonly> <!--label은 입력하는 칸 위에 작게 어떤 값을 입력하는지 알려주는 값.--> <label for="email">Email</label> <span id="id-check"></span> </div> <div class="form-floating"> <input type="password" class="form-control" name="password" id="password" placeholder="Password" autoComplete="off"> <label for="password">Password</label> </div> <div class="form-floating"> <input type="password" class="form-control" name="passwordConfirm" id="passwordConfirm" placeholder="passwordConfirm" autoComplete="off"> <label for="passwordConfirm">passwordConfirm</label> </div> <div class="form-floating"> <input type="text" class="form-control" name="nickname" id="nickname" value='<c:out value="${member.nickname}"/>'> <label for="nickname">nickname</label> <button class="btn btn-light rounded-pill px-3" type="button" name="btn" id="btnNicknameCheck">Duplicate check</button> <span id="nickname-check"></span> </div> <div class="form-floating"> <input type="text" class="form-control" name="cellNo" id="cellNo" value='<c:out value="${member.cellNo}"/>'> <label for="cellNo">cellNo</label> </div> <div class="checkbox mb-3"> <label> <input type="checkbox" value="remember-me"> Remember me </label> </div> <button class="w-50 btn btn-lg btn-dark" type="button" onclick="history.back();" >이전페이지로가기</button> <button class="w-50 btn btn-lg btn-primary" type="button" name="btn" id="btnModify" >회원정보수정</button> </form> </main> </body> </html>
반응형'프로젝트 > 도서 관리 시스템' 카테고리의 다른 글
[Spring Boot] 2-4. 스프링 시큐리티, 회원 가입 구현하기 (0) 2023.07.05 [Spring Boot] 2-3. 스프링 시큐리티, 로그인/로그아웃 구현 (0) 2023.07.05 [Spring Boot] 2-1. 세션을 사용하는 회원 로그인, 로그아웃 (0) 2023.06.19 [Spring Boot] 1-4. 화면 레이아웃 구성 (3) 2023.06.19 [Spring Boot] 1-3. Oracle + MyBatis 연동 테스트 (0) 2023.06.19