SpringBoot : 회원가입, 이메일
package edu.kh.project.member.controller;
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.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttribute;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import edu.kh.project.member.model.dto.Member;
import edu.kh.project.member.model.service.MemberService;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
@Controller
@SessionAttributes({"loginMember"})
@RequestMapping("/member")
public class MemberController {
@Autowired
private MemberService service;
@PostMapping("/login")
public String login(Member inputMember, Model model,
@RequestHeader("referer") String referer,
RedirectAttributes ra,
@RequestParam(value="saveId", required = false) String saveId,
HttpServletResponse resp
) {
Member loginMember = service.login(inputMember);
String path = "redirect:";
if(loginMember != null) { // 로그인 성공시
path += "/";
model.addAttribute("loginMember", loginMember);
Cookie cookie = new Cookie("saveId", loginMember.getMemberEmail());
if(saveId != null) { // 체크가 되었을 때
cookie.setMaxAge(60*60*24*30);
} else { // 체크가 안되었을 때
cookie.setMaxAge(0);
}
cookie.setPath("/");
resp.addCookie(cookie);
} else { // 로그인 실패
path += referer;
ra.addFlashAttribute("message", "아이디 또는 비밀번호 불일치");
}
return path;
}
@GetMapping("/logout")
public String logout(SessionStatus status) {
status.setComplete();
return "redirect:/";
}
// 회원 가입 페이지 이동
@GetMapping("/signUp")
public String signUp() {
// 기존 : views/common/ .jsp
// Boot : templates/ .html
return "member/signUp";
}
// 회원 가입 진행
@PostMapping("/signUp")
public String signUp( Member inputMember,
String[] memberAddress,
RedirectAttributes ra ) {
// Member inputMember : 커맨드 객체 (제출된 파라미터가 저장된 객체)
// String[] memberAddress :
// input name="memberAddress" 3개가 저장된 배열
// RedirectAttributes ra :
// 리다이렉트 시 데이터를 request scope로 전달하는 객체
System.out.println("주소 : " + inputMember.getMemberAddress());
// 주소 입력 시 : 01234,서울 성동구 어쩌구,2층
// 만약에 입력하지 않았다면 ,, 이런식으로 구분자만 나옴
// 주소를 입력하지 않은 경우 null 로 변경
// 주소가 입력되지 않았다면
if(inputMember.getMemberAddress().equals(",,")) {
inputMember.setMemberAddress(null);
// 주소를 입력한 경우 ,, -> ^^^
} else {
// String.join("구분자", String[])
// 배열의 요소를 하나의 문자열로 변경
// 요소 사이에 구분자를 추가함
String addr = String.join("^^^", memberAddress); // ['12345','서울시어쩌구','2층'] -> ["12345^^^서울시어쩌구^^^2층"]
inputMember.setMemberAddress(addr);
}
// 회원 가입 서비스 호출
int result = service.signUp(inputMember);
// 가입 성공 여부에 따라서 주소 결정
String path = "redirect:";
String message = null;
if(result > 0) { // 가입 성공
path += "/"; // 메인페이지로
message = inputMember.getMemberNickname() + "님의 가입을 환영합니다";
} else { // 가입 실패
// 회원 가입 페이지
//path += "/member/signUp"; // 절대경로
path += "signUp"; // 상대경로
message = "회원 가입 실패";
}
// 리다이렉트 시 session에 잠깐 올라갔다 request로 복귀하도록 세팅
ra.addFlashAttribute("message", message);
return path;
}
}
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<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">
<title>회원가입</title>
<link rel="stylesheet" th:href="@{/css/member/signUp-style.css}"> <!-- static 기준으로 경로 작성 -->
</head>
<body>
<main>
<th:block th:replace="~{common/header}"></th:block>
<section class="signUp-content">
<form th:action="@{/member/signUp}" method="POST" name="signUpFrm" id="signUpFrm">
<!-- 이메일 입력 -->
<label for="memberEmail">
<span class="required">*</span> 아이디(이메일)
</label>
<div class="signUp-input-area">
<input type="text" name="memberEmail" id="memberEmail"
placeholder="아이디(이메일)" maxlength="30" autocomplete="off">
<button id="sendAuthKeyBtn" type="button">인증번호 받기</button>
</div>
<span class="signUp-message" id="emailMessage">메일을 받을 수 있는 이메일을 입력해주세요.</span>
<!-- 인증번호 입력 -->
<label for="emailCheck">
<span class="required">*</span> 인증번호
</label>
<div class="signUp-input-area">
<input type="text" name="authKey" id="authKey" s placeholder="인증번호 입력" maxlength="6" autocomplete="off" >
<button id="checkAuthKeyBtn" type="button">인증하기</button>
</div>
<span class="signUp-message" id="authKeyMessage"></span>
<!-- 인증번호가 일치하지 않습니다 -->
<!-- 비밀번호/비밀번호 확인 입력 -->
<label for="memberPw">
<span class="required">*</span> 비밀번호
</label>
<div class="signUp-input-area">
<input type="password" name="memberPw" id="memberPw"
placeholder="비밀번호" maxlength="20" >
</div>
<div class="signUp-input-area">
<input type="password" name="memberPwConfirm" id="memberPwConfirm"
placeholder="비밀번호 확인" maxlength="20" >
</div>
<span class="signUp-message" id="pwMessage">영어,숫자,특수문자(!,@,#,-,_) 6~20글자 사이로 입력해주세요.</span>
<!-- 닉네임 입력 -->
<label for="memberNickname">
<span class="required">*</span> 닉네임
</label>
<div class="signUp-input-area">
<input type="text" name="memberNickname" id="memberNickname" placeholder="닉네임" maxlength="10" >
</div>
<span class="signUp-message" id="nickMessage">한글,영어,숫자로만 2~10글자</span>
<!-- 전화번호 입력 -->
<label for="memberTel">
<span class="required">*</span> 전화번호
</label>
<div class="signUp-input-area">
<input type="text" name="memberTel" id="memberTel" placeholder="(- 없이 숫자만 입력)" maxlength="11">
</div>
<span class="signUp-message" id="telMessage">전화번호를 입력해주세요.(- 제외)</span>
<!-- 주소 입력 -->
<label for="memberAddress">주소</label>
<!-- name 값이 동일할 경우 String[] memberAddress: 배열로 넘어옴 ex) ['04746', '서울 어쩌구', '2층']
만약, 주소가 선택사항이라 사용자가 기입하지 않을 경우 구분자 '' 따옴표만 넘어옴
Controller 단에서 ''(따옴표로 넘어오면) -> null로 db에 입력되도록 처리해줄 것임
ex) 04746^^^서울 어쩌구^^^2층으로 만들어줄 것임 -> 이유: , 구분자와 헷갈릴 수 있기 때문
ex) 04157,서울시 성동구 어쩌구동,(구분자로서 역할을 제대로 하지 못함) 무슨빌라,2층
-> 0416^^^서울 성동구, 어쩌구^^^2층
-->
<div class="signUp-input-area">
<input type="text" name="memberAddress" id="sample6_postcode" placeholder="우편번호" maxlength="6">
<button type="button" onclick="sample6_execDaumPostcode()">검색</button>
</div>
<div class="signUp-input-area">
<input type="text" name="memberAddress" id="sample6_address" placeholder="도로명/지번 주소">
</div>
<div class="signUp-input-area">
<input type="text" name="memberAddress" id="sample6_detailAddress" placeholder="상세 주소">
</div>
<button id="signUpBtn">가입하기</button>
</form>
</section>
</main>
<th:block th:replace="~{common/footer}"></th:block>
<script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
<script>
function sample6_execDaumPostcode() {
new daum.Postcode({
oncomplete: function(data) {
// 팝업에서 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분.
// 각 주소의 노출 규칙에 따라 주소를 조합한다.
// 내려오는 변수가 값이 없는 경우엔 공백('')값을 가지므로, 이를 참고하여 분기 한다.
var addr = ''; // 주소 변수
//사용자가 선택한 주소 타입에 따라 해당 주소 값을 가져온다.
if (data.userSelectedType === 'R') { // 사용자가 도로명 주소를 선택했을 경우
addr = data.roadAddress;
} else { // 사용자가 지번 주소를 선택했을 경우(J)
addr = data.jibunAddress;
}
// 우편번호와 주소 정보를 해당 필드에 넣는다.
document.getElementById('sample6_postcode').value = data.zonecode;
document.getElementById("sample6_address").value = addr;
// 커서를 상세주소 필드로 이동한다.
document.getElementById("sample6_detailAddress").focus();
}
}).open();
}
</script>
<script th:src="@{/js/member/signUp.js}"></script>
</body>
</html>
-> Ajax 관련 파일 옮기기
package edu.kh.project.member.controller;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import edu.kh.project.member.model.dto.Member;
import edu.kh.project.member.model.service.AjaxService;
@Controller // 요청/응답 제어 + bean 등록
public class AjaxController {
@Autowired
private AjaxService service;
// ** 닉네임으로 전화번호 조회
@GetMapping("/selectMemberTel")
@ResponseBody
public String selectMemberTel(/*@RequestParam("nickname")*/ String nickname) {
// 쿼리스트링에 담겨있는 파라미터
// return 리다이렉트 / 포워드 -> 새로운 화면 보임 (동기식)
// return 데이터 -> 데이터를 요청한 곳으로 반환 (비동기식)
// @ResponseBody
// -> Controller의 결과로 데이터를 반환할 때 사용하는 어노테이션
return service.selectMemberTel(nickname);
}
// ** 이메일로 회원정보 조회
@PostMapping("/selectMember")
@ResponseBody // 비동기 요청한곳으로 응답 + Java데이터 JSON, TEXT로 변환
public Member selectMember(@RequestBody Map<String, Object> paramMap) {
// @RequestBody Map<String, Object> paramMap
// -> 요청된 HTTP Body에 담긴 모든 데이터를 자바 객체인 Map으로 반환
//System.out.println("paramMap:" + paramMap); // {email = user01@test...}
String email = (String) paramMap.get("email"); // user01@test...
return service.selectMember(email);
}
@GetMapping("/dupCheck/email")
@ResponseBody
public int checkEmail(String email) {
return service.checkEmail(email);
}
@GetMapping("/dupCheck/nickname")
@ResponseBody
public int checkNickname(String nickname) {
return service.checkNickname(nickname);
}
}
package edu.kh.project.member.model.service;
import edu.kh.project.member.model.dto.Member;
public interface AjaxService {
/** 닉네임으로 전화번호 조회
* @param nickname
* @return tel
*/
String selectMemberTel(String nickname);
/** 이메일로 회원정보 조회
* @param email
* @return
*/
Member selectMember(String email);
/** 이메일 중복검사
* @param email
* @return count
*/
int checkEmail(String email);
/** 닉네임 중복검사
* @param nickname
* @return count
*/
int checkNickname(String nickname);
}
package edu.kh.project.member.model.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import edu.kh.project.member.model.dao.AjaxDAO;
import edu.kh.project.member.model.dto.Member;
@Service // 서비스임을 명시 + bean 등록
public class AjaxServiceImpl implements AjaxService{
@Autowired
private AjaxDAO dao;
// 닉네임으로 전화번호 조회
@Override
public String selectMemberTel(String nickname) {
return dao.selectMemberTel(nickname);
}
// 이메일로 회원정보 조회
@Override
public Member selectMember(String email) {
return dao.selectMember(email);
}
// 이메일 중복검사
@Override
public int checkEmail(String email) {
return dao.checkEmail(email);
}
// 닉네임 중복검사
@Override
public int checkNickname(String nickname) {
return dao.checkNickname(nickname);
}
}
package edu.kh.project.member.model.dao;
import org.apache.ibatis.annotations.Mapper;
import edu.kh.project.member.model.dto.Member;
@Mapper
public interface AjaxMapper {
/** 닉네임으로 전화번호 조회
* @param nickname
* @return
*/
String selectMemberTel(String nickname);
/** 이메일로 회원정보 조회
* @param email
* @return
*/
Member selectMember(String email);
/** 이메일 중복검사
* @param email
* @return
*/
int checkEmail(String email);
/** 닉네임 중복검사
* @param nickname
* @return
*/
int checkNickname(String nickname);
}
package edu.kh.project.member.model.dao;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import edu.kh.project.member.model.dto.Member;
@Repository // DB 연결 의미 + bean 으로 등록
public class AjaxDAO {
@Autowired
private AjaxMapper mapper;
// 닉네임으로 전화번호 조회
public String selectMemberTel(String nickname) {
return mapper.selectMemberTel(nickname);
}
// 이메일로 회원정보 조회
public Member selectMember(String email) {
return mapper.selectMember(email);
}
// 이메일 중복검사
public int checkEmail(String email) {
return mapper.checkEmail(email);
}
// 닉네임 중복검사
public int checkNickname(String nickname) {
return mapper.checkNickname(nickname);
}
}
<?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="edu.kh.project.member.model.dao.AjaxMapper">
<!-- resultMap은 보통 위에 작성! -->
<resultMap type="Member" id="member_rm">
<!-- property가 java, column이 db라고 생각하면 됨. -->
<!-- DB의 기본 키(복합키면 여러 개 작성) -->
<id property="memberNo" column="MEMBER_NO" />
<!-- DB의 일반 컬럼들 -->
<result property="memberEmail" column="MEMBER_EMAIL" />
<result property="memberPw" column="MEMBER_PW" />
<result property="memberNickname" column="MEMBER_NICKNAME" />
<result property="memberTel" column="MEMBER_TEL" />
<result property="memberAddress" column="MEMBER_ADDR" />
<result property="profileImage" column="PROFILE_IMG" />
<result property="enrollDate" column="ENROLL_DATE" />
<result property="memberDeleteFlag" column="MEMBER_DEL_FL" />
<result property="authority" column="AUTHORITY" />
</resultMap>
<!-- parameterType : 전달 받은 파라미터의 자료형 작성
-> 선택사항으로, 작성 안하면 TypeHandler가 알아서 처리
-->
<!-- 자바 마이바티스
int -> _int
String -> string
-->
<!-- 닉네임으로 전화번호 조회 -->
<select id="selectMemberTel" resultType="string">
SELECT MEMBER_TEL FROM "MEMBER"
WHERE MEMBER_NICKNAME = #{nickname}
AND MEMBER_DEL_FL = 'N'
</select>
<!-- resultMap은 언제사용?
조회 결과 컬럼명과 DTO의 필드명이 다를 때 사용
-->
<!-- 이메일로 회원정보 조회 -->
<select id="selectMember" resultMap="member_rm">
SELECT MEMBER_NO, MEMBER_EMAIL, MEMBER_NICKNAME, MEMBER_TEL,
NVL(MEMBER_ADDR, '미작성') MEMBER_ADDR,
TO_CHAR(ENROLL_DATE, 'YYYY"년" MM"월" DD"일" HH24"시" MI"분" SS"초"') AS ENROLL_DATE
FROM "MEMBER"
WHERE MEMBER_EMAIL = #{email}
AND MEMBER_DEL_FL = 'N'
</select>
<!-- 이메일 중복 검사 -->
<select id="checkEmail" resultType="_int">
SELECT COUNT(*) FROM "MEMBER"
WHERE MEMBER_EMAIL= #{email}
AND MEMBER_DEL_FL = 'N'
</select>
<!-- 닉네임 중복 검사 -->
<select id="checkNickname" resultType="_int">
SELECT COUNT(*) FROM "MEMBER"
WHERE MEMBER_NICKNAME = #{nickname}
AND MEMBER_DEL_FL = 'N'
</select>
</mapper>
추가 후
프로젝트 우클릭 > Gradle > Project Gradle Refresh 꼭 하기!
package edu.kh.project.common.config;
import java.util.Properties;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
@Configuration
@PropertySource("classpath:/config.properties") // config.properties 이메일 인증 이용하겠다!
public class EmailConfig {
// @Value : properties 파일에서 key가 일치하는 부분의 value를 얻어와 대입
@Value("${spring.mail.username}")
private String username; // o3odw98@gmail.com
@Value("${spring.mail.password}")
private String password; // hsrnftupgpomuqjh
@Bean
public JavaMailSender javaMailSender() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
// 메일 관련 설정
mailSender.setUsername(username); // o3odw98@gmail.com
mailSender.setPassword(password); // hsrnftupgpomuqjh
mailSender.setHost("smtp.gmail.com");
mailSender.setPort(587);
Properties prop = new Properties(); // 속성을 묶을 수 있는 객체 생성
prop.setProperty("mail.transport.protocol", "smtp"); // -> 추가된 속성 세팅(깊게 알 필요 X)
prop.setProperty("mail.smtp.auth", "true");
prop.setProperty("mail.smtp.starttls.enable", "true");
prop.setProperty("mail.debug", "true");
prop.setProperty("mail.smtp.ssl.trust","smtp.gmail.com");
prop.setProperty("mail.smtp.ssl.protocols","TLSv1.2");
mailSender.setJavaMailProperties(prop);
return mailSender; // 반환된 객체가 bean 등록됨
}
}
package edu.kh.project.member.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import edu.kh.project.member.model.service.EmailService;
@Controller
@RequestMapping("/sendEmail")
public class EmailController {
@Autowired
private EmailService service;
@GetMapping("/signUp")
@ResponseBody
public int signUp(String email) {
return service.signUp(email, "회원 가입");
}
@GetMapping("/checkAuthKey")
@ResponseBody
public int checkAuthKey(String inputKey, String email) {
return service.checkAuthKey(inputKey, email);
}
}
package edu.kh.project.member.model.service;
public interface EmailService {
int signUp(String email, String string);
int checkAuthKey(String inputKey, String email);
}
package edu.kh.project.member.model.service;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import edu.kh.project.member.model.dao.EmailDAO;
import jakarta.mail.Message;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeMessage;
@Service
public class EmailServiceImpl implements EmailService{
@Autowired
private EmailDAO dao;
@Autowired
private JavaMailSender mailSender;
private String fromEmail = "o3odw98@gmail.com";
private String fromUsername = "수업용프로젝트";
public String createAuthKey() {
String key = "";
for(int i=0 ; i< 6 ; i++) {
int sel1 = (int)(Math.random() * 3); // 0:숫자 / 1,2:영어
if(sel1 == 0) {
int num = (int)(Math.random() * 10); // 0~9
key += num;
}else {
char ch = (char)(Math.random() * 26 + 65); // A~Z
int sel2 = (int)(Math.random() * 2); // 0:소문자 / 1:대문자
if(sel2 == 0) {
ch = (char)(ch + ('a' - 'A')); // 대문자로 변경
}
key += ch;
}
}
return key;
}
@Transactional
@Override
public int signUp(String email, String title /* "회원 가입" */) {
// 6자리 난수 인증번호 생성
String authKey = createAuthKey();
try {
//인증메일 보내기
MimeMessage mail = mailSender.createMimeMessage();
// 제목
String subject = "[Board Project]"+title+" 인증코드";
// 문자 인코딩
String charset = "UTF-8";
// 메일 내용
String mailContent
= "<p>Board Project "+title+" 인증코드입니다.</p>"
+ "<h3 style='color:blue'>" + authKey + "</h3>";
// 송신자(보내는 사람) 지정
mail.setFrom(new InternetAddress(fromEmail, fromUsername));
// 수신자(받는사람) 지정
mail.addRecipient(Message.RecipientType.TO, new InternetAddress(email));
// 이메일 제목 세팅
mail.setSubject(subject, charset);
// 내용 세팅
mail.setText(mailContent, charset, "html" /* 중요! */); //"html" 추가 시 HTML 태그가 해석됨
mailSender.send(mail);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
Map<String, String> map = new HashMap<String, String>();
map.put("authKey", authKey);
map.put("email", email);
System.out.println(map);
// 하나의 메소드로 dao 두개 연결
int result = dao.updateAuthKey(map);
if(result == 0) {
result = dao.insertAuthKey(map);
}
return result;
}
@Override
public int checkAuthKey(String inputKey, String email) {
Map<String, String> map = new HashMap<String, String>();
map.put("inputKey", inputKey);
map.put("email", email);
int result = dao.checkAuthKey(map);
return result;
}
}
package edu.kh.project.member.model.dao;
import java.util.Map;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface EmailMapper {
int updateAuthKey(Map<String, String> map);
int insertAuthKey(Map<String, String> map);
int checkAuthKey(Map<String, String> map);
}
package edu.kh.project.member.model.dao;
import java.util.Map;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
@Repository
public class EmailDAO {
@Autowired
private EmailMapper mapper;
public int updateAuthKey(Map<String, String> map) {
return mapper.updateAuthKey(map);
}
public int insertAuthKey(Map<String, String> map) {
return mapper.insertAuthKey(map);
}
public int checkAuthKey(Map<String, String> map) {
return mapper.checkAuthKey(map);
}
}
<?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="edu.kh.project.member.model.dao.EmailMapper"> <!-- 패키지 + 클래스명 -->
<!-- DML : resultMap 생략 가능하다 (어차피 int이기 때문) -->
<update id="updateAuthKey">
UPDATE "AUTH_KEY" SET
CODE = #{authKey},
CREATE_TIME = sysdate
WHERE EMAIL = #{email}
</update>
<insert id="insertAuthKey">
INSERT INTO "AUTH_KEY" VALUES(SEQ_AUTH_KEY_NO.NEXTVAL, #{authKey}, #{email}, DEFAULT)
</insert>
<select id="checkAuthKey" resultType="_int">
SELECT COUNT(*) FROM AUTH_KEY
WHERE CODE = #{inputKey}
AND EMAIL = #{email}
</select>
</mapper>
// 회원 가입 JS
/* 정규 표현식(Regular Expression)
https://regexper.com/
https://regexr.com/
https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/%EC%A0%95%EA%B7%9C%EC%8B%9D
- 특정한 규칙을 가진 문자열 집합을 표현하는데 사용하는 형식 언어
- 문자열에 대한 검색, 일치 여부, 치환 등을 수행할 수 있음
*** JS 정규표현식 객체 생성 방법 ***
1. const regEx = new RegExp("정규표현식");
2. const regEx = /정규표현식/;
*** 정규표현식 객체가 제공하는 메서드(함수) ***
1. regEx.test(문자열)
-> 문자열이 정규표현식 패턴에 부합하면 true, 아니면 false
2. regEx.exec(문자열)
-> 문자열이 정규표현식 패턴에 부합하면
첫 번째 매칭되는 문자열을 반환,
없으면 null 반환
*** 정규 표현식의 메타 문자***
문자열의 패턴을 나타내는 문자.
문자마다 지정된 특별한 뜻이 담겨있다.
a (일반문자열) : 문자열 내에 a라는 문자열이 존재하는 검색
[abcd] : 문자열 내에 a,b,c,d 중 하나라도 일치하는 문자가 있는지 검색
^(캐럿) : 문자열의 시작을 의미
$(달러) : 문자열의 끝을 의미
\w (word, 단어) : 아무 글자(단, 띄어쓰기, 특수문자, 한글 X)
\d (digit, 숫자) : 아무 숫자(0~9 중 하나)
\s (space, 공간) : 아무 공백 문자(띄어쓰기, 엔터, 탭 등)
[0-9] : 0부터 9까지 모든 숫자
[ㄱ-힣] : ㄱ부터 힣까지 모든 한글
[가-힣] : 가부터 힣까지 모든 한글(자음만, 모음만 있는 경우 제외)
[a-z] : 모든 영어 소문자
[A-Z] : 모든 영어 대문자
* 특수문자의 경우 각각을 입력하는 방법밖엔 없음
단, 메타문자와 중복되는 특수문자는
앞에 \(백슬래시)를 추가하여 탈출 문자(Escape)로 만들어 사용
* 수량 관련 메타 문자
a{5} : a문자가 5개 존재 == aaaaa
a{2,5} : a가 2개 이상 5개 이하 == aa, aaa, aaaa, aaaaa
a{2,} : a가 2개 이상
a{,5} : a가 5개 이하
* : 0개 이상 == 0번 이상 반복 == 있어도되고, 없어도 되고
+ : 1개 이상 == 1번 이상 반복
? : 0개 또는 1개
. : 1칸 (개행문자를 제외한 문자 하나)
*/
// JS 객체 : { "K":V, "K":V, "K":V, "K":V } (Map 형식)
// 특징
// 1) 원하는 value를 얻어오는 방법
// - 객체명.Key
// - 객체명["Key"]
// 2) 객체에 특정 Key가 존재하지 않으면 추가할 수 있다
// ex) const obj = {"a":1, "b":2}
// obj.c = 3 // -> {"a":1, "b":2, "c":3}
// 3) 객체에 특정 Key를 삭제할 수 있다 (delete 연산자)
// ex) const obj = {"a":1, "b":2}
// delete obj.b; // {"a":1}
/* 유효성 검사 진행 여부 확인용 객체 */
// -> 모든 value가 true인 경우에만 회원 가입 진행
const checkObj = {
"memberEmail" : false,
"memberPw" : false,
"memberPwConfirm" : false,
"memberNickname" : false,
"memberTel" : false,
"authKey" : false
};
// 이메일 유효성 검사
const memberEmail = document.getElementById("memberEmail");
const emailMessage = document.getElementById("emailMessage");
// 이메일이 입력될 때 마다
memberEmail.addEventListener("input", () => {
// 입력된 이메일이 없을 경우
if(memberEmail.value.trim().length == 0){
memberEmail.value = "";
emailMessage.innerText = "메일을 받을 수 있는 이메일을 입력해주세요.";
// confirm, error 클래스 삭제해서 검정 글씨로 만들기
emailMessage.classList.remove("confirm", "error");
checkObj.memberEmail = false; // 빈칸 == 유효 X
return;
}
// 정규 표현식을 이용해서 유효한 형식이지 판별
// 1) 정규표현식 객체 생성
const regEx = /^[A-Za-z\d\-\_]{4,}@[가-힣\w\-\_]+(\.\w+){1,3}$/;
// 2) 입력 받은 이메일과 정규식 일치 여부 판별
if( regEx.test(memberEmail.value) ){ // 유효한 경우
/* fetch() API를 이용한 ajax(비동기 통신) : 이메일 중복*/
// url : /dupCheck/email
// GET 방식
fetch("/dupCheck/email?email=" + memberEmail.value)
.then(res => res.text())
.then(count => {
// count : 중복되면 1, 중복 아니면 0
if(count == 0) {
emailMessage.innerText = "사용 가능한 이메일입니다.";
emailMessage.classList.add("confirm"); // .confirm 스타일 적용
emailMessage.classList.remove("error"); // .error 스타일 제거
checkObj.memberEmail = true;
} else {
emailMessage.innerText = "이미 사용중인 이메일입니다.";
emailMessage.classList.add("error"); // .error 스타일 적용
emailMessage.classList.remove("confirm"); // .confirm 스타일 제거
checkObj.memberEmail = false;
}
})
.catch(err => console.log(err));
} else{ // 유효하지 않은 경우(무효인 경우)
emailMessage.innerText = "이메일 형식이 유효하지 않습니다";
emailMessage.classList.add("error"); // .error 스타일 적용
emailMessage.classList.remove("confirm"); // .confirm 스타일 제거
checkObj.memberEmail = false; // 유효 X
}
});
// 비밀번호/비밀번호 확인 유효성 검사
const memberPw = document.getElementById("memberPw");
const memberPwConfirm = document.getElementById("memberPwConfirm");
const pwMessage = document.getElementById("pwMessage");
// 비밀번호 입력 시 유효성 검사
memberPw.addEventListener("input", () => {
// 비밀번호가 입력되지 않은 경우
if(memberPw.value.trim().length == 0){
memberPw.value = ""; // 띄어쓰지 못넣게 하기
pwMessage.innerText = "영어,숫자,특수문자(!,@,#,-,_) 6~20글자 사이로 입력해주세요.";
pwMessage.classList.remove("confirm", "error"); // 검정 글씨
checkObj.memberPw = false; // 빈칸 == 유효 X
return;
}
// 정규 표현식을 이용한 비밀번호 유효성 검사
// 영어,숫자,특수문자(!,@,#,-,_) 6~20글자 사이
const regEx = /^[a-zA-Z0-9\!\@\#\-\_]{6,20}$/;
// 입력한 비밀번호가 유효한 경우
if(regEx.test(memberPw.value)){
checkObj.memberPw = true;
// 비밀번호가 유효하게 작성된 상태에서
// 비밀번호 확인이 입력되지 않았을 때
if(memberPwConfirm.value.trim().length == 0){
pwMessage.innerText = "유효한 비밀번호 형식입니다";
pwMessage.classList.add("confirm");
pwMessage.classList.remove("error");
}else{
// 비밀번호가 유효하게 작성된 상태에서
// 비밀번호 확인이 입력되어 있을 때
// 비밀번호 == 비밀번호 확인 (같을 경우)
if(memberPw.value == memberPwConfirm.value){
pwMessage.innerText = "비밀번호가 일치합니다";
pwMessage.classList.add("confirm");
pwMessage.classList.remove("error");
checkObj.memberPwConfirm = true;
} else{ // 다를 경우
pwMessage.innerText = "비밀번호가 일치하지 않습니다";
pwMessage.classList.add("error");
pwMessage.classList.remove("confirm");
checkObj.memberPwConfirm = false;
}
}
} else{ // 유효하지 않은 경우
pwMessage.innerText = "비밀번호 형식이 유효하지 않습니다";
pwMessage.classList.add("error");
pwMessage.classList.remove("confirm");
checkObj.memberPw = false;
}
});
// 비밀번호 확인 유효성 검사
memberPwConfirm.addEventListener('input', ()=>{
if(checkObj.memberPw){ // 비밀번호가 유효하게 작성된 경우에
// 비밀번호 == 비밀번호 확인 (같을 경우)
if(memberPw.value == memberPwConfirm.value){
pwMessage.innerText = "비밀번호가 일치합니다";
pwMessage.classList.add("confirm");
pwMessage.classList.remove("error");
checkObj.memberPwConfirm = true;
} else{ // 다를 경우
pwMessage.innerText = "비밀번호가 일치하지 않습니다";
pwMessage.classList.add("error");
pwMessage.classList.remove("confirm");
checkObj.memberPwConfirm = false;
}
} else { // 비밀번호가 유효하지 않은 경우
checkObj.memberPwConfirm = false;
}
});
// 닉네임 유효성 검사
const memberNickname = document.getElementById("memberNickname");
const nickMessage = document.getElementById('nickMessage');
// 닉네임이 입력이 되었을 때
memberNickname.addEventListener("input", ()=>{
// 닉네임 입력이 되지 않은 경우
if(memberNickname.value.trim() == ''){
nickMessage.innerText = "한글,영어,숫자로만 2~10글자";
nickMessage.classList.remove("confirm", "error");
checkObj.memberNickname = false;
memberNickname.value = "";
return;
}
// 정규표현식으로 유효성 검사
const regEx = /^[가-힣\w\d]{2,10}$/;
if(regEx.test(memberNickname.value)){// 유효
/* fetch() API를 이용한 ajax(비동기 통신) : 닉네임 중복검사 */
// url : /dupCheck/nickname
fetch("/dupCheck/nickname?nickname=" + memberNickname.value)
.then(resp => resp.text())
.then(count => {
// count : 중복되면 1, 중복 아니면 0
if(count == 0) {
nickMessage.innerText = "사용 가능한 닉네임입니다.";
nickMessage.classList.add("confirm"); // .confirm 스타일 적용
nickMessage.classList.remove("error"); // .error 스타일 제거
checkObj.memberNickname = true;
} else {
nickMessage.innerText = "이미 사용중인 닉네임입니다.";
nickMessage.classList.add("error"); // .error 스타일 적용
nickMessage.classList.remove("confirm"); // .confirm 스타일 제거
checkObj.memberNickname = false;
}
})
.catch(err => console.log(err));
} else{ // 무효
nickMessage.innerText = "닉네임 형식이 유효하지 않습니다";
nickMessage.classList.add("error");
nickMessage.classList.remove("confirm");
checkObj.memberNickname = false;
}
});
// 전화번호 유효성 검사
const memberTel = document.getElementById("memberTel");
const telMessage = document.getElementById("telMessage");
// 전화번호가 입력 되었을 때
memberTel.addEventListener("input", ()=>{
// 전화번호가 입력이 되지 않은 경우
if(memberTel.value.trim() == ''){
telMessage.innerText = "전화번호를 입력해주세요.(- 제외)";
telMessage.classList.remove("confirm", "error");
checkObj.memberTel = false;
memberTel.value = "";
return;
}
// 정규표현식으로 유효성 검사
const regEx = /^0(1[01679]|2|[3-6][1-5]|70)[1-9]\d{2,3}\d{4}$/;
if(regEx.test(memberTel.value)){// 유효
telMessage.innerText = "유효한 전화번호 형식입니다";
telMessage.classList.add("confirm");
telMessage.classList.remove("error");
checkObj.memberTel = true;
} else{ // 무효
telMessage.innerText = "전화번호 형식이 유효하지 않습니다";
telMessage.classList.add("error");
telMessage.classList.remove("confirm");
checkObj.memberTel = false;
}
});
// --------------------- 이메일 인증 ---------------------
// 인증번호 발송
const sendAuthKeyBtn = document.getElementById("sendAuthKeyBtn");
const authKeyMessage = document.getElementById("authKeyMessage");
let authTimer;
let authMin = 4;
let authSec = 59;
// 인증번호를 발송한 이메일 저장
let tempEmail;
sendAuthKeyBtn.addEventListener("click", function(){
authMin = 4;
authSec = 59;
checkObj.authKey = false;
if(checkObj.memberEmail){ // 중복이 아닌 이메일인 경우
/* fetch() API 방식 ajax */
fetch("/sendEmail/signUp?email="+memberEmail.value)
.then(resp => resp.text())
.then(result => {
if(result > 0){
console.log("인증 번호가 발송되었습니다.")
tempEmail = memberEmail.value;
}else{
console.log("인증번호 발송 실패")
}
})
.catch(err => {
console.log("이메일 발송 중 에러 발생");
console.log(err);
});
alert("인증번호가 발송 되었습니다.");
authKeyMessage.innerText = "05:00"; // 1초마다 줄어들면서 갱신
// 인증 시: 인증완료되었습니다
// 실패 시: 인증실패되었습니다..
authKeyMessage.classList.remove("confirm");
authTimer = window.setInterval(()=>{
// 삼항연산자 : 조건 ? true : false
authKeyMessage.innerText = "0" + authMin + ":" + (authSec < 10 ? "0" + authSec : authSec);
// 남은 시간이 0분 0초인 경우
if(authMin == 0 && authSec == 0){
checkObj.authKey = false;
clearInterval(authTimer);
return;
}
// 0초인 경우
if(authSec == 0){
authSec = 60;
authMin--;
}
authSec--; // 1초 감소
}, 1000)
} else{
alert("중복되지 않은 이메일을 작성해주세요.");
memberEmail.focus();
}
});
// 인증 확인
const authKey = document.getElementById("authKey");
const checkAuthKeyBtn = document.getElementById("checkAuthKeyBtn");
checkAuthKeyBtn.addEventListener("click", function(){
if(authMin > 0 || authSec > 0){ // 시간 제한이 지나지 않은 경우에만 인증번호 검사 진행
/* fetch API */
const obj = {"inputKey":authKey.value, "email":tempEmail}
const query = new URLSearchParams(obj).toString()
// inputKey=123456&email=user01
fetch("/sendEmail/checkAuthKey?" + query)
.then(resp => resp.text())
.then(result => {
if(result > 0){
clearInterval(authTimer);
authKeyMessage.innerText = "인증되었습니다.";
authKeyMessage.classList.add("confirm");
checkObj.authKey = true;
} else{
alert("인증번호가 일치하지 않습니다.")
checkObj.authKey = false;
}
})
.catch(err => console.log(err));
} else{
alert("인증 시간이 만료되었습니다. 다시 시도해주세요.")
}
});
// 회원 가입 form태그가 제출 되었을 때
document.getElementById("signUpFrm").addEventListener("submit", e=>{
// checkObj에 모든 value가 true인지 검사
// (배열용 for문)
// for ... of : 향상된 for문
// -> iterator(반복자) 속성을 지닌 배열, 유사 배열 사용 가능
// (객체용 for문)
// ** for ... in 구문 ***
// -> JS 객체가 가지고 있는 key를 순서대로 하나씩 꺼내는 반복문
for(let key in checkObj){
if(!checkObj[key]){ // 각 key에 대한 value(true/false)를 얻어와
// false인 경우 == 유효하지 않다!
switch(key){
case "memberEmail":
alert("이메일이 유효하지 않습니다"); break;
case "memberPw":
alert("비밀번호가 유효하지 않습니다"); break;
case "memberPwConfirm":
alert("비밀번호가 확인되지 않았습니다"); break;
case "memberNickname" :
alert("닉네임이 유효하지 않습니다"); break;
}
// 유효하지 않은 input 태그로 focus 이동
// - key를 input의 id와 똑같이 설정했음!
document.getElementById(key).focus();
e.preventDefault(); // form 태그 기본 이벤트 제거
return; // 함수 종료
}
}
});
-> 회원가입 성공!
CF ) GIT 할 때 이 파일 꼭 추가하여 진행하기!
- 파일 내 맨 하단에 config.properties 문구 추가하기
=> config.properties 제외하고 git에 올라감!
C:\workspace\8_Boot\boardProject-boot