프로젝트 기능구현의 시작은 HTML 혹은 Entity구조를 작성하는 것에서 부터 시작한다.
1. 스키마 및 테이블 생성
Users Table : 회원가입을 완료 했을시 클라이언트의 정보를 담을 Table이다.
EmailAuths Table : 회원가입시에 이메일인증이 완료된 클라이언트의 정보를 담을 Table이다.
2. HTML 기본 구조 짜기
Html 구조를 작성하는 것은 시스템을 작성하는 개발자의 마음이지만 가장 중요한 점은 각 정보를 넘겨주는 역할을 하는 태그(form, submit, input)들은 name과 Entity이름, rel(속성)과 Js의 동일성이 적용되어야 모든 값들을 DB에서 부터 Controller까지 연결이 된다.
↓↓↓↓회원가입 관련(Html코드)은(는) 아래에 더보기 클릭 ↓↓↓↓
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>스터디 :: 회원가입</title>
<th:block th:replace="~{fragments/head :: common}"></th:block>
<link rel="stylesheet" th:href="@{/member/resources/stylesheets/register.css}">
<script src="https://t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
<script defer th:src="@{/member/resources/scripts/register.js}"></script>
</head>
<body>
<th:block th:replace="~{fragments/body :: header}"></th:block>
<th:block th:replace="~{fragments/body :: cover}"></th:block>
<main class="--main main">
<div class="--content content">
<form class="form step1" method="post" id="form">
<div class="title-container">
<h2 class="title">회원가입</h2>
<div class="step-container">
<span class="text" rel="stepText">이용약관 및 개인정보 처리방침</span>
<span class="step step1">1</span>
<span class="line"></span>
<span class="step step2">2</span>
<span class="line"></span>
<span class="step step3">3</span>
</div>
</div>
<div class="step step1">
<label class="label">
<h2 class="title">서비스 이용약관</h2>
<textarea class="--object-input term" readonly>서비스 이용약관</textarea>
</label>
<label class="label">
<h2 class="title">개인정보 처리방침</h2>
<textarea class="--object-input term" readonly>개인정보 처리방침</textarea>
</label>
<label class="--object-check">
<input name="termAgree" type="checkbox">
<span class="--checkbox"><i class="fa-solid fa-check --icon"></i></span>
<span class="--text">위 서비스 이용약관 및 개인정보 처리방침을 읽어보았고 이해하였으며 동의합니다.</span>
</label>
</div>
<div class="step step2">
<table class="table">
<tbody>
<tr>
<th>이메일</th>
<td>
<label class="label email">
<span hidden>이메일</span>
<input class="--object-input input" maxlength="50" name="email"
placeholder="이메일 주소를 입력해 주세요." type="email">
<input class="--object-button" name="emailSend" value="인증번호 전송" type="button">
</label>
<label class="label email">
<span hidden>인증번호</span>
<input class="--object-input input" disabled maxlength="50" name="emailAuthCode"
placeholder="이메일 인증번호를 입력해 주세요." type="text">
<input name="emailAuthSalt" type="hidden">
<input class="--object-button" disabled name="emailVerify" value="인증번호 확인"
type="button">
</label>
<span class="warning" rel="emailWarning">이메일 주소를 입력해 주세요.</span>
</td>
</tr>
<tr>
<th>비밀번호</th>
<td>
<label class="label password">
<span hidden>비밀번호</span>
<input class="--object-input input" maxlength="50" name="password"
placeholder="비밀번호를 입력해 주세요." type="password">
</label>
<label class="label password">
<span hidden>비밀번호 재입력</span>
<input class="--object-input input" maxlength="50" name="passwordCheck"
placeholder="비밀번호를 한번 더 입력해 주세요." type="password">
</label>
</td>
</tr>
<tr>
<th>닉네임</th>
<td>
<label class="label">
<span hidden>닉네임</span>
<input class="--object-input input" maxlength="5" name="nickname"
placeholder="닉네임을 입력해 주세요." type="text">
</label>
</td>
</tr>
<tr>
<th>이름</th>
<td>
<label class="label">
<span hidden>이름</span>
<input class="--object-input input" maxlength="5" name="name"
placeholder="이름을 입력해 주세요." type="text">
</label>
</td>
</tr>
<tr>
<th>연락처</th>
<td>
<label class="label">
<span hidden>연락처</span>
<input class="--object-input input" maxlength="12" name="contact"
placeholder="연락처를 ' - ' 없이 입력해 주세요." type="text">
</label>
</td>
</tr>
<tr>
<th>주소</th>
<td>
<label class="label address">
<span hidden>우편번호</span>
<input class="--object-input input" maxlength="6" name="addressPostal"
placeholder="우편 번호" readonly type="text">
<input class="--object-button" name="addressFind" value="주소 찾기" type="button">
</label>
<label class="label address">
<span hidden>기본 주소</span>
<input class="--object-input input" maxlength="100" name="addressPrimary"
placeholder="주소 찾기를 통해 주소를 입력해 주세요."
readonly type="text">
</label>
<label class="label address">
<span hidden>상세 주소</span>
<input class="--object-input input" maxlength="100" name="addressSecondary"
placeholder="상세 주소를 입력해 주세요." type="text">
</label>
<div class="panel address" rel="addressFindPanel">
<div class="dialog" rel="addressFindPanelDialog"></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="step step3">
<span class="title">회원가입 완료</span>
<span class="message"><b>스터디</b>에 회원가입해 주셔서 감사드립니다.<br>아래<i>로그인하러 가기</i>버튼을 클릭하여 로그인 해주세요.</span>
</div>
<div class="warning" rel="warning">
<i class="fa-solid fa-triangle-exclamation --icon"></i>
<div class="text" rel="warningText"></div>
</div>
<div class="button-container">
<div class="button green" rel="nextButton">
<span class="text">다음<i class="fa-solid fa-chevron-right --icon"></i></span>
</div>
</div>
</form>
</div>
</main>
<th:block th:replace="~{fragments/body :: footer}"></th:block>
</body>
</html>
3. 회원가입창을 브라우저에 View하기 위한 기본 Mapping(GET 요청방식)
- value는 register(회원가입관련), method는 GET요청방식(View의 목적 및 가져온 값 View), produces = MediaType.TEXT_HTML_VALUE는 Content-Type을 HTML의 값으로 modelAndVie 객체를 통해 반환한다는 뜻이 된다.
- ModelAndView객체를 생성해야 한다. (왜 생성해야할까?) 반환값을 modelAndView객체로 보내는데 이때 modelAndView객체에 "member/register.html"의 값을 담아서 반환해주기 때문이다.
4. 회원가입창을 브라우저에 View하기 위한 기본 Mapping(POST 요청방식)
- value는 register(회원가입관련), method는 GET요청방식(View의 목적 및 가져온 값 View), produces = MediaType.APPLICATION_JSON_VALUE는 위와 같이 Content-Type을 json형태의 값으로 보내기 위해 사용한다. [참고자료 1을 참조]
- postRegister의 타입이 String인 이유는 Object타입(Json객체)을 toString()메서드를 통해 문자열로 변한 값을 반환하기 때문이다.
- Enum타입의 result변수는 memberService에서 register() 메서드의 기능이 작동된 user와 auth값을 받는다.
- JSONObject타입의 responseObjet 객체를 생성하여 put()메서드를 통해 ("result(키)",위에서 지정한 result.name().toLowerCase() -> 소문자화 한 result값 ) 값을 대입한다.
- 마지막으로 소문자화 된 responseObjcet({"result" : "결과값"})을 문자열 타입으로 변화시켜 반환한다.
이때 주의해야할 점
@ResponseBody(anotation)을 사용하지 않고 값을 반환한다면 [{"result":"success"}] 다음과같이 대괄호 안에 중괄호가 들어가 있다. 이말인 즉슨 Json은 ResponseBody를 통해 Object타입인지 배열타입인지를 인지하기 때문에 ResponseBody를 사용하지 않을경우 타입이 맞지 않아 오류가 발생하게 된다. (실제로 @ResponseBody를 작성하지 않고 프로그램을 구동해 오류가 발생함.)
5. 이메일 인증 기능구현하기
이메일 인증 기능 구현 순서
- 최초 클라이언트가 도메인 접속시 GET요청방식을 통해 회원가입 페이지가 뜸.
- 클라이언트가 회원가입 각각의 input칸에 자신의 개인정보를 작성
- 작성된 정보중 가장 먼저 처리 되어야 하는것이 이메일이 클라이언트의 이메일이 맞는지 확인해야하기 때문에 클라이언트의 이메일에 사이트 관련 인증번호페이지를 보내야 함.(보냄과 동시에 인증번호 전송 버튼, 이메일 작성 칸은 disabled 속성으로 처리)
- 인증번호가 올바른지 확인하기 위해 email, code, salt(아래에 자세히 기술할 예정)가 클라이언트가 가진 code, salt와 동일 한지 판단 후 올바를 경우 인증 번호 확인을 눌렀을 경우 버튼을 disabled 처리와 동시에 아래에 message기능을 통해 올바른 인증번호를 입력하였다고 클라이언트에게 보내주고 cursur를 비밀번호 input칸으로 옮김.
여기서 짚고 넘어가야할 것!
위와 같은 이메일 인증번호 관련 코드 및 동일성을 확인하기 위해서는 DB부터 Controller를 거쳐야 하지만 <3>,<4>와 같이 Button 및 input속성의 disabled, message기능을 통한 알림을 위해서는 JavaScript를 사용해야 한다.
- form을 상수로 지정하여 사용하는 이유는 모든 데이터를 전송처리 하기위해 사용하는 태그이기 때문이다. form을 상수로 지정해 놓으면 그 속에 속해있는 모든 rel속성과 클래스들을 편리하게 사용할 수 있게 된다.
- Warning 함수는 2가지 기능을 할 수 있도록 만든 함수 이다.
- Show : text를 전달인자로 받는 show함수는
- form태그안의 어떠한 태그 중 rel 속성이 warningText인 태그에 innerText를 통해 아래에서 새로 지정한 text값으로 대체하여 작성되어진다.
- form태그안의 어떠한 태그 중 rel 속성이 warning인 태그에 ClassList.add()메서드를 통해 show함수 사용시 warning이 붙은 태그에 visible 클래스를 추가시켜준다
- Hide: form태그안의 어떠한 태그중 rel 속성이 warning인 태그의 ClassList.remove() 메서드를 통해 hide함수 사용시 warning이 붙은 태그에 visible 클래스를 삭제시켜준다.
- Show : text를 전달인자로 받는 show함수는
- EmailWarning 함수도 마찬가지로 2가지 기능을 사용하는 함수이다(Email관련에서만 경고창을 띄우는 역할을 한다.)
- Show : text를 전달인자로 받는 show함수는
- form태그안의 어떠한 태그 중 rel 속성이 warningText인 태그에 innerText를 통해 아래에서 새로 지정한 text값으로 대체하여 작성되어진다.
- form태그안의 어떠한 태그 중rel 속성이 warning인 태그에 ClassList.add()메서드를 통해 show함수 사용시 warning이 붙은 태그에 visible 클래스를 추가시켜준다
- Hide: form태그안의 어떠한 태그중 rel 속성이 emailWarning인 태그의 ClassList.remove() 메서드를 통해 hide함수 사용시 emailWarning이 붙은 태그에 visible 클래스를 삭제시켜준다.
- Show : text를 전달인자로 받는 show함수는
form.querySelector('[rel="nextButton"]').addEventListener('click', () => {
// form의 클래스중 querySelector를 통해 rel의 속성중 nextButton을 가진 태그의 클릭 이벤트가 발생시를 나타낸 구현부이다.
form.querySelector('[rel="warning"]').classList.remove('visible');
// rel값중 warning을 가진 태그는 클릭이벤트가 시작된 직후 visible 클래스를 삭제 시킨다.(visible이 삭제되면 css에서 visible이 없는 경우라고 판단한다.)
if (form.classList.contains('step1')) {
// form이 step1을 포함하고 있고
if (!form['termAgree'].checked) {
// form요소중 termAgree Name을 가진 checkbox가 check되어있지 않은 경우
EmailWarning.show(' 서비스 이용약관 및 개인정보 처리방침을 읽고 동의해 주세요.');
// form 의 요소중 rel속성에 warningText를 가진 요소에 innerText를 통해 문자를 보여준다.
// 보여줌과 동시에 rel속성에 warning을 가진 요소에 visible 클래스를 추가해준다.
return;
// 값을 return 한다.
}
form.querySelector('[rel="stepText"]').innerText = '개인정보 입력';
// rel 속성중 stepText를 가진 요소에 Text를 추가넣는다.
form.classList.remove('step1');
// step1클래스를 삭제한다.
form.classList.add('step2');
// step2 클래스를 추가한다.
} else if (form.classList.contains('step2')) {
위의 코드는 다음 버튼을 클릭했을때 발생하는 이벤트의 기능을 구현한 로직이다.
- form태그안 속성중 'warning'을 가진 태그에 visible 클래스를 제거한다.(버튼을 클릭할때 마다 이벤트가 발생함으로 그전에 경고창이 떠있었을 경우를 대비해 remove를 시켜주는 것이다.)
- 첫번째 if절은 form태그가 step1클래스를 포함하고 있는지에 대한 여부를 묻고있다.(만약 termAgree가 체크되어있지 않다면 EmailWarning show함수를 통해 text(서비스 blur~)를 전달한다.)
- stepText.innerText는 페이지 상단의 회원가입 순차가 변경되면서 작성되는 표시글이다.
- 완료가 되었다는건 체크가 되어진 상태로 다음 버튼을 눌렀다는 뜻이므로 form태그의 step1클래스를 삭제해주고 step2 클래스를 추가해줌으로써 다음단계로 넘거가게 한다.
- step2도 <3>과 같이 똑같은 처리를 해준다. 다만 step2에선 실제 회원가입을 위한 데이터를 작성하는 공간이므로 또다른 기능을 추가하여야 한다.
6. 이메일 인증구현(회원가입페이지[Step 2])
- 위에서 step2 클래스가 정상적으로 form태그에 추가가 되었다면 다음 버튼을 눌렀을 경우 회원가입 페이지가 클라이언트에게 보여진다.
- 현재 창에서 보여지는 JS기능으로는 인증번호 확인칸이 disabled가 활성화 되어 있는 상태이다. 즉 인증번호 전송을 눌렀을 경우 어떠한 이벤트가 발생하여 바뀐다는 뜻이다.
↓↓↓↓(input빈칸 Warning관련) 코드는 아래에 더보기 클릭 ↓↓↓↓
if (!form['emailSend'].disabled || !form['emailVerify'].disabled) {
Warning.show('이메일 인증을 완료해 주세요.');
return;
}
if (form['password'].value === '' || form['passwordCheck'].value === '') {
Warning.show('비밀번호 작성을 완료해 주세요.');
return;
}
if (form['password'].value !== form['passwordCheck'].value) {
Warning.show('비밀번호가 일치하지 않습니다. 다시 작성해 주세요. ');
return;
}
if (form['nickname'].value === '') {
Warning.show('닉네임 작성을 완료해 주세요.');
return;
}
if (form['name'].value === '') {
Warning.show('이름 작성을 완료해 주세요.');
return;
}
if (form['contact'].value === '') {
Warning.show('연락처 작성을 완료해 주세요.');
return;
}
if (form['addressPostal'].value === '' || form['addressPrimary'].value === '' || form['addressSecondary'].value === '') {
Warning.show('주소 작성을 완료해 주세요.');
return;
}
- 위 코드는 각각의 input칸의 value값이 없다. 즉, 비어있을 경우 경고창을 클라이언트에게 보여주기 위한 로직이다. 다만 주의 해야할 점은 email칸과 password칸은 조금 다른 로직으로 처리해야 한다. email칸은 둘중 하나라도 disabled가 풀려있다면 경고창을 띄워준다.(disabled가 풀렸다는 뜻은 처리가 되지 않았다는 뜻이기 때문이다.) password칸은 passwordCheck칸과 value가 동일할 경우에만 다음 칸으로 이동시켜 준다.(자신이 작성한 비밀번호를 한번더 작성했을시 동일해야 하기 때문이다.)
모든 if문을 통과했을 경우 클라이언트가 올바른 값들을 입력했다는 뜻으로 간주하고 실제 그 값들을 DB에 저장시켜 브라우저는 클라이언트가 회원가입에 성공했다는 것을 클라이언트에게 인식시켜 주어야 한다. 아래는 그 로직을 위한 것이다.
Cover.show('회원가입을 진행중입니다.\n\n잠시만 기다려 주세요.');
const xhr = new XMLHttpRequest();
const formData = new FormData();
formData.append('email', form['email'].value);
formData.append('code', form['emailAuthCode'].value);
formData.append('salt', form['emailAuthSalt'].value);
formData.append('password', form['password'].value);
formData.append('nickname', form['nickname'].value);
formData.append('name', form['name'].value);
formData.append('contact', form['contact'].value);
formData.append('addressPostal', form['addressPostal'].value);
formData.append('addressPrimary', form['addressPrimary'].value);
formData.append('addressSecondary', form['addressSecondary'].value);
xhr.open('POST', './register');
// 경로도 유심히 봐야한다. ./은
xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
Cover.hide();
if (xhr.status >= 200 && xhr.status < 300) {
const responseObject = JSON.parse(xhr.responseText);
switch (responseObject['result']) {
case 'success':
form.querySelector('[rel="stepText"]').innerText = '회원가입 완료';
// form태그의 요소중 rel값을 stepText를 가진 태그의 Text를 추가한다.
form.querySelector('[rel="nextButton"]').innerText = '로그인하러 가기';
form.classList.remove('step2');
form.classList.add('step3');
break;
case 'email_not_verified':
Warning.show('이메일 인증이 완료되지 않았습니다.');
break;
default:
EmailWarning.show('알 수 없는 이유로 회원가입을 완료하지 못하였습니다. 잠시 후 다시 시도해 주세요.');
}
} else {
EmailWarning.show('서버와 통신하지 못하였습니다.잠시후 다시 시도해 주세요.');
}
}
}
xhr.send(formData);
} else if (form.classList.contains('step3')) {
window.location.href = 'login';
// /login으로 바로 이동하는데 현재의 경우 페이지를 만들지 않아 404오류가 뜨는 상태이다.
// step3의 클래스 포함할 경우 login
// page의 노출을 꺼려하는 경우 location.replace를 통해 뒤로가기 클릭시 숨기는 것을 원하는 page를 노출시키지 않을 수 있다.
}
});
- Cover.show 함수를 통해 클라이언트에게 회원가입 중입니다라는 메세지를 보여줌으로써 그사이 DB에 값을 insert 한다.
- xhr을 통해 Controller에서 받는 값들을 처리하는 로직을 짜야한다.
- form.append()메서드는 ('key', 'value')를 form태그에 추가해주는데 이때 value는 form태그안에 name이 각각 동일성을 띄고 있는 값들을 append한다는 뜻이다.
- xhr.open()메서드는 ('요청방식', 'html파일 경로(Controller와 일치하는 값)')이 맞을 경우 xhr 실행의 시작점 역할을 한다.
- 이때 Http상태코드가 200이상 300미만일경우(연결이 성공일 경우) 상수인 responseObject라는 Json값을 받아주는 역할을 하는 친구를 만들어준다. (Controller Post요청방식에서 Json값을 반환해주기 때문에)
- Controller에서 반환해주는 타입은 String이다. 그 값을 JSON.parse()메서드를 통해 Controller에서 받아온 문자열 값을 Object타입으로 parse시켜준다.
- responseObject['result'] 의 값을 받는 경우에만 실행된다. 즉 , {"result" : success, failure} 이경우에만 switch문이 작동이 된다. {"result" : "success"}일경우 form안의 태그중 stepText를 가진 태그의 text를 회원가입완료라고 변경 해주고 nextbutton(현재는 다음)을 로그인하러가기로 innerText시킨다.
- 그와 동시에 form태그의 클래스에서 step2클래스를 삭제하고 step3클래스를 추가한다.(Step3로 페이지가 이동한다는 뜻이다.)
'SpringBoot' 카테고리의 다른 글
SpringBoot 게시판 기능구현 1 (0) | 2022.11.20 |
---|---|
SpringBoot 회원가입 기능구현 4 , 비밀번호 재설정 기능구현 (0) | 2022.11.15 |
SpringBoot 로그인 기능 구현, session값을 통한 게시판 접근 (0) | 2022.11.15 |
SpringBoot 회원가입 기능구현 3 (0) | 2022.11.13 |
SpringBoot 회원가입 기능구현 2 (1) | 2022.11.11 |