2020년 12월 28일

업데이트:


프로젝트 구성

20201228_01jpg

  • 빨간 박스 파일은 새로 생성한 파일
  • 파란 박스 파일은 수정한 파일



  • Cookie 저장(아이디 저장 체크)
    • 로그인 시 아이디 저장 체크
      20201228_02jpg

    • 쿠키 확인
      20201228_03

    • 로그아웃 시 쿠키 확인
      20201228_04

  • Cookie 삭제(아이디 저장 해제)
    • 로그인 시 아이디 저장 해제
      20201228_05

    • 쿠키 확인
      20201228_06

    • 로그아웃 시 쿠키 확인
      20201228_07


LoginServlet.java

// 로그인이 성공했을 때 Session에 로그인 정보 추가하기

if(loginMember != null){
    session.setMaxInactiveInterval(60 * 30); // 현재 세션 30분 후 만료

    // session에 로그인 정보를 추가함 
    session.setAttribute("loginMember", loginMember);

    // cookie -> 사용자 컴퓨터에 저장
    // session -> 서버에 저장

    // 서버가 접속한 브라우저마다 session을 구분하는 방법
    // --> 해당 브라우저의 쿠키 파일에 session을 구분할 수 잇는
    //      session id를 저장시켜 두었다가 접속될 때마다 쿠키에서 
    //      자동으로 session id를 얻어간다.

    // 쿠키 객체 생성
    Cookie cookie = new Cookie("saveId", memberId);

    // 아이디 저장 체크박스 체크 확인
    if(save != null) { // 체크 했으면
        cookie.setMaxAge(60 * 60 * 24 * 7); // 생명주기 1주일
    }else{
        cookie.setMaxAge(0); // 생명주기가 0이면 삭제된다.
    }

    // 쿠키 유효 디렉토리 지정
    cookie.setPath(request.getContextPath());
    // setPath(String path) : 쿠키의 유효한 디렉토리 설정.
    // ex) setPath("/") : 모든 문서(디렉토리)에서 쿠키 사용이 가능하다.
    // 위의 코드는 요청한 페이지 '/wsp'에서 사용이 가능하다는 얘기

    // 생성된 쿠키를 클라이언트로 전달(응답)
    response.addCookie(cookie);
}else{
    // 로그인이 실패했을 때 

    // 생략
}
response.sendRedirect(request.getHeader("referer"));



header.jsp

<!-- cookie에 저장된 아이디가 있을 경우 checkd 속성을 추가 -->
<input type="checkbox" name="save" is="save" 
 <c:if test="${!empty cookie.saveId.value}">
    checked
 </c:if>
 > 아이디 저장

c:if문으로 인해 cookie 값이 비어있지 않으면 input 태그에 checked라는 속성이 추가된다.



회원가입(페이지 이동)

  • 로그인 모달창에서 회원가입 클릭
    20201228_08

  • 회원가입 페이지
    20201228_09


header.jsp

<!-- 회원가입 버튼을 누르면 회원가입 페이지로 이동할 수 있도록
  href 부분을 변경한다. -->
  
<!-- 로그인 모달창 부분 -->
<a class="btn btn-lg btn-secondary btn-block"
     href="${contextPath}/member/signUpForm.do">회원가입</a>



SignUpFormServlet.java

package com.kh.wsp.member.controller;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/member/signUpForm.do")
public class SignUpFormServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// 회원가입 화면을 보여주는 서블릿
		String path ="/WEB-INF/views/member/signUpForm.jsp";
		RequestDispatcher view = request.getRequestDispatcher(path);
		
		view.forward(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}
}



signUpForm.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>회원가입</title>
<style>
	/* number 태그 화살표 제거 */
	input[type="number"]::-webkit-outer-spin-button, input[type="number"]::-webkit-inner-spin-button
		{
		-webkit-appearance: none;
		margin: 0;
	}
</style>
</head>
<body>
	<div class="container">
		<%-- <%@ include file="../common/header.jsp"%> --%>
		<jsp:include page="../common/header.jsp"></jsp:include>

		<div class="py-5 text-center">
			<h2>회원 가입</h2>
		</div>

		<div class="row">
			<div class="col-md-6 offset-md-3">

				<form method="POST" action="signUp.do" class="needs-validation" name="signUpForm" onsubmit="return validate();">
					<%-- action="${contextPath}/member/signUp.do" --%>

					<!-- 아이디 -->
					<div class="row mb-5 form-row">
						<div class="col-md-3">
							<label for="id">* 아이디</label>
						</div>
						<div class="col-md-6">
							<input type="text" class="form-control" name="id" id="id" maxlength="12" placeholder="아이디를 입력하세요" autocomplete="off" required>
							<!-- required : 필수 입력 항목으로 지정 -->
							<!-- autocomplete="off" : input 태그 자동완성 기능을 끔 -->

							<!-- 팝업창 중복체크 여부 판단을 위한 hidden 타입 요소 -->
							<input type="hidden" name="idDup" id="idDup" value="false">
						</div>

						<!-- ajax 중복검사 시 필요없음 -->
						<div class="col-md-3">
                <button type="button" class="btn btn-primary" id="idDupCheck">중복검사</button>
            </div>

						<div class="col-md-6 offset-md-3">
							<span id="checkId">&nbsp;</span>
						</div>
					</div>


					<!-- 비밀번호 -->
					<div class="row mb-3 form-row">
						<div class="col-md-3">
							<label for="pwd1">* 비밀번호</label>
						</div>
						<div class="col-md-6">
							<input type="password" class="form-control" id="pwd1" name="pwd1" maxlength="12" placeholder="비밀번호를 입력하세요" required>
						</div>

						<div class="col-md-6 offset-md-3">
							<span id="checkPwd1">&nbsp;</span>
						</div>
					</div>

					<!-- 비밀번호 확인 -->
					<div class="row mb-3 form-row">
						<div class="col-md-3">
							<label for="pwd2">* 비밀번호 확인</label>
						</div>
						<div class="col-md-6">
							<input type="password" class="form-control" id="pwd2" maxlength="12" placeholder="비밀번호 확인" required>
						</div>

						<div class="col-md-6 offset-md-3">
							<span id="checkPwd2">&nbsp;</span>
						</div>
					</div>
					<br>

					<!-- 이름 -->
					<div class="row mb-3 form-row">
						<div class="col-md-3">
							<label for="name">* 이름</label>
						</div>
						<div class="col-md-6">
							<input type="text" class="form-control" id="name" name="name" required>
						</div>

						<div class="col-md-6 offset-md-3">
							<span id="checkName">&nbsp;</span>
						</div>
					</div>

					<!-- 전화번호 -->
					<div class="row mb-3 form-row">
						<div class="col-md-3">
							<label for="phone1">* 전화번호</label>
						</div>
						<!-- 전화번호1 -->
						<div class="col-md-3">
							<select class="custom-select" id="phone1" name="phone1" required>
								<option>010</option>
								<option>011</option>
								<option>016</option>
								<option>017</option>
								<option>019</option>
							</select>
						</div>

						<!-- number타입의 input태그에는 maxlength를 설정할 수 없음 -->
						<!-- 전화번호2 -->
						<div class="col-md-3">
							<input type="number" class="form-control phone" id="phone2" name="phone2" required>
						</div>

						<!-- 전화번호3 -->
						<div class="col-md-3">
							<input type="number" class="form-control phone" id="phone3" name="phone3" required>
						</div>

						<div class="col-md-6 offset-md-3">
							<span id="checkPhone">&nbsp;</span>
						</div>
					</div>

					<!-- 이메일 -->
					<div class="row mb-3 form-row">
						<div class="col-md-3">
							<label for="email">* Email</label>
						</div>
						<div class="col-md-6">
							<input type="email" class="form-control" id="email" name="email" autocomplete="off" required>
						</div>

						<div class="col-md-6 offset-md-3">
							<span id="checkEmail">&nbsp;</span>
						</div>
					</div>
					<br>

					<!-- 주소 -->
					<!-- 오픈소스 도로명 주소 API -->
					<!-- https://www.poesis.org/postcodify/ -->
					<div class="row mb-3 form-row">
						<div class="col-md-3">
							<label for="postcodify_search_button">우편번호</label>
						</div>
						<div class="col-md-3">
							<input type="text" name="post" class="form-control postcodify_postcode5">
						</div>
						<div class="col-md-3">
							<button type="button" class="btn btn-primary" id="postcodify_search_button">검색</button>
						</div>
					</div>

					<div class="row mb-3 form-row">
						<div class="col-md-3">
							<label for="address1">도로명 주소</label>
						</div>
						<div class="col-md-9">
							<input type="text" class="form-control postcodify_address" name="address1" id="address1">
						</div>
					</div>

					<div class="row mb-3 form-row">
						<div class="col-md-3">
							<label for="address2">상세주소</label>
						</div>
						<div class="col-md-9">
							<input type="text" class="form-control postcodify_details" name="address2" id="address2">
						</div>
					</div>

					<!-- 관심분야 -->
					<hr class="mb-4">
					<div class="row">
						<div class="col-md-3">
							<label>관심 분야</label>
						</div>
						<div class="col-md-9 custom-control custom-checkbox">
							<div class="form-check form-check-inline">
								<input type="checkbox" name="memberInterest" id="sports" value="운동" class="form-check-input custom-control-input">
								<label class="form-check-label custom-control-label" for="sports">운동</label>
							</div>
							<div class="form-check form-check-inline">
								<input type="checkbox" name="memberInterest" id="movie" value="영화" class="form-check-input custom-control-input">
								<label class="form-check-label custom-control-label" for="movie">영화</label>
							</div>
							<div class="form-check form-check-inline">
								<input type="checkbox" name="memberInterest" id="music" value="음악" class="form-check-input custom-control-input">
								<label class="form-check-label custom-control-label" for="music">음악</label>
							</div>
							<br>
							<div class="form-check form-check-inline">
								<input type="checkbox" name="memberInterest" id="cooking" value="요리" class="form-check-input custom-control-input">
								<label class="form-check-label custom-control-label" for="cooking">요리</label>
							</div>
							<div class="form-check form-check-inline">
								<input type="checkbox" name="memberInterest" id="game" value="게임" class="form-check-input custom-control-input">
								<label class="form-check-label custom-control-label" for="game">게임</label>
							</div>
							<div class="form-check form-check-inline">
								<input type="checkbox" name="memberInterest" id="etc" value="기타" class="form-check-input custom-control-input">
								<label class="form-check-label custom-control-label" for="etc">기타</label>
							</div>
						</div>
					</div>

					<hr class="mb-4">
					<button class="btn btn-primary btn-lg btn-block" type="submit">가입하기</button>
				</form>
			</div>
		</div>
		<br>
		<br>

		<!-- jQuery와 postcodify 를 로딩한다. -->
		<script src="//d1p7wdleee1q2z.cloudfront.net/post/search.min.js"></script>
		
		<!-- 회원 관련 Javascript 코드를 모아둘 wsp_member.js 파일을 작성 -->
		<script src="${contextPath}/resources/js/wsp_member.js"></script>

	</div>
	<jsp:include page="../common/footer.jsp"></jsp:include>
</body>
</html>



회원가입 유효성 검사

유효성 검사를 위해 Javascript 파일을 생성한다.

아이디

  • 형식 불일치 시
    20201228_10

  • 형식 일치 시
    20201228_11

  • wsp_member.js
var validateCheck = {
    "id" : false,
    "pwd1" : false,
    "pwd2" : false,
    "name" : false,
    "phone2" : false,
    "email" : false,
}
// 아이디 유효성 검사
// 첫 글자는 영어 소문자, 나머지 글자는 영어 대, 소문자 + 숫자, 총 6~12글자
$("#id").on("input", function(){
    var regExp = /^[a-z][a-zA-Z\d]{5,11}$/;

    var value = $("#id").val();
    if(!regExp.test(value)){
        $("#checkId").text("아이디 형식이 유효하지 않습니다.")
            .css("color", "red");
        validateCheck.id = false;
    } else{
        $("#checkId").text("유효한 아이디 형식입니다.")
        .css("color", "green");
        validateCheck.id = true;
    }
});



비밀번호 유효성 검사

  • 비밀번호 유효성 검사를 통과하지 않고 비밀번호 확인을 입력할 시
    20201228_12

  • 비밀번호 두개가 일치하지 않을 시
    20201228_13

  • 비밀번호가 일치할 시
    20201228_14

// 비밀번호 유효성 검사
// 영어 대, 소문자 + 숫자, 총 6~12글자
// + 비밀번호, 비밀번호 확인의 일치 여부
// + 비밀번호를 입력하지 않거나 유효하지 않은 상태로
//   비밀번호 확인을 작성하는 경우

$("#pwd1, #pwd2").on("input", function(){
    // 비밀번호 유효성 검사
    var regExp = /^[a-zA-Z\d]{6,12}$/;

    var v1 = $("#pwd1").val();
    var v2 = $("#pwd2").val();

    if(!regExp.test(v1)){
        $("#checkPwd1").text("비밀번호 형식이 유효하지 않습니다.")
        .css("color", "red");
        validateCheck.pwd1 = false;
    } else{
        $("#checkPwd1").text("유효한 비밀번호 형식입니다.")
        .css("color", "green");
        validateCheck.pwd1 = true;
    }


    // 비밀번호가 유효하지 않은 상태에서 비밀번호 확인 작성 시
    if(!validateCheck.pwd1 && v2.length > 0){
        swal("유효한 비밀번호를 먼저 작성해주세요.");
        $("#pwd2").val(""); // 비밀번호 확인에 입력한 값 삭제
        $("#pwd1").focus();
    }else {
        // 비밀번호, 비밀번호 확인의 일치 여부
       if(v1.length == 0 || v2.length == 0){
			$("#checkPwd2").text("");
		}else if(v1 == v2){
            $("#checkPwd2").text("비밀번호 일치")
            .css("color", "green");
            validateCheck.pwd2 = true;
        }else{
            $("#checkPwd2").text("비밀번호 불일치")
            .css("color", "red");
            validateCheck.pwd2 = false;
        }
    }
});



이름 유효성 검사

  • 형식에 맞지 않을 시
    20201228_15

  • 형식에 맞을 시
    20201228_16

/*
이름
/^[가-힣]{2,}$/; // 한글 두 글자 이상
*/ 
$("#name").on("input", function(){
    var regExp = /^[가-힣]{2,}$/;

    var value = $("#name").val();
    if(!regExp.test(value)){
        $("#checkName").text("이름 형식이 유효하지 않습니다.")
            .css("color", "red");
            validateCheck.name = false;
    }else {
        $("#checkName").text("유효한 이름 형식입니다.")
            .css("color", "green");
            validateCheck.name = true;
    }
});



전화번호 유효성 검사

  • 형식에 맞지 않을 시
    20201228_17

  • 형식에 맞을 시
    20201228_18

// 전화번호 유효성 검사
$(".phone").on("input", function(){
    // 전화번호 input 태그에 4글자 초과 입력하지 못하게 하는 이벤트
	if ($(this).val().length > 4) {
        $(this).val( $(this).val().slice(0, 4));
        
        // this : 이벤트가 발생한 요소
        // $(this) 
	}

    var regExp1 = /^\d{3,4}$/;
    var regExp2 = /^\d{4}$/;

    var v1 = $("#phone2").val();
    var v2 = $("#phone3").val();

    if(!regExp1.test(v1) || !regExp2.test(v2)){
        $("#checkPhone").text("전화번호가 유효하지 않습니다.")
        .css("color", "red");
        validateCheck.phone2 = false;
    }else{
        $("#checkPhone").text("유효한 형식의 전화번호 입니다.")
        .css("color", "green");
        validateCheck.phone2 = true;
    }
});



이메일 유효성 검사

  • 형식에 맞지 않을 시
    20201228_19

  • 형식에 맞을 시
    20201228_20

/*
이메일
/^[\w]{4,}@[\w]+(\.[\w]+){1,3}$/; // 4글자 아무단어 @ 아무단어 . * 3
*/
$("#email").on("input", function(){
    var regExp = /^[\w]{4,}@[\w]+(\.[\w]+){1,3}$/;

    var value = $("#email").val();
    if(!regExp.test(value)){
        $("#checkEmail").text("이메일 형식이 유효하지 않습니다.")
            .css("color", "red");
            validateCheck.email = false;
    }else {
        $("#checkEmail").text("유효한 이메일 형식입니다.")
            .css("color", "green");
            validateCheck.email = true;
    }
});



전체적인 유효성 검사

function validate(){

    // 유효성 검사 여부 확인
    for(var key in validateCheck){
        if(!validateCheck[key]){
            var msg;
            switch(key){
                case "id" : msg="아이디가"; break;
                case "pwd1" :
                case "pwd2" : msg = "비밀번호가"; break;
                case "name" : msg="이름이"; break;
                case "phone2" : msg="전화번호가"; break;
                case "email" : msg="이메일이"; break;
            }

            swal(msg + " 유효하지 않습니다.");

            $("#" + key).focus();
            return false;
        }
    }

}

// 주소 
$(function(){
    $("#postcodify_search_button").postcodifyPopUp();
});

회원가입(sql 반영)

  • 유효성 검사를 모두 통과하지 않을 시
    20201228_23

  • 유효성 검사를 모두 통과할 시
    20201228_24

  • 회원가입이 성공 후 DB
    20201228_25

  • Member.java
// 회원가입용 생성자 추가
	public Member(String memberId, String memberPwd, String memberName, String memberPhone, String memberEmail,
			String memberAddress, String memberInterest) {
		super();
		this.memberId = memberId;
		this.memberPwd = memberPwd;
		this.memberName = memberName;
		this.memberPhone = memberPhone;
		this.memberEmail = memberEmail;
		this.memberAddress = memberAddress;
		this.memberInterest = memberInterest;
	}



  • SignUpServlet.java
@WebServlet("/member/signUp.do")
public class SignUpServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// get보다 post를 쓰는 이유
		// 1. 정보의 양이 많아서
		// 2. queryString을 숨기기 위해
		
				
		// POST 방식 전송 시 한글이 깨지는 문제를 해결하기 위해 인코딩 변경
		request.setCharacterEncoding("UTF-8");
		
		// 전달 받은 파라미터를 모두 변수에 저장
		String memberId = request.getParameter("id");
		String memberPwd = request.getParameter("pwd1");
		String memberName = request.getParameter("name");
		String memberEmail = request.getParameter("email");
		
		String phone1 = request.getParameter("phone1");
		String phone2 = request.getParameter("phone2");
		String phone3 = request.getParameter("phone3");
			
		// 전화번호를 '-' 구분자로 하여 하나로 합치기
		String memberPhone = phone1 + "-" + phone2 + "-" + phone3;
		
		String post = request.getParameter("post"); // 우편번호
		String address1 = request.getParameter("address1"); // 도로명주소
		String address2 = request.getParameter("address2"); // 상세 주소
		
		String memberAddress = post + "," + address1 + "," + address2;
				
		String[] interest = request.getParameterValues("memberInterest");
		
		String memberInterest = null;
		
		if(interest != null) memberInterest = String.join(", ", interest);
	
		// Member 객체를 생성하여 파라미터 모두 저장
		Member member = new Member(memberId, memberPwd, memberName, 
									memberPhone, memberEmail, 
									memberAddress, memberInterest);
		
		// 비즈니스 로직 수행
		try {
			// 비즈니스 로직 수행 후 결과를 반환 받아 저장
			int result = new MemberService().signUp(member);
			
			String swalIcon = null;
			String swalTitle = null;
			String swalText = null;
			
			
			if(result > 0) { // 성공
				swalIcon = "success";
				swalTitle = "회원가입 성공!";
				swalText = memberName + "회원가입을 환영합니다.";
			}else { // 실패
				swalIcon = "error";
				swalTitle = "회원가입 실패";
				swalText = memberName + "문제가 지속될 경우 고객센터로 문의 바랍니다.";
			}
			
			HttpSession session = request.getSession();
			
			session.setAttribute("swalIcon", swalIcon);
			session.setAttribute("swalTitle", swalTitle);
			session.setAttribute("swalText", swalText);
			
			// 회원가입 진행 후 메인 페이지로 이동(메인 화면 재요청)
			response.sendRedirect(request.getContextPath());
			
		}catch (Exception e) {
			e.printStackTrace();
			
			request.setAttribute("errorMsg", "회원가입 과정에서 오류가 발생했습니다.");
			
			String path = "/WEB-INF/views/common/errorPage.jsp";
			
			RequestDispatcher view = request.getRequestDispatcher(path);
			view.forward(request, response);
		}
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}

}



  • MemberService.java
/** 회원가입 Service
    * @param member
    * @return result
    * @throws Exception
    */
public int signUp(Member member) throws Exception {
    // 1) Connection 얻어오기
    Connection conn = getConnection();
    // getConnection 기존에 있던 커넥션 풀에서 하나를 가져옴. 새로 생성 X
    
    // 2) DAO 메소드 호출 후 결과 반환
    int result = dao.signUp(conn, member);
    
    // 3) 트랜잭션 처리
    if(result > 0) commit(conn);
    else rollback(conn);
    
    // 4) Connection 반환
    close(conn);
    
    // 5) 결과값 반환
    return result;
}



  • member-query.xml
<entry key="signUp">
INSERT INTO MEMBER
VALUES(SEQ_MNO.NEXTVAL, ?, ?, ?, ?, ?, ?, ?, 
DEFAULT, DEFAULT, DEFAULT)
</entry>



  • MemberDAO.java
/** 회원가입 DAO
    * @param conn
    * @param member
    * @return result
    * @throws Exception
    */
public int signUp(Connection conn, Member member) throws Exception {
    
    //1) 결과를 저장할 변수 선언
    int result = 0;
    
    // 2) SQL 구문 준비
    try {
        // 3) PreparedStatement 객체를 얻어와 SQL 구문 세팅
        String query = prop.getProperty("signUp");
        pstmt = conn.prepareStatement(query);
        
        // 4) 위치 홀더(?)에 알맞은 값 세팅
        //MEMBER_ID, MEMBER_PWD, MEMBER_NM,
        //MEMBER_PHONE, MEMBER_EMAIL, 
        //MEMBER_ADDR, MEMBER_INTEREST
        
        pstmt.setString(1, member.getMemberId());
        pstmt.setString(2, member.getMemberPwd());
        pstmt.setString(3, member.getMemberName());
        pstmt.setString(4, member.getMemberPhone());
        pstmt.setString(5, member.getMemberEmail());
        pstmt.setString(6, member.getMemberAddress());
        pstmt.setString(7, member.getMemberInterest());
        
        // 5) SQL 구문 수행 후 반환값 저장
        result = pstmt.executeUpdate();
        
    }finally {
        // 6) 사용한 JDBC 자원 반환
        close(pstmt);
    }
    
    // 7) 결과 반환
    return result;
}

댓글남기기