07_Framework
<%@ 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
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으로 변환!
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);
}
}
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);
}
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);
}
}
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);
}
}
<?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>
<?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
회원가입 클릭 시, 해당 화면으로 넘어감
// 회원 가입 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; // 함수 종료
}
}
});
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);
}
}
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);
}
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);
}
}
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);
}
}
<?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>
// 회원 가입 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; // 함수 종료
}
}
});
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.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);
}
}
<?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>