메인화면의 배너 부분은 swiper.js를 이용해 슬라이드 효과를 주었습니다.
<%@page import="com.fasterxml.jackson.annotation.JsonInclude.Include"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>회원가입</title>
<!-- Favicon-->
<link rel="icon" type="image/x-icon" href="/resources/assets/favicon.ico" />
<!-- Core theme CSS (includes Bootstrap)-->
<link href="/resources/css/styles.css" rel="stylesheet" />
<link href="/resources/css/user/join.css" rel="stylesheet" />
</head>
<body>
<!-- Navigation-->
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container px-4 px-lg-5">
<a class="navbar-brand" href="/">My shop</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0 ms-lg-4">
<li class="nav-item"><a class="nav-link active" aria-current="page" href="/">Home</a></li>
<li class="nav-item"><a class="nav-link" href="#!">About</a></li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" id="navbarDropdown" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">Shop</a>
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
<li><a class="dropdown-item" href="#!">All Products</a></li>
<li><hr class="dropdown-divider" /></li>
<li><a class="dropdown-item" href="#!">Popular Items</a></li>
<li><a class="dropdown-item" href="#!">New Arrivals</a></li>
</ul>
</li>
</ul>
<form class="d-flex">
<ul class="navbar-nav me-auto mb-2 mb-lg-0 ms-lg-4">
<li class="nav-item"><a href="/user/login" class="nav-link">로그인</a></li>
<li class="nav-item"><a href="/user/join"class="nav-link">회원가입</a></li>
</ul>
</form>
</div>
</div>
</nav>
<header>
<div class="container px-4 px-lg-5 my-5">
<div class="text-center text-white">
<h1 class="display-4 fw-bolder" style="color:#000000;">회원가입</h1>
<p class="lead fw-normal text-white-50 mb-0"></p>
</div>
</div>
</header>
<div class="container mb-3">
<form name="joinForm" id="joinForm" method="post">
<table id="container">
<tr>
<td id="result" colspan="2"></td>
</tr>
<tr>
<th><label for="useremail">아이디</label></th>
<td><input type="text" name="useremail" id="useremail" placeholder="example@000.com" oninput="checkId()">
<br><span class="idCheck" id="idCheck"></span></td>
</tr>
<tr>
<th><label for="userpw">비밀번호</label></th>
<td><input type="password" name="userpw" id="userpw" placeholder="8자리 이상 써주세요" oninput="checkPw()">
<br><span class ="pwCheck"></span></td>
</tr>
<tr>
<th><label for="userpw_re">비밀번호 확인</label></th>
<td><input type="password" name="userpw_re" id="userpw_re" oninput="checkPW()">
<br><span class="pwRe"></span></td>
</tr>
<tr>
<th><label for="username">이름</label></th>
<td><input type="text" name="username" id="username"></td>
</tr>
<tr class="zipcode_area">
<th>우편번호</th>
<td>
<input readonly name="postnum" type="text" id="postnum" placeholder="우편번호"><input type="button" onclick="DaumPostcode()" value="우편번호 찾기">
</td>
</tr>
<tr class="addr_area">
<th>주소</th>
<td><input readonly name="addr" type="text" id="addr" placeholder="주소"></td>
</tr>
<tr>
<th>상세주소</th>
<td><input name="detailaddress" type="text" id="detailaddress" placeholder="상세주소"></td>
</tr>
<tr>
<th>참고항목</th>
<td><input readonly name="seealso" type="text" id="seealso" placeholder="참고항목"></td>
</tr>
<tr>
<th><label for="userphone">전화번호</label></th>
<td><input type="text" name="userphone" id="userphone" placeholder="전화번호입력"><input type="button" value="인증번호 받기" id="randomnum" style="width:110px;"></td>
</tr>
<tr>
<th colspan="2"><input type="text" name="phonecheck" id="phonecheck" placeholder="인증번호 입력" style="display: block; margin:0 auto;"><input type="button" value="인증번호 확인" id="randomnumcheck" style="width:110px;"></th>
<td><span class ="numselect"></span></td>
</tr>
<tr>
<th colspan="2">
<input type="submit" id="joinSubmit" value="가입 완료">
</th>
</tr>
</table>
</form>
</div>
<form id="phoneNumber" action="/user/message">
</form>
<!-- Footer-->
<footer>
<div class="container"><p class="m-0 text-center text-white" style="color:#000000 !important;">Copyright © Your Website 2022</p></div>
</footer>
<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 src="/resources/js/join.js"></script>
</body>
</html>
아이디는 ajax를 사용하여 사용자가 입력시 새로고침 없이 입력한 값이 이미 존재하는
아이디인지 아닌지 확인할 수 있도록 했습니다.
또한 이메일 형식으로만 가입을 받을 것 이기 때문에
이메일 정규식을 사용하여 이메일인지 아닌지 구분했습니다.//아이디 중복체크 및 이메일 정규식 function checkId(){ let useremail = $("#useremail").val(); let idCheck = document.getElementsByClassName("idCheck"); let regEmail = /^[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*\.[a-zA-Z]{2,3}$/; $.ajax({ url:'/user/checkId', //데이터를 주고받을 파일 주소 type:'post', //post 방식으로 전송 data:{useremail:useremail}, //위의 변수에 담긴 데이터를 전송해준다. dataType:'JSON', //JSON 타입으로 값을 담아온다. success : function(cnt){ //파일 주고받기가 성공했을 경우. data 변수 안에 값을 담아온다. if(useremail.length < 1 && cnt != 1){ idCheck[0].textContent = "아이디를 입력해주세요"; } else if(!regEmail.test(useremail)){ idCheck[0].textContent = "이메일 형식으로 입력해주세요."; } else if(cnt != 1 && regEmail.test(useremail)){ idCheck[0].textContent = "사용할 수 있는 아이디입니다."; } else{ idCheck[0].textContent = "중복된 아이디입니다."; } }, error:function(){ console.log("처리실패") } }); }
이메일 형식이 아닐시
이메일 형식이며 중복된 아이디가 없을 시
//비밀번호 유효성체크 function checkPw(){ let userpw = $("#userpw").val(); let checkNumber = userpw.search(/[0-9]/g); let checkEnglish = userpw.search(/[a-z]/ig); let pwCheck = $(".pwCheck"); let userpwCheck = $("#userpw_re").val(); if(userpw.length < 1){ pwCheck[0].textContent = "비밀번호를 입력해주세요"; } else if(!/^(?=.*[a-zA-Z])(?=.*[~!@#$%^*+=-])(?=.*[0-9]).{8,25}$/.test(userpw)){ pwCheck[0].textContent = "숫자+영문자+특수문자 조합으로 8자리 이상 사용해야 합니다."; } else if(checkNumber <0 || checkEnglish <0){ pwCheck[0].textContent = "숫자와 영문자를 혼용하여야 합니다."; } else if(/(\w)\1\1\1/.test(userpw)){ pwCheck[0].textContent = "같은 문자를 4번 이상 사용하실 수 없습니다."; } else{ pwCheck[0].textContent = ""; } } //비밀번호 확인 function checkPW(){ let userpwCheck = $("#userpw_re").val(); let userpw = $("#userpw").val(); let pwre = $(".pwRe"); if(userpwCheck < 1){ pwre[0].textContent = "비밀번호 확인해주세요"; } else if(userpwCheck != userpw){ pwre[0].textContent = "비밀번호가 틀립니다"; } else{ pwre[0].textContent = ""; } }
비밀번호 또한 유효성 체크를 통해 사용자에게 text로 보여지게 했습니다.
비밀번호를 알맞게 입력했다면 text에 빈 문자열을 넣어 사용자에게 아무 text도
보이지 않도록 했습니다.
주소찾기 api는 다음 주소찾기 api를 사용하였습니다.
//다음 주소찾기 api
function DaumPostcode() {
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("seealso").value = extraAddr;
} else {
document.getElementById("seealso").value = '';
}
// 우편번호와 주소 정보를 해당 필드에 넣는다.
document.getElementById('postnum').value = data.zonecode;
document.getElementById("addr").value = addr;
// 커서를 상세주소 필드로 이동한다.
document.getElementById("detailaddress").focus();
}
}).open();
}
다음 주소api를 사용하기 위해서는 jsp파일에 해당 스크립트를 한줄 추가해줘야 합니다.
<script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
우편번호 찾기를 클릭시 다음과 같은 창이뜨게됩니다.
해당 창에서 자신의 주소를 찾아 선택하게되면 자동으로 주소와 참고항목에 입력됩니다.
문자인증을 구현하기 위해 Coolsms API를 사용하였습니다.
Coolsms 사용법
전화번호를 입력후 인증번호 받기를 클릭하면 입력한 휴대폰으로 아래 사진처럼 문자가 오게되고
인증번호 입력후 인증번호 확인버튼을 클릭할 시 아이디 중복체크와 마찬가지로 ajax를 통해
새로고침 없이 사용자에게 결과를 보여지도록 하였습니다.
//전화번호 문자인증
$("#randomnum").on("click",function(e){
let userphone = $("#userphone").val();
let numselect = $(".numselect");
let regPhone = /^01([0|1|6|7|8|9])-?([0-9]{3,4})-?([0-9]{4})$/;
if(userphone == "" || !regPhone.test(userphone)){
alert("전화번호를 제대로 입력해주세요");
return false;
}
$.ajax({
type:'post',
url:'/user/message',
data:{userphone:userphone},
dataType:'JSON',
success : function(num){ //파일 주고받기가 성공했을 경우. 변수 안에 값을 담아온다.
$("#randomnumcheck").on("click",function(e){
let phonecheck = $("#phonecheck").val();
e.preventDefault();
if($.trim(num) == phonecheck){
alert("전화번호 인증 완료");
numselect[0].textContent = "인증 완료";
}
else if($.trim(num) != phonecheck){
alert("인증번호가 틀립니다.");
numselect[0].textContent = "인증번호가 틀립니다.";
}
})
},
error:function(){
console.log("처리실패")
}
});
})
인증번호가 맞다면 아래 사진과 같이 인증 완료라는 텍스트가 뜨고
실패했다면 인증 실패라는 텍스트가 뜹니다!
그 후 가입 완료 버튼을 클릭시 아래 유효성을 통과한다면 회원가입이 완료됩니다.
//회원가입 유효성
$("#joinSubmit").on("click",function(e){
const joinForm = $("#joinForm");
let numselect = $(".numselect");
let useremail = $("#useremail").val();
let idCheck = document.getElementsByClassName("idCheck");
let userphone = $("#userphone").val();
let phonecheck = $("#phonecheck").val();
let userpw = $("#userpw").val();
let userpwCheck = $("#userpw_re").val();
let username = $("#username").val();
let postnum = $("#postnum").val();
let detailaddress = $("#detailaddress").val();
let checkNumber = userpw.search(/[0-9]/g);
let checkEnglish = userpw.search(/[a-z]/ig);
let regEmail = /^[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*\.[a-zA-Z]{2,3}$/;
e.preventDefault();
if(useremail == ""){
alert("아이디를 입력해주세요");
}
else if(idCheck[0].textContent == "중복된 아이디입니다."){
alert("중복된 아이디입니다.");
}
else if(!regEmail.test(useremail)){
alert("이메일 형식으로 작성해주세요");
}
else if(userpw == ""){
alert("비밀번호를 입력해주세요");
}
else if(!/^(?=.*[a-zA-Z])(?=.*[~!@#$%^*+=-])(?=.*[0-9]).{8,25}$/.test(userpw)){
alert('숫자+영문자+특수문자 조합으로 8자리 이상 사용해야 합니다.');
return false;
}
else if(checkNumber <0 || checkEnglish <0){
alert("숫자와 영문자를 혼용하여야 합니다.");
return false;
}
else if(/(\w)\1\1\1/.test(userpw)){
alert('같은 문자를 4번 이상 사용하실 수 없습니다.');
return false;
}
else if(userpwCheck == ""){
alert("비밀번호 확인을 해주세요");
}
else if(userpwCheck != userpw){
alert("비밀번호가 틀립니다. 비밀번호 확인을 다시 해주세요");
}
else if(username == ""){
alert("이름을 입력해주세요");
}
else if(postnum ==""){
alert("우편번호를 입력해주세요");
}
else if(detailaddress == ""){
alert("상세주소를 입력해주세요");
}
else if(numselect[0].textContent = "인증번호가 틀립니다." || phonecheck == ""){
alert("전화번호 인증을 완료해주세요");
}
else{
joinForm.attr("action","/user/join");
joinForm.submit();
}
})
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>로그인</title>
<link href="/resources/css/styles.css" rel="stylesheet"/>
<link rel="stylesheet" href="/resources/css/main.css"/>
<style>
.heading{
color:#000000;
}
.loginForm{
width:50%;
margin:0 auto;
}
.loginForm h3{
float:left;
width:30%;
color:#000000;
line-height: 3.24rem;
}
.loginForm input[type=text], .loginForm input[type=password]{
float:right;
width:70%;
}
.loginForm>.col-12::after{
display:block;
content:"";
height:50px;
clear:both;
}
</style>
</head>
<body style="background-color: rgb(245,246,247);">
<c:if test="${f != null}">
<script>
alert("로그인에 실패하였습니다. 아이디와 비밀번호를 다시 확인해주세요");
</script>
</c:if>
<!-- Navigation-->
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container px-4 px-lg-5">
<a class="navbar-brand" href="/">My shop</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0 ms-lg-4">
<li class="nav-item"><a class="nav-link active" aria-current="page" href="/">Home</a></li>
<li class="nav-item"><a class="nav-link" href="#!">About</a></li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" id="navbarDropdown" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">Shop</a>
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
<li><a class="dropdown-item" href="#!">All Products</a></li>
<li><hr class="dropdown-divider" /></li>
<li><a class="dropdown-item" href="#!">Popular Items</a></li>
<li><a class="dropdown-item" href="#!">New Arrivals</a></li>
</ul>
</li>
</ul>
<form class="d-flex">
<ul class="navbar-nav me-auto mb-2 mb-lg-0 ms-lg-4">
<li class="nav-item"><a href="/user/login" class="nav-link">로그인</a></li>
<li class="nav-item"><a href="/user/join"class="nav-link">회원가입</a></li>
</ul>
</form>
</div>
</div>
</nav>
<header>
<div class="container px-4 px-lg-5 my-5">
<div class="text-center text-white">
<h1 class="display-4 fw-bolder" style="color:#000000;">로그인</h1>
<p class="lead fw-normal text-white-50 mb-0"></p>
</div>
</div>
</header>
<div id="main">
<div class="wrapper style1 special">
<div class="inner">
<h2 class="heading alt"></h2>
<br>
<form class="loginForm" name="loginForm" id="loginForm" action="/user/login" method="post">
<div class="col-12">
<h3>아이디</h3>
<input type="text" name = "useremail">
</div>
<div class="col-12">
<h3>비밀번호</h3>
<input type="password" name = "userpw">
</div>
<div class="col-12" style="text-align: center">
<input type="submit" value="로그인" class="primary">
</div>
</form>
</div>
</div>
</div>
<!-- Footer-->
<footer>
<div class="container"><p class="m-0 text-center text-white" style="color:#000000 !important;">Copyright © Your Website 2022</p></div>
</footer>
</body>
<script src="/resources/js/scripts.js"></script>
<script>
</script>
</html>
로그인은 로그인 버튼 클릭시 form action을 통해 컨트롤러로 보내준뒤 로직을
수행합니다.
package com.my.controller;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.http.HttpEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.my.domain.UserDTO;
import com.my.service.UserService;
import com.my.service.UserphoneService;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
@Controller
@Log4j
@RequestMapping("/user/*")
public class UserController {
@Setter(onMethod_ = @Autowired)
private UserphoneService service;
@Setter(onMethod_ = @Autowired)
private UserService services;
//회원가입, 로그인 페이지 이동
@GetMapping({"/login","/join"})
public void goLogin() {
}
//회원가입
@PostMapping("/join")
public String join(UserDTO user, HttpServletResponse ra, RedirectAttributes rar) {
if(services.join(user)) {
Cookie joinid = new Cookie("joinid", user.getUseremail());
joinid.setMaxAge(300);
ra.addCookie(joinid);
rar.addFlashAttribute("joinid", ra);
return "/user/login";
}
return "redirect:/";
}
//로그인
@PostMapping("/login")
public String login(String useremail, String userpw, HttpServletRequest resq, RedirectAttributes ra) {
HttpSession session = resq.getSession();
UserDTO loginUser = services.login(useremail, userpw);
if(loginUser != null) {
session.setAttribute("loginUserid", loginUser.getUseremail());
session.setAttribute("loginUsername", loginUser.getUsername());
session.setAttribute("userpw", loginUser.getUserpw());
session.setAttribute("userphone", loginUser.getUserphone());
session.setAttribute("postnum", loginUser.getPostnum());
session.setAttribute("addr", loginUser.getAddr());
session.setAttribute("detailaddress", loginUser.getDetailaddress());
session.setAttribute("seealso", loginUser.getSeealso());
}
else if(loginUser == null) {
ra.addFlashAttribute("f", "실패임");
return "redirect:/user/login";
}
return "redirect:/";
}
//아이디 중복 체크
@PostMapping("/checkId")
@ResponseBody
public int checkId(@RequestParam("useremail") String useremail) {
int cnt = services.checkId(useremail);
return cnt;
}
//문자 인증번호
@PostMapping("/message")
@ResponseBody
public String message(@RequestParam("userphone") String userphone) {
String num = service.phone(userphone);
log.info(num);
return num;
}