[Framework] TIL 056 - 23.10.06

유진·2023년 10월 5일
0

07_Framework

main.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
    
<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title>프로젝트</title>
	
	<!-- fontaswesom 아이콘 사용할 수 있는 스크립트 연결-->
	<script src="https://kit.fontawesome.com/f821b57119.js" crossorigin="anonymous"></script>
</head>
<body>
	<main>
        
        
        <%-- header.jsp 추가 --%>
        
        <%--
        	<jsp:include page="jsp파일경로" />
        	
        	- jsp 파일 경로는 'webapp 폴더 기준'으로 작성
        	- JSP 액션 태그(jsp에 기본 내장됨)
        	- 다른 jsp 파일의 코드를 현재 위치에 추가
        
         --%>
         
        <jsp:include page="/WEB-INF/views/common/header.jsp" />
		

        <section class="content">
            <section class="content-1">
            	<h3>로그인된 회원 정보</h3>
             	${sessionScope.loginMember}
             	
             	
             	<h3>닉네임이 일치하는 회원의 전화번호 조회</h3>
            	
            	<input type="text" id="inputNickname">
            	<button id="btn1">조회</button>
            	<h4 id="result1"></h4>
            	
            	<hr>
            	
            	<h3>이메일을 입력받아 일치하는 회원의 정보를 조회</h3>
            	<input id="inputEmail">
            	<button id="btn2">조회</button>
            	<ul id="result2">
            	</ul>
            
            
            </section>
            

            <!-- 아이디/비밀번호/로그인버튼 영역 -->
            <section class="content-2">
            
            	<c:choose>
            		<%-- 로그인이 안되었을때 --%>
            		<%-- EL empty : 비어있거나 null이면 true --%>
            		<c:when test="${empty sessionScope.loginMember}">
            		
	            		<form action="/member/login" method="post" name="login-form" id="loginFrm">
	
		                    <fieldset class="id-pw-area">
		                        <section>
		                            <input type="text" name="memberEmail" placeholder="이메일"
		                            	autocomplete="off"
		                            	value="${cookie.saveId.value}"
		                            	>
		                            <input type="password" name="memberPw" placeholder="비밀번호">
		                        </section>
		
		                        <section>
		                            <button>로그인</button>
		                        </section>
		                    </fieldset>
		
		                    <label>
		                    
		                    	<c:if test="${not empty cookie.saveId.value}">
		                    		<%-- 쿠키에 저장된 이메일이 있으면 변수 선언 : save --%>
		                    		
		                    		<c:set var="save" value="checked"/>
		                    	</c:if>
		                    	
		                        <input type="checkbox" name="saveId" ${save}> 아이디 저장
		                        
		                    </label>
		
		                    <!-- 회원가입/ Id/pw 찾기 영역 -->
		                    <section class="signup-find-area">
		                        <a href="#">회원가입</a>
		                        <span>|</span>
		                        <a href="#">ID/PW 찾기</a>
		                    </section>
	
	                	</form>
            		</c:when>
            		
            		<%-- 로그인이 되었을때 --%>
            		<c:otherwise>
            			<article class="login-area">
            				
            				<a href="#">
            					<img src="/resources/images/user.png" id="memberProfile">
            				</a>
            				
            				<div class="my-info">
            					<div>
            						<a href="#" id="nickname">${sessionScope.loginMember.memberNickname}</a>
            						<a href="/member/logout" id="logoutBtn">로그아웃</a>
            					</div>
            					
            					<p></p>
            				</div>
            				
            			</article>
            			
            			
            		</c:otherwise>
            	</c:choose>
            
            

            </section>

        </section>
       

    </main>
    
    <jsp:include page="/WEB-INF/views/common/footer.jsp" />

	<!-- main.js 추가 -->
	<script src="/resources/js/main.js"></script>

</body>
</html>

TIP !

  • sts에서 js 파일, 흑백이 아닌 js파일처럼 여는 법
    js 파일 우클릭 > Open With > Other > js 검색 후 JS Editor 클릭 후 OK

main.js


const loginFrm = document.getElementById("loginFrm");

const memberEmail = document.querySelector("#loginFrm input[name='memberEmail']");
const memberPw = document.querySelector("#loginFrm input[name='memberPw']");

if(loginFrm != null){
    // 로그인 시도를 할 때
    loginFrm.addEventListener("submit", e => {

        // 이메일이 입력되지 않은 경우
        // 문자열.trim() : 문자열 좌우 공백 제거
        if(memberEmail.value.trim().length == 0){
            alert("이메일을 입력해주세요.");

            memberEmail.value = ""; // 잘못 입력된 값(공백) 제거
            memberEmail.focus(); // 이메일 input태그에 초점을 맞춤

            e.preventDefault(); // (기본이벤트 제거 : 제출 못하게하기)
            return; 
        }


        // 비밀번호가 입력되지 않은 경우
        if(memberPw.value.trim().length == 0){
            alert("비밀번호를 입력해주세요.");

            memberPw.value = ""; // 잘못 입력된 값(공백) 제거
            memberPw.focus(); // 이메일 input태그에 초점을 맞춤

            e.preventDefault(); // 제출 못하게하기
            return; 
        }


    });
}




// -----------------------------------------------

// fetch API : 웹 브라우저에서 서버로 HTTP 요청을 하게해주는 최신 인터페이스

/** 
 * fetch(url)
 * .then(response => response.json() / response.text())          // 파싱
 * .then(data => console.log(data))                              // 데이터 가공
 * .catch(error => console.log(error));
 * 
 * 첫 번째 then() 함수는 서버 요청에 대한 응답이 왔을때 실행됨
 * - 응답받은 데이터가 반환되는 값이 단순 자료형 1개면 text(),
 * 객체(Map)면 json() 으로 파싱(구문해석)한 후 다음 then() 함수로 넘겨준다.
 * 
 * 
 * 두 번째 then() 함수는 response.json()/text()으로 상황에 맞게
 * 데이터가 파싱 완료되면 실행.
 * 파싱된 데이터가 전달되며, 이 값을 로직에 맞게 가공한다.
 * 
 * 
*/

// 닉네임이 일치하는 회원의 전화번호 조회
const inputNickname = document.getElementById("inputNickname");
const btn1 = document.getElementById("btn1");
const result1 = document.getElementById("result1");

btn1.addEventListener("click", () => {

    // fetch API를 이용해서 ajax
    // GET 방식 요청 (파라미터를 쿼리스트링으로 추가)

    // Promise : 비동기 함수 호출 또는 연산이 완료되었을 때
    //         이후에 처리할 함수나 에러를 처리하기 위한
    //         함수를 설정하는 모듈
    //         -> 비동기 연산의 최종 결과 객체

    fetch("/selectMemberTel?nickname=" + inputNickname.value)
    .then( resp => resp.text() ) // 응답 객체(자료형 1일때)를 문자열 형식으로 파싱
    .then( data => {
        // 데이터 가공
        console.log(data);
        result1.innerText = data;
    })
    .catch( err => console.log(err) );

});

// fetch() API 를 이용한 POST 방식 요청

// 이메일을 입력받아 일치하는 회원의 정보를 조회
const inputEmail = document.getElementById("inputEmail");
const btn2 = document.getElementById("btn2");
const result2 = document.getElementById("result2");

btn2.addEventListener("click", () => {

    // JSON.stringify() : JS 객체 -> JSON
    // JSON.parse()     : JSON    -> JS 객체

    // JSON : Javascript 객체 문법으로, 구조화된 데이터를 표현하기 위한
    //          문자 기반의 표준 포맷이다.
    //          서버에서 클라이언트로 데이터를 전송하여 표현하거나,
    //          그 반대의 경우에 사용한다.

    // POST 방식 
    fetch("/selectMember", { // K:V 형식으로 작성해야 함
        method : "POST",
        headers : {"Content-Type" : "application/json"},
                // 요청 보내는 자원을 명시
                // -> js 객체를 json 형식으로 만들어 파라미터로 전달
        body : JSON.stringify({"email" : inputEmail.value}) // JS객체 형태 : { K : V }
    })
    .then(resp => resp.json()) // 응답 객체를 자바스크립트 객체 형태로
                                // 파싱하는것
    .then(member => {
        console.log(member); // javascript 객체
    })
    .catch( err => console.log(err) );

});

JAVA <-> 'JSON' <-> JavaScript : 항상 JSON으로 변환!

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

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

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

}

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 // bean 중에서 타입이 같은 객체를 DI(의존성 주입)
	private SqlSessionTemplate sqlSession;

	// 닉네임으로 전화번호 조회
	public String selectMemberTel(String nickname) {
		
		return sqlSession.selectOne("ajaxMapper.selectMemberTel", nickname);
	}

	
	// 이메일로 회원정보 조회
	public Member selectMember(String email) {
		
		return sqlSession.selectOne("ajaxMapper.selectMember", email);
	}
	
}

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="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>

</mapper>

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>edu.kh</groupId>
	<artifactId>project</artifactId>
	<name>boardProject</name>
	<packaging>war</packaging>
	<version>1.0.0-BUILD-SNAPSHOT</version>
	
	<!-- properties : 메이븐이 적용된 프로젝트에서 공통적으로 사용할 값을 작성하는 태그 -->
	<properties>
		<java-version>11</java-version>
		<org.springframework-version>5.3.14</org.springframework-version>
		<org.aspectj-version>1.9.9.1</org.aspectj-version>
		<org.slf4j-version>1.7.25</org.slf4j-version>
	</properties>
	
	<!-- dependencies : Maven 프로젝트는 외부 저장소와 의존 관계를 맺고 있어 프로젝트에 필요한 파일(라이브러리)을
		사용자가 직접 다운 받을 필요 없이, 해당 태그 내에 지정된 형식으로 작성하면 네트워크를 통해 외부 저장소에서 자동으로 얻어와 세팅함
	 -->
	<dependencies>
	
		<!-- lombok 라이브러리 -->
		<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
		<dependency>
		    <groupId>org.projectlombok</groupId>
		    <artifactId>lombok</artifactId>
		    <version>1.18.24</version>
		    <scope>provided</scope>
		</dependency>
		
		<!-- 오라클 JDBC 드라이버 -->
		<!-- https://mvnrepository.com/artifact/com.oracle.database.jdbc/ojdbc11 -->
		<dependency>
		    <groupId>com.oracle.database.jdbc</groupId>
		    <artifactId>ojdbc11</artifactId>
		    <version>21.5.0.0</version>
		</dependency>
		
		<!-- 스프링에서 JDBC를 사용할 수 있게하는 라이브러리 -->
		<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
		<dependency>
		    <groupId>org.springframework</groupId>
		    <artifactId>spring-jdbc</artifactId>
		    <version>${org.springframework-version}</version>
		</dependency>
		
		<!-- Mybatis 영속성 프레임워크 -->
		<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
		<dependency>
		    <groupId>org.mybatis</groupId>
		    <artifactId>mybatis</artifactId>
		    <version>3.5.9</version>
		</dependency>
		
		<!-- Spring - Mybatis 연결 모듈, 연결 역할을 하는 라이브러리 -->
		<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
		<dependency>
		    <groupId>org.mybatis</groupId>
		    <artifactId>mybatis-spring</artifactId>
		    <version>2.0.6</version>
		</dependency>
		
		<!-- 커넥션 풀 기능을 사용하기 위한 라이브러리 -->
		<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 -->
		<dependency>
		    <groupId>org.apache.commons</groupId>
		    <artifactId>commons-dbcp2</artifactId>
		    <version>2.9.0</version>
		</dependency>
		
		
		<!-- Spring-security 모듈 추가 -->
		<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-core -->
		<dependency>
		    <groupId>org.springframework.security</groupId>
		    <artifactId>spring-security-core</artifactId>
		    <version>5.7.1</version>
		</dependency>
		
		<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-web -->
		<dependency>
		    <groupId>org.springframework.security</groupId>
		    <artifactId>spring-security-web</artifactId>
		    <version>5.7.1</version>
		</dependency>
		
		<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-config -->
		<dependency>
		    <groupId>org.springframework.security</groupId>
		    <artifactId>spring-security-config</artifactId>
		    <version>5.7.1</version>
		</dependency>
		
		
		<!-- Jackson-databind -->
		<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
		<dependency>
		    <groupId>com.fasterxml.jackson.core</groupId>
		    <artifactId>jackson-databind</artifactId>
		    <version>2.14.2</version>
		</dependency>
		
		
		
		
		
		<!-- Spring 모듈 -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${org.springframework-version}</version>
			<exclusions>
				<!-- Exclude Commons Logging in favor of SLF4j -->
				<exclusion>
					<groupId>commons-logging</groupId>
					<artifactId>commons-logging</artifactId>
				 </exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${org.springframework-version}</version>
		</dependency>
				
		<!-- AspectJ : AOP 기능을 사용하기 위한 언어 문법 -->
		<!-- aspectjrt : AspectJ 런타임 프로그램 -->
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjrt</artifactId>
			<version>${org.aspectj-version}</version>
		</dependency>	
		
		<!-- AspectJ Weaver : aspect의 정보를 바탕으로 aspect를 구성한 코드를 생성하는데 필요한 유틸리티 프로그램 -->
		<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjweaver</artifactId>
			<version>${org.aspectj-version}</version>
		</dependency>
		
		<!-- Log4j (Logging) -->
		<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
		<dependency>
		    <groupId>org.apache.logging.log4j</groupId>
		    <artifactId>log4j-core</artifactId>
		    <version>2.17.1</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>${org.slf4j-version}</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>jcl-over-slf4j</artifactId>
			<version>${org.slf4j-version}</version>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>${org.slf4j-version}</version>
			<scope>runtime</scope>
		</dependency>

		<!-- @Inject -->
		<dependency>
			<groupId>javax.inject</groupId>
			<artifactId>javax.inject</artifactId>
			<version>1</version>
		</dependency>
				
		<!-- Servlet -->
		<!-- Servlet 버전을 4.0으로 변경 -->
		<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
		<dependency>
		    <groupId>javax.servlet</groupId>
		    <artifactId>javax.servlet-api</artifactId>
		    <version>4.0.1</version>
		    <scope>provided</scope>
		</dependency>

		<dependency>
			<groupId>javax.servlet.jsp</groupId>
			<artifactId>jsp-api</artifactId>
			<version>2.1</version>
			<scope>provided</scope>
		</dependency>
		
		
		<!-- 단위 테스트 도구 (JUnit) -->
		<!-- https://mvnrepository.com/artifact/junit/junit -->
		<dependency>
		    <groupId>junit</groupId>
		    <artifactId>junit</artifactId>
		    <version>4.13.2</version>
		    <scope>test</scope>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
		<dependency>
		    <groupId>org.springframework</groupId>
		    <artifactId>spring-test</artifactId>
		    <version>${org.springframework-version}</version>
		    <scope>test</scope>
		</dependency>    
	</dependencies>
	
	<!-- build : 프로젝트 빌드 시 사용되는 플러그인 추가 및 버전 정보 설정 -->
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-eclipse-plugin</artifactId>
                <version>2.9</version>
                <configuration>
                    <additionalProjectnatures>
                        <projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
                    </additionalProjectnatures>
                    <additionalBuildcommands>
                        <buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>
                    </additionalBuildcommands>
                    <downloadSources>true</downloadSources>
                    <downloadJavadocs>true</downloadJavadocs>
                </configuration>
            </plugin>
            
            <!-- 컴파일러 플러그인은 프로젝트의 소스(자바코드)를 컴파일하는 데 사용
			jdk 1.6 이상 사용 시 3.0 이상 버전을 사용, source, taget에는 사용하는 jdk 버전을 작성 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                    <compilerArgument>-Xlint:all</compilerArgument>
                    <showWarnings>true</showWarnings>
                    <showDeprecation>true</showDeprecation>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.2.1</version>
                <configuration>
                    <mainClass>org.test.int1.Main</mainClass>
                </configuration>
            </plugin>
            
            <!-- Could not initialize class org.apache.maven.plugin.war.util.WebappStructureSerializer -->
			<!-- 메이븐 구성 문제로 인한 pom.xml 오류 발생 시 해결 -->
			<plugin>
				<artifactId>maven-war-plugin</artifactId>
				<version>3.2.2</version>
			</plugin>
        </plugins>
    </build>
</project>


-> 꼭 추가하기!

[ 결과 ]

이메일 입력 후 조회 버튼 클릭 시, 콘솔창에 뜸
AjaxController.java 콘솔창에 뜸


회원가입
: 이메일 / 닉네임 중복체크

css/member/signUp-style.css
js/member/singUp.js
views/member/signUp.jsp

main.jsp

MemberController.java


회원가입 클릭 시, 해당 화면으로 넘어감


1) 이메일 중복체크

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

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


});


// --------------------- 이메일 인증 ---------------------

// 인증번호 발송


// 인증 확인




// 회원 가입 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; // 함수 종료
        }
    }
});

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

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
	 */
	int checkEmail(String email);
}

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

}

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 // bean 중에서 타입이 같은 객체를 DI(의존성 주입)
	private SqlSessionTemplate sqlSession;

	// 닉네임으로 전화번호 조회
	public String selectMemberTel(String nickname) {
		
		return sqlSession.selectOne("ajaxMapper.selectMemberTel", nickname);
	}

	
	// 이메일로 회원정보 조회
	public Member selectMember(String email) {
		
		return sqlSession.selectOne("ajaxMapper.selectMember", email);
	}

	// 이메일 중복검사
	public int checkEmail(String email) {
		
		return sqlSession.selectOne("ajaxMapper.checkEmail", email);
	}
	
}

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="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>

</mapper>
  • 이메일: 중복될 경우
  • 이메일: 중복되지 않을 경우

2) 닉네임 중복체크

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


});


// --------------------- 이메일 인증 ---------------------

// 인증번호 발송


// 인증 확인




// 회원 가입 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; // 함수 종료
        }
    }
});

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

}

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 // bean 중에서 타입이 같은 객체를 DI(의존성 주입)
	private SqlSessionTemplate sqlSession;

	// 닉네임으로 전화번호 조회
	public String selectMemberTel(String nickname) {
		
		return sqlSession.selectOne("ajaxMapper.selectMemberTel", nickname);
	}

	
	// 이메일로 회원정보 조회
	public Member selectMember(String email) {
		
		return sqlSession.selectOne("ajaxMapper.selectMember", email);
	}

	// 이메일 중복검사
	public int checkEmail(String email) {
		
		return sqlSession.selectOne("ajaxMapper.checkEmail", email);
	}

	// 닉네임 중복검사
	public int checkNickname(String nickname) {
		
		return sqlSession.selectOne("ajaxMapper.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="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>
  • 닉네임: 중복될 경우
  • 닉네임: 중복되지 않을 경우

0개의 댓글