[SpringBoot] TIL 077 - 23.11.13

유진·2023년 11월 12일
0
post-thumbnail

SpringBoot : 회원가입, 이메일

MemberController.java

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;
	}
   

}

signUp.html

<!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 관련 파일 옮기기

AjaxController.java

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);
	}
	
	
	
}

AjaxService.java

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);
}

AjaxServiceImpl.java

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);
	}

}

AjaxMapper.java

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);

}

AjaxDAO.java

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);
	}
	
}

ajax-mapper.xml

<?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>


이메일

build.gradle

추가 후
프로젝트 우클릭 > Gradle > Project Gradle Refresh 꼭 하기!

config.properties

EmailConfig.java

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 등록됨
	}
	
	
}

EmailController.java

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);
	}
}

EmailService.java

package edu.kh.project.member.model.service;

public interface EmailService {

	int signUp(String email, String string);

	int checkAuthKey(String inputKey, String email);

}

EmailServiceImpl.java

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;
	}
	
}

EmailMapper.java

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);
	
}

EmailDAO.java

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);
	}
	
}

email-mapper.xml

<?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>

signUp.js

// 회원 가입 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

0개의 댓글