회원가입 로직
사용자가 회원가입 시 너무 많은 입력사항이 있을 경우 귀찮아 할 수 있다. 그래서 너무많은 정보를 요청하는 것은 옳지 않다. 하지만 사이트에 필요한 최소한의 정보는 반드시 요청해야한다. 유효성체크를 통해 홈페이지만의 회원가입 규칙을 정하고 규칙을 어긋난 회원가입을 막는다. ID중복체크를 통해 Id의 고유값(not null)을 가져오고 이 id와 sequence로 주어지는 user_no을 통해 사이트의 여러기능을 사용 할 때 회원정보를 불러온다.
SQL
설계를 끝내고 코드를 짜려면 회원가입에 필요한 data를 DB에 저장하기 위해 users 테이블을 만들어야 한다.
CREATE TABLE users(
user_no NUMBER PRIMARY KEY NOT null,
user_type NUMBER,
id varchar2(50),
nickname varchar2(50),
email varchar2(50),
password varchar2(50),
name varchar2(50),
joindate DATE,
gender varchar2(50),
zipcode varchar2(50),
addr varchar2(500),
addr_detail varchar2(500),
user_level varchar2(50),
user_auth NUMBER,
file_no NUMBER
);
Primary key에 시퀀스넘버도 주면 테이블 완성이다.
CREATE SEQUENCE seq_user_no INCREMENT BY 1 START WITH 1;
SELECT seq_user_no.nextval FROM dual;
SELECT seq_user_no.currval FROM dual;
XML
먼저 DB에 직접적으로 명령을 수행할 쿼리문을 작성해준다.
<?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="mapper.users">
<!-- 회원가입 -->
<insert id="insertUsers"
parameterType="com.kosta.petner.bean.Users">
<![CDATA[
INSERT INTO USERS(user_no,
user_type,
id,
nickname,
email,
password,
name,
joindate,
gender,
zipcode,
addr,
addr_detail,
user_level,
user_auth)
values(seq_user_no.nextval,
#{user_type},
#{id},
#{nickname},
#{email},
#{password},
#{name},
sysdate,
#{gender},
#{zipcode},
#{addr},
#{addr_detail},
'BRONZE',
1)
]]>
</insert>
<!-- 중복체크 -->
<select id="selectId" parameterType="String"
resultType="com.kosta.petner.bean.Users">
<![CDATA[
select * from users where id =#{id}
]]>
</select>
insert를 통해 회원가입 로직에 맞게 data를 집어넣도록 하고, id에 대한 중복값을 막기위해 id로 유저정보를 조회할 수 있는 쿼리문 또한 함께 작성한다.
DAO/ DAOImpl
public interface UsersDAO {
// 회원가입
void insertUsers(Users users) throws Exception;
// 아이디 중복체크
Users selectId(String id) throws Exception;
@Repository
public class UsersDAOImpl implements UsersDAO{
@Autowired
private SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
//회원가입
@Override
public void insertUsers(Users users) throws Exception {
sqlSession.insert("mapper.users.insertUsers", users);
}
//아이디 중복체크
@Override
public Users selectId(String id) {
return sqlSession.selectOne("mapper.users.selectId", id);
}
앞으로 계속 쓸 세션을 autowire해주고, 쿼리문을 호출 할 DAO에 회원가입 메서드를 만들어준다.
(이제 DAO를 호출 하면 쿼리문이 실행 되는 것)
Service/ ServcieImpl
컨트롤러와 DAO를 연결시켜 줄 service부분을 작성한다.
public interface UsersService {
// 회원가입
public void joinUsers(Users users) throws Exception;
// 중복체크
public boolean isDoubleId(String id) throws Exception;
@Service
public class UsersServiceImpl implements UsersService {
@Autowired
UsersDAO usersDAO;
@Autowired
BCryptPasswordEncoder bcryptPasswordEncoder;
@Override
public void joinUsers(Users users) throws Exception {
Users usersvo = usersDAO.selectId(users.getId());
if(usersvo!=null) throw new Exception("아이디 중복");
usersDAO.insertUsers(users);
}
@Override
public boolean isDoubleId(String id) throws Exception {
//userDAO를 통해 xml에서 가져온 id 값을 users에 담는다.
Users users = usersDAO.selectId(id);
if(users==null) return false;
return true;
}
impl은 부모속성을 물려받은 자식클래스이다. 그렇기때문에 어노테이션을 꼭 해줄것!
그냥 DAO의 insertUsers메서드를 호출 할 수도 있지만, 아이디 중복을 막기위해 회원정보를 조회하고,
만약 회원정보에 id값이 있다면 exception(오류)를 발생하게 한다.
xml과 dao에서 ID를 통해 회원목록을 불러온 중복체크 메서드는 boolean으로 참 거짓 값을 가져오게 한다.
Controller
package com.kosta.petner.controller;
import java.util.Date;
import javax.inject.Inject;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.util.WebUtils;
//import com.kosta.petner.bean.ChatSession;
import com.kosta.petner.bean.Users;
import com.kosta.petner.dao.UsersDAO;
import com.kosta.petner.service.UsersService;
@Controller
public class UsersController {
@Autowired
BCryptPasswordEncoder bcryptPasswordEncoder;
@Inject
UsersService usersService;
@Autowired
HttpSession session;
@Autowired
UsersDAO usersDAO;
//회원가입 이동
@RequestMapping(value = "/join", method = RequestMethod.GET)
String joinForm() {
return "users/join/joinForm";
}
//회원가입
@RequestMapping(value = "/joinpet", method = RequestMethod.POST)
String join(@ModelAttribute Users users, Model model) {
System.out.println("암호화 전 : " + users);
try{
String passBcrypt = bcryptPasswordEncoder.encode(users.getPassword());
users.setPassword(passBcrypt);
usersService.joinUsers(users);
System.out.println("암호화 후 : " + users);
} catch(Exception e) {
e.printStackTrace();
model.addAttribute("err", "회원가입 오류");
model.addAttribute("page", "err");
}
return "users/join/joinSuccess";
}
//중복체크
@ResponseBody
@RequestMapping(value = "/checkId", method = RequestMethod.POST)
String checkId(@RequestParam("id") String id, Model model) {
try {
if(usersService.isDoubleId(id)) return "true";
}catch(Exception e) {
e.printStackTrace();
model.addAttribute("page","err");
}
return "false";
}
역시나 컨트롤러 annotation과 import를 잊지말자! 우선은 회원가입폼으로 이동하는 Get방식의 mapping과 회원가입이 실행되는 Post방식의 mapping을 작성한다.
회원가입 컨트롤러에서는 객체를 가져오기위해 ModelAttribute를 쓴다. 중복체크에서는 id값만 가져오기 때문에 requestParam을 썼다.
usersService.joinUsers(users);를 통해 xml-DAO-service로 연결된 회원가입 메서드가 실행된다.
(*Bcrypt는 스프링시큐리티에서 제공하는강력한 암호화 방법이다. 지금은 안써도되니 주석처리 해도 된다. 나중에 설명)
//암호화 부분
//String passBcrypt = bcryptPasswordEncoder.encode(users.getPassword());
// users.setPassword(passBcrypt);
JSP
JoinForm.jsp 전체코드
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="resources/css/common.css">
<link rel="stylesheet" href="resources/css/form_component.css">
<script src="https://kit.fontawesome.com/064a55beb6.js" crossorigin="anonymous"></script>
<title>회원가입</title>
<script src= "https://code.jquery.com/jquery-3.4.1.js"></script>
<script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
<script>
function check() {
//id유효성검사
var id =$("#id").val(); //id 입력 값
var reg_id = /^[A-Za-z]{1}[A-Za-z0-9_-]{3,19}$/; //영문+숫자조합(4~20까지)
var checked_id = $("input[name='checked_id']").val(); //중복체크 여부
if(id == "") {
$('#checkid-msg').text("아이디를 입력해주세요").css("color", "red");
$("#id").focus();
return false;
}if(!reg_id.test(id)){
$('#checkid-msg').text("아이디는 영문과 숫자조합만 가능합니다.(4~20자)").css("color", "red");
$("#id").focus();
return false;
}if(checked_id==''){
$('#checkid-msg').text("아이디 중복확인을 먼저 해주세요").css("color", "red");
$("#id").focus();
return false;
}else{
$('#checkid-msg').text("사용가능한 아이디입니다.").css("color", "green");
}
//비밀번호 유효성검사
var password =$("#password").val(); //비밀번호 입력값
var ck_password = $("#ck_password").val(); //비밀번호 체크 입력값
var acceptPass = /^(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,16}$/;
if(!password){
$('#checkpass-msg').text("비밀번호를 입력해주세요").css("color", "red");
$("#password").focus();
return false;
}if(!acceptPass.test(password)){
$('#checkpass-msg').text("비밀번호는 영어+숫자+특수문자를 조합해야 합니다(8자 이상)").css("color", "red");
$("#password").focus();
return false;
}if(!ck_password){
$('#checkpass-msg').text("비밀번호 확인을 입력해주세요").css("color", "red");
$("#ck_password").focus();
return false;
}if(password !== ck_password){
$('#checkpass-msg').text("비밀번호가 일치하지 않습니다").css("color", "red");
$("#password").focus();
return false;
}else{
$('#checkpass-msg').text("사용가능한 비밀번호입니다.").css("color", "green");
}
//email유효성검사 +email 합치기
var reg_email = /^[a-zA-Z]{1}[a-zA-Z0-9_]{4,11}$/;
var email_id =$("#email_id").val();
var email_domain =$("#email_domain").val();
var email ="";
if(!email_id){
$('#checkmail-msg').text("이메일을 입력해주세요").css("color", "red");
$("#email_id").focus();
return false;
}if(!reg_email.test(email_id)){
$('#checkmail-msg').text("올바른 이메일을 입력해주세요").css("color", "red");
$("#email_id").focus();
return false;
}else{
$('#checkmail-msg').text("사용가능한 이메일입니다.").css("color", "green");
}
email = email_id+"@"+email_domain; //콤마뺴기
$("#email").val(email);
//이름 유효성 검사
var reg_name = /^[가-힣]+$/;
var name = $("#name").val();
if(!name){
$('#checkname-msg').text("이름을 입력해주세요").css("color", "red");
$("#name").focus();
return false;
} if(!reg_name.test(name)){
$('#checkname-msg').text("이름은 한글로 입력해주세요").css("color", "red");
$("#name").focus();
return false;
}else{
$('#checkname-msg').text("사용가능한 이름입니다.").css("color", "green");
}
//별명 유효성검사
var reg_nickname = /^[가-힣]{2,6}$/;
var nickname = $("#nickname").val();
if(!nickname){
$('#checknickname-msg').text("별명을 입력해주세요").css("color", "red");
$("#nickname").focus();
return false;
} if(!reg_name.test(nickname)){
$('#checknickname-msg').text("별명은 한글로 지어주세요(2~6)").css("color", "red");
$("#nickname").focus();
return false;
}
//주소 유효성검사
var add3 = $("#add3").val();
if(!add3){
$('#checkaddress-msg').text("상세주소를 입력해주세요").css("color", "red");
$("#add3").focus();
return false;
}else{
return true;
}
} //check()끝
//주소검색 API
function Zipcode() {
new daum.Postcode({
oncomplete: function(data) {
// 팝업에서 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분.
// 각 주소의 노출 규칙에 따라 주소를 조합한다.
// 내려오는 변수가 값이 없는 경우엔 공백('')값을 가지므로, 이를 참고하여 분기 한다.
var addr = ''; // 주소 변수
var extraAddr = ''; // 참고항목 변수
//사용자가 선택한 주소 타입에 따라 해당 주소 값을 가져온다.
if (data.userSelectedType === 'R') { // 사용자가 도로명 주소를 선택했을 경우
addr = data.roadAddress;
} else { // 사용자가 지번 주소를 선택했을 경우(J)
addr = data.jibunAddress;
}
// 사용자가 선택한 주소가 도로명 타입일때 참고항목을 조합한다.
if(data.userSelectedType === 'R'){
// 법정동명이 있을 경우 추가한다. (법정리는 제외)
// 법정동의 경우 마지막 문자가 "동/로/가"로 끝난다.
if(data.bname !== '' && /[동|로|가]$/g.test(data.bname)){
extraAddr += data.bname;
}
// 건물명이 있고, 공동주택일 경우 추가한다.
if(data.buildingName !== '' && data.apartment === 'Y'){
extraAddr += (extraAddr !== '' ? ', ' + data.buildingName : data.buildingName);
}
// 표시할 참고항목이 있을 경우, 괄호까지 추가한 최종 문자열을 만든다.
if(extraAddr !== ''){
extraAddr = ' (' + extraAddr + ')';
}
// 조합된 참고항목을 해당 필드에 넣는다.
document.getElementById("add4").value = extraAddr;
} else {
document.getElementById("add4").value = '';
}
// 우편번호와 주소 정보를 해당 필드에 넣는다.
$("#add1").val(data.zonecode);
$("#add2").val(addr);
}
}).open();
}
$(function () {
$('#joinForm').submit(function() {
}); //유효성체크 끝
//중복아이디 = ajax 쓰는이유: data를 서버에 주고 다시 받아올때 비동기방법을 씀
$('#doubleId').click(function() {
var id = $("#id").val(); //사용자가 입력한 아이디를 변수 id에 담음
var reg_id = /^[A-Za-z]{1}[A-Za-z0-9_-]{3,19}$/;
$("input[name=checked_id]").val('y'); //중복체크 yes
if(id=="") {
$('#checkid-msg').text("아이디를 입력해주세요").css("color", "red");
$("#id").focus();
return;
}if(!reg_id.test(id)){
$('#checkid-msg').text("아이디는 영문과 숫자조합만 가능합니다.(4~20자)").css("color", "red");
$("#id").focus();
return;
}
$.ajax({
type:"post",
url:"http://localhost:8088/petner/checkId",
data:{id:id}, //id(key):id(value)
success:function(data,textStatus) {
if(data=="true") {
$('#checkid-msg').text("중복아이디 입니다").css("color", "red");
}else{
$('#checkid-msg').text("사용 가능한 아이디입니다").css("color", "green");
}
}
}) //ajax 끝
}); // 중복체크 끝
});//function ready 끝
</script>
</head>
</head>
<body>
<div id="wrapper">
<div class="login_form w50">
<h3 class="form_title">회원가입</h3>
<form id= "joinForm" action="./joinpet" method="POST" class="join_form" onsubmit = "return check()">
<div class="f_row">
<p class="fc_title">🙊 펫트너 가족되기 🙊</p>
<p class="tip"><i class="fa-solid fa-asterisk"></i> 회원가입후 추가로 정보를 등록할 수 있어요</p>
<div class="flex_col">
<label class="fcRadio1 mb10" style= "width:800px;"><input type="radio" name="user_type" id="user_type" value= "2" checked><span>보호자로 등록하기 ( 펫시터를 찾고있어요 ) </span></label>
<label class="fcRadio1 " style= "width:800px;"><input type="radio" name="user_type" value= "1"><span>펫시터로 등록하기 ( 돌봐줄 동물을 찾고있어요 )</span></label>
</div>
</div>
<!-- TEXT/PASSWORD -->
<div class="f_row">
<p class="fc_title">아이디</p>
<p class="flex_agn_center">
<input type="hidden" name="checked_id" value="">
<input class="mr12" type="text" placeholder="ID" name ="id" id="id"/>
<span class="pet_btn second_btn transition02" id="doubleId">중복확인</span>
</p>
<p><small id="checkid-msg" class="form-error"></small></p>
</div>
<div class="f_row flex_col" style= "width:200px;">
<p class="fc_title">비밀번호</p>
<input class="mb10" type="password" name ="password" id="password" placeholder="비밀번호입력"/>
<input type="password" name="ck_password" id="ck_password" placeholder="비밀번호확인"/>
<p><small id="checkpass-msg" class="form-error"></small></p>
</div>
<div class="f_row">
<p class="fc_title">이메일</p>
<p class="flex_agn_center">
<input type ="hidden" id= "email" name= "email"/>
<input type="text" placeholder="" id="email_id" name=""/>
<span style="margin:0 10px;">@</span>
<select class="fcc_select" name="" id="email_domain">
<option value="naver.com">naver.com</option>
<option value="gmail.com">gmail.com</option>
<option value="hanmail.net">hanmail.net</option>
<option value="hotmail.com">hotmail.com</option>
<option value="korea.com">korea.com</option>
<option value="nate.com">nate.com</option>
<option value="yahoo.com">yahoo.com</option>
</select>
</p>
<p><small id="checkmail-msg" class="form-error"></small></p>
</div>
<div class="f_row">
<p class="fc_title">이름</p>
<input type="text" id="name" name="name"/>
<p><small id="checkname-msg" class="form-error"></small></p>
</div>
<div class="f_row">
<p class="fc_title">나의별명</p>
<input type="text" id="nickname" name="nickname"/>
<p><small id="checknickname-msg" class="form-error"></small></p>
</div>
<div class="f_row">
<p class="fc_title">성별</p>
<label class="fcRadio1 mr12">
<input type="radio" name="gender" id="gender" value="male" checked><span>남자</span>
</label>
<label class="fcRadio1 mr12">
<input type="radio" name="gender" value="female"><span>여자</span>
</label>
</div>
<div class="f_row">
<p class="fc_title">주소입력</p>
<div class="flex_col">
<p class="mb10">
<input class="mr12" type="text" id="add1" name="zipcode" readonly/><input type="button" class="pet_btn second_btn transition02" onclick="Zipcode()" value= "주소찾기"/>
</p>
<input class="mb10" type="text" id ="add2" name="addr" readonly/>
<input class="mb10" type="text" id ="add3" name="addr_detail" placeholder="상세주소입력"/>
<input type="hidden" id="add4" name="address" placeholder="참고항목">
<p><small id="checkaddress-msg" class="form-error"></small></p>
</div>
<input type="hidden" name="joindate" id="joindate"/>
</div>
<input type="submit" class="pet_btn submit_btn transition02" value= "회원가입하기"/>
</form>
</div>
</div>
</body>
회원가입은 유효성체크, 주소API, ID중복체크, css 등 많은 부분이 구현되어 있어 코드가 굉장히 길어졌다.
(* 단순 가입만 원한다면 <body></body>안에 있는 코드만 넣어도 된다.)
유효성체크는 여러 정규식을 통해 회원가입 규칙에 맞도록 작성해준다. 유효성체크는 여러번 검사를 통해 꼼꼼히 작성하였고 통과하지 못 할 경우 onsubmit() 태그를 통하여 다시 submit을 막는다.
주소API는 카카오톡(또는 다음)에서 제공하는 API를 썼고 내 회원가입 양식에 맞게 여러 코드를 제공하고 있다.
중복체크는 Ajax를 통해 실제 data를 와 대조 후 Id가 있으면 중복에 걸리게 하였다.
모든 유효성체크를 지나면 submit이 실행되고 회원가입 완성 폼으로 넘어간다.
(email은 유효성체크 뿐만아니라 email_id와 email_domain으로 나뉜 두개의 데이터를 email 이라는 id를 주어 합쳐야 한다.
그렇지 않으면 id, domain 식의 두 데이터가 칼럼 하나에 저장된다.) <조금 애를 먹은 부분
JoinSuccess.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:set var="cssPath" value="${pageContext.request.contextPath}/resources/css"/>
<c:set var="imgPath" value="${pageContext.request.contextPath}/resources/images"/>
<!DOCTYPE html>
<html>
<head>
<c:import url='/WEB-INF/views/include/common_head.jsp'/>
<title>회원가입 성공!</title>
</head>
<style>
#findId input[type=text]{width:100%;}
.desc{text-align:center; margin-bottom:35px;}
.find_id{width:120px !important; text-align:center;}
.flex_between{flex-wrap:nowrap}
.submit_btn{position: relative; top:180px;}
</style>
<body>
<div id="wrapper">
<!-- HEADER BASIC -->
<c:import url='/WEB-INF/views/include/header.jsp'/>
<!-- CONTAINER -->
<div class="container">
<div class="w45">
<h3 class="form_title">회원가입 성공</h3>
<div class="desc">
<c:choose>
<c:when test="${users.user_type ==2}">
<p class="mb10" style="color:#ADD8E6;">[💁🏻♂️ 보호자]</p>
<p class="mb10" style="color: orange;">🐶 ${users.id}님의 가입을 축하드립니다! 🐱</p>
</c:when>
<c:otherwise>
<p class="mb10"style="color:#ADD8E6;">[🙋 펫시터]<p>
<p class="mb10" style="color: orange;">🐶 ${users.id}님의 가입을 축하드립니다! 🐱</p>
</c:otherwise>
</c:choose>
</div>
</div>
</div>
<!-- FOOTER BASIC -->
<c:import url='/WEB-INF/views/include/footer.jsp'/>
</div>
</body>
</html>
구현 화면


