본문 바로가기
BACK-END/SPRING

[Spring]회원가입 폼 만들기

by 썬키 2022. 3. 14.

게시판 하나를 만들 때, 그에 따른 순서와 필요한 데이터들만 잘 숙지한다면 어렵지 않게 해낼수가 있다.

그 뒤에 따라오는 부가적인 것들은 따로 공부한다면 더 좋은 퀄리티를 낼 수 있다.

 

오늘은 회원가입 폼을 만드는 작업을 했다.

이 역시, 이클립스에서 했던것과 거의 유사했기 때문에 어렵지는 않았다.

하지만 write.jsp에서 JS를 이용한 유효성 검사라든지 Alert 창을 띄우는 작업은 조금 어려웠다.

솔직히 말해, 잘 이해하지 못하고 그냥 따라치기 바빴던 거 같다.

 

자바스크립트는 조금 더 욕심내서 공부하면 좋을듯하다.

 

최종적으로 만들어낸 회원가입 폼은 이렇게 생겼다.

 

 

순서는 VO 객체 생성 - Mapper.java(추상메서드) - Mapper.xml(쿼리문) - Service - Controller - JSP

 

<MemberVO>

package com.webjjang.member.vo;

import java.util.Date;

import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.multipart.MultipartFile;

import lombok.Data;

@Data
public class MemberVO {
	private String id, pw, name, gender, tel, email, status, photo;
	// 날짜형 입력을 받을 때, 문자열로 들어오므로 패턴을 지정해서 정의해 놓으면 Date 객체로 만들때 사용한다.
	@DateTimeFormat(pattern = "yyyy-MM-dd")
	private Date birth;
	private Date regDate;
	private Date conDate;
	private int gradeNo;
	private String gradeName;
	// 사용자가 업로드한 프로필 사진을 저장하는 변수
	// write.jsp에서 name="photoFile"로 지정할것★ 
	// post이고 enctype="multipart/form-data" 지정해야만 한다.
	private MultipartFile photoFile;
}

MemberVO 에서는 @Data를 이용해 getter(),setter(), toString을 생성했다.

여기서 중요하게 봐야할 부분은 @DateTimeFormat 부분이다.

생년월일(bitrh)은 날짜형을 입력받기 때문에 문자열로 들어오므로 패턴을 정의해놓은 Annotation을 사용했다.

 

그리고, 프로필사진(photoFile)에 해당하는 부분은 리턴타입을 MultipartFile로 지정했다.

 

추상 메서드, 쿼리문 작성, 서비스 부분은 기본적이니까 넘어가도록 하고

MemberController 로 넘어간다.

 

<MemberController>

package com.webjjang.member.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.webjjang.member.service.MemberService;
import com.webjjang.member.vo.LoginVO;
import com.webjjang.member.vo.MemberVO;
import com.webjjang.util.file.FileUtil;

import lombok.extern.log4j.Log4j;

@Controller
@RequestMapping("/member")
@Log4j
public class MemberController {

	
	@Autowired
	private MemberService service;
	
	// 1. loginForm
	@GetMapping("/login.do")
	public String loginForm() throws Exception {
		log.info("login 폼으로 이동");
		return "member/login";
	}
	
	// 2. login
	@PostMapping("/login.do")
	public String login(LoginVO invo, HttpSession session, RedirectAttributes rttr) throws Exception {
		log.info("login 처리 - invo : " + invo);
		session.setAttribute("login", service.login(invo));
		rttr.addFlashAttribute("msg", "성공적으로 로그인이 되었습니다.");
		// 원래 main으로 redirect
		return "redirect:/board/list.do";
	}
	
	// 3. logout
	@GetMapping("/logout.do")
	public String logout(HttpSession session) throws Exception {
		log.info("logout 처리");
		// 로그아웃 처리 - session의 정보를 지운다.
		session.removeAttribute("login");	
		// 원래 main으로 redirect
		return "redirect:/board/list.do";
	}
	
	// 4. 회원가입 폼
	@GetMapping("/write.do")
	public String writeForm() throws Exception {
		return "member/write";
	}
	// 5. 회원가입 처리
	@PostMapping("/write.do")
	public String write(MemberVO vo, HttpServletRequest request, RedirectAttributes rttr) throws Exception {
		
		
		// 회원 사진을 저장할 위치
		String path = "/upload/member";
		
		// 서버에 파일 저장히기 → 서버에 저장된 파일명을 받아서 photo에 넣기
		vo.setPhoto(FileUtil.upload(path, vo.getPhotoFile(), request));
		
		// 회원 가입 처리
		service.write(vo);
		
		// redirect 하는 페이지에서 한 번만 사용되는 속성값을 전달할 수 있다. → session
		rttr.addFlashAttribute("msg", "성공적으로 회원가입이 되었습니다. \\n로그인 후 이용하세요.");
		
		return "redirect:/member/login.do";
	}
 }

회원가입 시, 프로필 사진을 따로 첨부하지 않은 회원은 noImage.jpg 라는 파일을

default로 지정하기 위해 Controller 부분에서 저렇게 설정하였다.

 

 

<Write.jsp>

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>회원가입</title>
<script type="text/javascript">
	$(function() {

		// id중복체크 변수, 비밀번호와 비밀범호 확인이 같은지 체크 변수 -> 전역 변수 선언
		var idCheck = false;
		var pwCheck = false;
		
		// datepicker 클래스 이벤트 - 적정한 옵션을 넣어서 초기화 시켜 준다. 기본 datepicker()로 사용 가능
		$(".datepicker").datepicker(
				{
					changeMonth : true,
					changeYear : true,
					dateFormat : "yy-mm-dd",
					dayNamesMin : [ "일", "월", "화", "수", "목", "금", "토" ],
					monthNamesShort : [ "1월", "2월", "3월", "4월", "5월", "6월",
							"7월", "8월", "9월", "10월", "11월", "12월" ]
				});

		var now = new Date();
		var startYear = now.getFullYear();
		var yearRange = (startYear - 100) + ":" + startYear;
		// datepicker - 초기값으로 셋팅하는 방법을 사용하면 2번째는 무시 당한다.
		//원래 있던 datepicker에 option을 추가하는 방법이다.
		$(".datepicker").datepicker("option", {
			"maxDate" : new Date(),
			"yearRange" : yearRange
		});

		// 		   $( ".datepicker" ).datepicker();

		// 아이디 체크 이벤트
		$("#id").keyup(function() {
			
			idCheck = false;
			
			var id = $("#id").val();
			// 공백문자 처리
			id = $.trim(id);
			$("#id").val(id);
			// alert("입력한 아이디 : " + id);

			// 4자 미만 처리
			if (id.length < 4) {
				$("#idCheckDiv").removeClass("alert-success");
				$("#idCheckDiv").addClass("alert-danger");
				$("#idCheckDiv").text("아이디는 4자 이상 영숫자이여야 합니다.");
				return;
			}

			// 20자 초과
			if (id.length > 20) {
				$("#idCheckDiv").removeClass("alert-success");
				$("#idCheckDiv").addClass("alert-danger");
				$("#idCheckDiv").text("아이디는 20자 이내 영숫자이여야 합니다.");
				return;
			}

			// 4~20 사이 아이디인 경우 - 중복 체크하러 간다. -> 서버로 간다. URL필요 -> 화면에 다른 데이터는 변하지 않으면서 일부 처리에 필요한 데이터만 변경 URL은 변경이 없다. ->Ajax
			// 중복안된 경우(아이디가 null인 경우) - 사용가능한 아이디 입니다., 중복이된 경우(아이디가 null이 아닌 경우) - 중복된 아이디입니다. -> 서버에서 처리
			// /member/idCheck -> *.do:sitemesh 위 아래 메뉴와 카피라이트가 붙는다.
			// result -> 서버에서 전달해 주는 데이터
			$("#idCheckDiv").load("/member/idCheck?id=" + id, function(result) {
				// 결과에 따른 배경색 처리
				// alert(result);
				// 클래스 다 지우기
				$("#idCheckDiv").removeClass("alert-success alert-danger");
				if (result.indexOf("가능한") == -1) {
					// 중복된 아이디인 경우 배경은 빨간색
					$("#idCheckDiv").addClass("alert-danger");
					idCheck = false;
				} else {
					// 사용가능한 아이디인 경우 배경은 파란색
					$("#idCheckDiv").addClass("alert-success");
					idCheck = true;
				}
			});

		}); //$("#id").keyup() 이벤트의 끝

		// 비밀번호 처리 이벤트
		$("#pw").keyup(function() {
			pwCheck = false;
			// $(this) == $("#pw")
			var pw = $(this).val();
			//alert(pw.length);
			// 4자 미만 처리
			if (pw.length < 4) {
				$("#pwCheckDiv").removeClass("alert-success");
				$("#pwCheckDiv").addClass("alert-danger");
				$("#pwCheckDiv").text("비밀번호는 4자 이상이여야 합니다.");
				return;
			}

			// 20자 초과 처리
			if (pw.length > 20) {
				$("#pwCheckDiv").removeClass("alert-success");
				$("#pwCheckDiv").addClass("alert-danger");
				$("#pwCheckDiv").text("비밀번호는 20자 이내이여야 합니다.");
				return;
			}

			// 4~20 사이 pw2와 같은지 체크
			var pw2 = $("#pw2").val();
			if (pw == pw2) {
				// 비밀번호와 비밀번호 확인이 같은 경우
				$("#pwCheckDiv, #pw2CheckDiv").removeClass("alert-danger");
				$("#pwCheckDiv, #pw2CheckDiv").addClass("alert-success");
				$("#pwCheckDiv, #pw2CheckDiv").text("적당한 비밀번호입니다.");
				pwCheck = true;
			} else {
				// 비밀번호와 비밀번호 확인이 같지 않은 경우
				$("#pwCheckDiv, #pw2CheckDiv").removeClass("alert-success");
				$("#pwCheckDiv, #pw2CheckDiv").addClass("alert-danger");
				$("#pwCheckDiv").text("비밀번호와 비밀번호 확인은 같아야 합니다.");
				if (pw2.length < 4)
					$("#pw2CheckDiv").text("비밀번호확인은 4자 이상이여야 합니다.");
				else if (pw2.length > 20)
					$("#pw2CheckDiv").text("비밀번호 확인은 20자 이내이여야 합니다.");
				else
					$("#pw2CheckDiv").text("비밀번호와 비밀번호 확인은 같아야 합니다.");
			}

		});
		// 비밀번호 확인 처리 이벤트
		$("#pw2").keyup(function() {
			pwCheck = false;

			// $(this) == $("#pw2")
			var pw2 = $(this).val();
			//alert(pw2.length);
			// 4자 미만 처리
			if (pw2.length < 4) {
				$("#pw2CheckDiv").removeClass("alert-success");
				$("#pw2CheckDiv").addClass("alert-danger");
				$("#pw2CheckDiv").text("비밀번호확인은 4자 이상이여야 합니다.");
				return;
			}

			// 20자 초과 처리
			if (pw2.length > 20) {
				$("#pw2CheckDiv").removeClass("alert-success");
				$("#pw2CheckDiv").addClass("alert-danger");
				$("#pw2CheckDiv").text("비밀번호 확인은 20자 이내이여야 합니다.");
				return;
			}

			// 4~20 사이 pw와 같은지 체크
			var pw = $("#pw").val();
			if (pw == pw2) {
				// 비밀번호와 비밀번호 확인이 같은 경우
				$("#pw2CheckDiv, #pwCheckDiv").removeClass("alert-danger");
				$("#pw2CheckDiv, #pwCheckDiv").addClass("alert-success");
				$("#pw2CheckDiv, #pwCheckDiv").text("적당한 비밀번호입니다.");
				pwCheck = true;
			} else {
				// 비밀번호와 비밀번호 확인이 같지 않은 경우
				$("#pwCheckDiv, #pw2CheckDiv").removeClass("alert-success");
				$("#pwCheckDiv, #pw2CheckDiv").addClass("alert-danger");
				$("#pw2CheckDiv").text("비밀번호와 비밀번호 확인은 같아야 합니다.");
				if (pw.length < 4)
					$("#pwCheckDiv").text("비밀번호확인은 4자 이상이여야 합니다.");
				else if (pw.length > 20)
					$("#pwCheckDiv").text("비밀번호 확인은 20자 이내이여야 합니다.");
				else
					$("#pwCheckDiv").text("비밀번호와 비밀번호 확인은 같아야 합니다.");
			}

		});
		// 비밀번호 처리 이벤트의 끝
		
		// 회원 가입 이벤트
		$("#writeForm").submit(function(){
			
			// alert("아이디 체크 : " + idCheck + "\n비밀번호 체크 : " + pwCheck);
			
			// 아이디 중복체크 - 사용 가능한 아이디 인지 확인
			if(!idCheck){
				alert("중복이 되지 않는 적당한 형식의 아이디를 사용하셔야만 합니다.");
				$("#id").focus();
				// form 전송을 무시시킨다.
				return false;
			}
			// 비밀번호와 비밀번호 확인
			if(!pwCheck){
				alert("비밀번호와 비밀번호 확인의 길이가 4~20이여야 하고 같아야 합니다.");
				$("#pw").focus();
				// form 전송을 무시시킨다.
				return false;
			}
			
			// form 전송을 무시시킨다. -> 나중에 꼭 주석처리해야만 한다.
			// return false;
		});

	});// $(function(){}) 의 끝
</script>
</head>
<body>
	<div class="container">
		<h1 style="text-align: center;">
			<strong>회원가입 폼</strong>
		</h1>
		<hr />
		<br>
		<form action="write.do" method="post" enctype="multipart/form-data"
			id="writeForm">
			<div class="form-group">
				<label for="id">아이디</label> <input name="id" id="id"
					class="form-control" required="required"
					pattern="[A-Za-z0-9]{4,20}" autocomplete="off">
				<div class="alert alert-danger" id="idCheckDiv">ID는 4자 이상의
					영ㆍ숫자를 입력하셔야 합니다.</div>
			</div>
			<div class="form-group">
				<label for="pw">비밀번호</label> <input type="password" name="pw"
					id="pw" class="form-control" required="required" pattern=".{4,20}"
					placeholder="비밀번호 입력">
				<div id="pwCheckDiv" class="alert alert-danger">PW는 4자 이상이어야
					합니다.</div>
			</div>
			<div class="form-group">
				<label for="pw2">비밀번호 확인</label> <input type="password" id="pw2"
					class="form-control" required="required" pattern=".{4,20}"
					placeholder="비밀번호 입력 확인">
				<div id="pw2CheckDiv" class="alert alert-danger">PW 확인은 4자
					이상이어야 합니다.</div>
			</div>
			<div class="form-group">
				<label for="name">이름</label> <input name="name" id="name"
					class="form-control" required="required" pattern="[가-힣]{2,10}">
			</div>
			<div class="form-group">
				<label>성별</label>
				<div>
					<label class="radio-inline"><input type="radio"
						name="gender" value="남자" checked="checked" /> 남자</label><label
						class="radio-inline"> <input type="radio" name="gender"
						value="여자" /> 여자
					</label>
				</div>
			</div>
			<div class="form-group">
				<label>생년월일</label> <input name="birth" id="birth"
					class="form-control datepicker" autocomplete="off">
			</div>
			<div class="form-group">
				<label>연락처</label> <input name="tel" id="tel" class="form-control"
					placeholder="예)010-1111-2222">
			</div>
			<div class="form-group">
				<label>이메일</label> <input name="email" id="email"
					class="form-control" placeholder="예)test@naver.com">
			</div>
			<div class="form-group">
				<label>사진</label> <input type="file" name="photoFile" id="photoFile"
					class="form-control">
			</div>
			<div class="text-center">
				<button class="btn btn-primary">가입</button>
				<button class="btn btn-default" type="button"
					onclick="history.back()">취소</button>
			</div>
		</form>
	</div>
</body>
</html>

 

함수를 이용했으면 코드가 더 간결해졌을텐데, 그러지 못해서 JS 부분의 코드가 좀 길어졌다.

JS 부분은 나도 완벽히 이해하지 못했지만 저 코드로 유효성 검사를 실시할 수 있고

비밀번호와 비밀번호 확인칸에 입력한 데이터 값이 같은지를 검사할 수 있다.

유효성에 적합하지 않는다면 false 값이 리턴돼 페이지 이동을 하지 않는다.

그리고 Ajax를 이용해 ID 중복 검사도 실시할 수 있다.

 

여기서 주의깊게 볼 것은 생년월일을 jQuery의 datepicker를 이용해서 데이터를 받고 있다.

 

jQuery의 datepicker

 음 솔직히 input type을 date로 작성하는 것과 datepicker를 이용하는 것의 큰 차이점을 모르겠다.

datepicker도 좋지만 나라면 그냥 input type을 date 타입으로 지정해서 데이터 입력하는 걸로 할 듯...

 

input 태그 속성 중에 하나인 autocomplete="off" 기능은 굉장히 좋은 거 같다.데이터를 입력할 때, 밑에 자동 완성 되는 목록들이 뜨지 않게 하는 속성.

 

그리고 Ajax를 이용한 ID 중복 체크를 하는 기능도 배웠는데Ajax를 알기전까지는 굉장히 어렵게 생각했었는데 막상 듣고보니 그렇게 별 거 아니었던...나는 Ajax가 무슨 프로그램이나 라이브러리 쯤일줄 알았는데 그냥 jQuery의 일부분이었다.

 

Ajax는 URL에는 변경이 없고 화면에 다른 데이터는 변하지 않으면서일부 처리에 필요한 데이터만 변경하는 것이라고 했다.

 

https://www.w3schools.com/jquery/jquery_ajax_load.asp

 

jQuery AJAX load() Method

W3Schools offers free online tutorials, references and exercises in all the major languages of the web. Covering popular subjects like HTML, CSS, JavaScript, Python, SQL, Java, and many, many more.

www.w3schools.com

더 자세한건 위의 링크에서 확인할 수 있다.

 

jQuery AJAX load 메서드를 이용해 ID 중복 체크를 실시했다.

 

	$("#idCheckDiv").load("/member/idCheck?id=" + id, function(result) {
				// 결과에 따른 배경색 처리
				// alert(result);
				// 클래스 다 지우기
				$("#idCheckDiv").removeClass("alert-success alert-danger");
				if (result.indexOf("가능한") == -1) {
					// 중복된 아이디인 경우 배경은 빨간색
					$("#idCheckDiv").addClass("alert-danger");
					idCheck = false;
				} else {
					// 사용가능한 아이디인 경우 배경은 파란색
					$("#idCheckDiv").addClass("alert-success");
					idCheck = true;
				}
			});

JS 부분에서 이렇게 작성하면 URL은 변경되지 않으면서 ID 중복 체크를 할 수 있다.

사실 코드 전체에 대한 이해도는 거의 1-2 에 가깝다. 

Ajax 사용법에 대해서 30분만에 짧게 배웠고 다 이해하기에는 확실히 무리가 있다.

 

아무튼 배우긴 했으니, 개인 프로젝트 준비할 때 게시판에 댓글 기능을 꼭 구현해보려고 한다.

 

 

댓글