springPage
src/main/webapp/WEB-INF/tiles-def
폴더의 main.xml
을 다음처럼 수정<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE tiles-definitions PUBLIC
"-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN"
"http://tiles.apache.org/dtds/tiles-config_3_0.dtd">
<tiles-definitions>
<definition name="main" template="/WEB-INF/views/template/layout.jsp">
<put-attribute name="title" value="Spring 회원제 게시판"/>
<put-attribute name="header" value="/WEB-INF/views/template/header.jsp"/>
<put-attribute name="left" value="/WEB-INF/views/template/left.jsp"/>
<put-attribute name="body" value="/WEB-INF/views/main/main.jsp"/>
<put-attribute name="footer" value="/WEB-INF/views/template/footer.jsp"/>
</definition>
</tiles-definitions>
tiles-def
폴더에 새 XML 파일 member.xml
생성<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE tiles-definitions PUBLIC
"-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN"
"http://tiles.apache.org/dtds/tiles-config_3_0.dtd">
<tiles-definitions>
<!-- 사용자 -->
<definition name="memberRegister" extends="main">
<put-attribute name="title" value="회원 가입"/>
<put-attribute name="body" value="/WEB-INF/views/member/memberRegister.jsp"/>
</definition>
<definition name="memberLogin" extends="main">
<put-attribute name="title" value="회원 로그인"/>
<put-attribute name="body" value="/WEB-INF/views/member/memberLogin.jsp"/>
</definition>
<definition name="memberView" extends="main">
<put-attribute name="title" value="MyPage"/>
<put-attribute name="body" value="/WEB-INF/views/member/memberView.jsp"/>
</definition>
<definition name="memberModify" extends="main">
<put-attribute name="title" value="회원 정보 수정"/>
<put-attribute name="body" value="/WEB-INF/views/member/memberModify.jsp"/>
</definition>
</tiles-definitions>
src/main/webapp/sql
폴더의 table.sql
을 다음처럼 수정/* 회원 관리 */
CREATE TABLE spmember(
mem_num NUMBER NOT NULL,
id VARCHAR2(12) UNIQUE NOT NULL,
auth NUMBER(1) DEFAULT 2 NOT NULL, /* 0=탈퇴, 1=정지, 2=일반, 3=관리자 */
CONSTRAINT spmember_pk PRIMARY KEY (mem_num)
);
CREATE TABLE spmember_detail(
mem_num NUMBER NOT NULL,
name VARCHAR2(30) NOT NULL,
passwd VARCHAR2(35) NOT NULL,
phone VARCHAR2(15) NOT NULL,
email VARCHAR2(50) NOT NULL,
zipcode VARCHAR2(5) NOT NULL,
address1 VARCHAR2(90) NOT NULL,
address2 VARCHAR2(90) NOT NULL,
photo BLOB,
photo_name VARCHAR2(100),
reg_date DATE DEFAULT SYSDATE NOT NULL,
modify_date DATE,
CONSTRAINT spmember_detail_pk PRIMARY KEY (mem_num),
CONSTRAINT spmember_detail_fk FOREIGN KEY (mem_num) REFERENCES spmember (mem_num)
);
CREATE SEQUENCE spmember_seq;
servlet-context.xml
에서 <beans:property name="definitions">
태그 사이의 내용을 다음처럼 수정 <beans:list>
<beans:value>/WEB-INF/tiles-def/main.xml</beans:value>
<beans:value>/WEB-INF/tiles-def/member.xml</beans:value>
</beans:list>
src/main/webapp/WEB-INF/spring
에서 root-context.xml
에서 kr.spring.board
를 kr.spring.member
로 일괄 변경servlet-context.xml
에서 <beans:beans>
태그 사이에 다음의 내용을 추가 <!-- 로그인 여부 검사 -->
<interceptors>
<interceptor>
<mapping path="/member/myPage.do"/>
<mapping path="/member/update.do"/>
<beans:bean class="kr.spring.interceptor.LoginCheckInterceptor"/>
</interceptor>
</interceptors>
src/main/resources/messages
폴더의 validation.properties
를 다음처럼 수정# 회원 관리
Pattern.id=4자
이상, 12자 이하의 영문, 숫자만 입력하세요
NotEmpty.name=이름은 필수 항목입니다
Pattern.passwd=4자 이상, 12자 이하의 영문, 숫자만 입력하세요
Pattern.now_passwd=4자 이상, 12자 이하의 영문, 숫자만 입력하세요
NotEmpty.email=이메일은 필수 항목입니다
Email.email=이메일 형식에 맞게 입력하세요
NotEmpty.phone=전화번호는 필수 항목입니다
Size.zipcode=우편번호는 5자만 입력 가능합니다
NotEmpty.address1=주소는 필수 항목입니다
NotEmpty.address2=상세 주소는 필수 항목입니다
invalidIdOrPassword=아이디 또는 비밀번호가 불일치합니다
invalidPassword=비밀번호가 불일치합니다
src/main/webapp/resources
에 새 폴더 js
생성하고 jquery-3.6.0.min.js
를 해당 폴더로 이동kr.spring.util
패키지에 새 클래스 AuthCheckException
생성package kr.spring.util;
public class AuthCheckException extends Exception { // 사용자 정의 예외 클래스
}
src/main/webapp/WEB-INF/views/main
폴더의 main.jsp
를 다음처럼 수정<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!-- 메인 시작 -->
<div class="page-main">
<h2>메인 화면</h2>
</div>
<!-- 메인 끝 -->
src/main/java
폴더 오른쪽 클릭하여 새 패키지 kr.spring.main.controller
생성하고 새 클래스 MainController
생성package kr.spring.main.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class MainController {
@RequestMapping("/main/main.do")
public String main() {
// Tiles 설정 반환
return "main";
}
}
views
폴더에 하위 폴더로 member
생성하고 새 JSP 파일 memberRegister.jsp
생성<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<!-- 중앙 컨텐츠 시작 -->
<script type="text/javascript" src="${pageContext.request.contextPath}/resources/js/jquery-3.6.0.min.js"></script>
<script type="text/javascript">
$(function() {
let checkId = 0;
// 아이디 중복 검사
$('#confirmId').click(function() {
if($('#id').val().trim()=='') {
$('#message_id').css('color', 'red').text('아이디를 입력하세요');
$('#id').val('').focus();
return;
}
$.ajax({
url:'confirmId.do',
type:'post',
data:{id:$('#id').val()},
dataType:'json',
cache:false,
timeout:30000,
success:function(param) {
if(param.result=='idNotFound') {
$('#message_id').css('color', '#000').text('등록 가능 아이디');
checkId = 1;
}
else if(param.result=='idDuplicated') {
$('#message_id').css('color', 'red').text('중복된 아이디');
$('#id').val('').focus();
checkId = 0;
}
else if(param.result=='notMatchPattern') {
$('#message_id').css('color', 'red').text('4~12자의 영문, 숫자만 가능');
$('#id').val('').focus();
checkId = 0;
}
else {
checkId = 0;
alert('아이디 중복 검사시 오류 발생!');
}
},
error:function() {
checkId = 0;
alert('네트워크 오류 발생!');
}
}); // end of ajax
}); // end of click
// 아이디 중복 안내 메시지 및 중복 여부 값을 초기화
$('#id').keyup(function() {
checkId = 0;
$('#message_id').text('');
}); // end of keyup
// submit 이벤트 발생시 아이디 중복 검사 여부 확인
$('#register_form').submit(function() {
if(checkId==0) {
$('#message_id').css('color', 'red').text('아이디 중복 검사 필수');
$('#id').focus().val().trim(); // trim()은 jQuery 객체를 반환하지 않기 때문에, focus()를 trim() 전에 사용해야 함
return false;
}
}); // end of submit
});
</script>
<div class="page-main">
<h2>회원 가입</h2>
<form:form modelAttribute="memberVO" action="registerUser.do" id="register_form">
<form:errors element="div" cssClass="error-color"/>
<ul>
<li>
<form:label path="id">아이디</form:label>
<form:input path="id" placeholder="4~12자의 영문, 숫자만 가능"/>
<input type="button" id="confirmId" value="ID 중복 검사">
<span id="message_id"></span>
<form:errors path="id" cssClass="error-color"/>
</li>
<li>
<form:label path="name">이름</form:label>
<form:input path="name"/>
<form:errors path="name" cssClass="error-color"/>
</li>
<li>
<form:label path="passwd">비밀번호</form:label>
<form:password path="passwd" placeholder="4~12자의 영문, 숫자만 가능"/>
<form:errors path="passwd" cssClass="error-color"/>
</li>
<li>
<form:label path="phone">전화번호</form:label>
<form:input path="phone"/>
<form:errors path="phone" cssClass="error-color"/>
</li>
<li>
<form:label path="email">이메일</form:label>
<form:input path="email"/>
<form:errors path="email" cssClass="error-color"/>
</li>
<li>
<form:label path="zipcode">우편번호</form:label>
<form:input path="zipcode"/>
<input type="button" onclick="sample2_execDaumPostcode()" value="우편번호 찾기">
<form:errors path="zipcode" cssClass="error-color"/>
</li>
<li>
<form:label path="address1">주소</form:label>
<form:input path="address1"/>
<form:errors path="address1" cssClass="error-color"/>
</li>
<li>
<form:label path="address2">상세 주소</form:label>
<form:input path="address2"/>
<form:errors path="address2" cssClass="error-color"/>
</li>
</ul>
<div class="align-center">
<form:button>전송</form:button>
<input type="button" value="홈으로" onclick="location.href = '${pageContext.request.contextPath}/main/main.do';">
</div>
</form:form>
<!-- 우편번호 스크립트 시작 -->
<!-- iOS에서는 position:fixed 버그가 있음, 적용하는 사이트에 맞게 position:absolute 등을 이용하여 top,left값 조정 필요 -->
<div id="layer" style="display:none;position:fixed;overflow:hidden;z-index:1;-webkit-overflow-scrolling:touch;">
<img src="//t1.daumcdn.net/postcode/resource/images/close.png" id="btnCloseLayer" style="cursor:pointer;position:absolute;right:-3px;top:-3px;z-index:1" onclick="closeDaumPostcode()" alt="닫기 버튼">
</div>
<script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
<script>
// 우편번호 찾기 화면을 넣을 element
var element_layer = document.getElementById('layer');
function closeDaumPostcode() {
// iframe을 넣은 element를 안보이게 한다.
element_layer.style.display = 'none';
}
function sample2_execDaumPostcode() {
new daum.Postcode({
oncomplete: function(data) {
// 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분.
// 각 주소의 노출 규칙에 따라 주소를 조합한다.
// 내려오는 변수가 값이 없는 경우엔 공백('')값을 가지므로, 이를 참고하여 분기 한다.
var addr = ''; // 주소 변수
var extraAddr = ''; // 참고항목 변수
//사용자가 선택한 주소 타입에 따라 해당 주소 값을 가져온다.
if (data.userSelectedType === 'R') { // 사용자가 도로명 주소를 선택했을 경우
addr = data.roadAddress;
} else { // 사용자가 지번 주소를 선택했을 경우(J)
addr = data.jibunAddress;
}
// 사용자가 선택한 주소가 도로명 타입일때 참고항목을 조합한다.
if(data.userSelectedType === 'R'){
// 법정동명이 있을 경우 추가한다. (법정리는 제외)
// 법정동의 경우 마지막 문자가 "동/로/가"로 끝난다.
if(data.bname !== '' && /[동|로|가]$/g.test(data.bname)){
extraAddr += data.bname;
}
// 건물명이 있고, 공동주택일 경우 추가한다.
if(data.buildingName !== '' && data.apartment === 'Y'){
extraAddr += (extraAddr !== '' ? ', ' + data.buildingName : data.buildingName);
}
// 표시할 참고항목이 있을 경우, 괄호까지 추가한 최종 문자열을 만든다.
if(extraAddr !== ''){
extraAddr = ' (' + extraAddr + ')';
}
//(주의)address1에 참고항목이 보여지도록 수정
// 조합된 참고항목을 해당 필드에 넣는다.
//(수정) document.getElementById("address2").value = extraAddr;
}
//(수정) else {
//(수정) document.getElementById("address2").value = '';
//(수정) }
// 우편번호와 주소 정보를 해당 필드에 넣는다.
document.getElementById('zipcode').value = data.zonecode;
//(수정) + extraAddr를 추가해서 address1에 참고항목이 보여지도록 수정
document.getElementById("address1").value = addr + extraAddr;
// 커서를 상세주소 필드로 이동한다.
document.getElementById("address2").focus();
// iframe을 넣은 element를 안보이게 한다.
// (autoClose:false 기능을 이용한다면, 아래 코드를 제거해야 화면에서 사라지지 않는다.)
element_layer.style.display = 'none';
},
width : '100%',
height : '100%',
maxSuggestItems : 5
}).embed(element_layer);
// iframe을 넣은 element를 보이게 한다.
element_layer.style.display = 'block';
// iframe을 넣은 element의 위치를 화면의 가운데로 이동시킨다.
initLayerPosition();
}
// 브라우저의 크기 변경에 따라 레이어를 가운데로 이동시키고자 하실때에는
// resize이벤트나, orientationchange이벤트를 이용하여 값이 변경될때마다 아래 함수를 실행 시켜 주시거나,
// 직접 element_layer의 top,left값을 수정해 주시면 됩니다.
function initLayerPosition(){
var width = 300; //우편번호서비스가 들어갈 element의 width
var height = 400; //우편번호서비스가 들어갈 element의 height
var borderWidth = 5; //샘플에서 사용하는 border의 두께
// 위에서 선언한 값들을 실제 element에 넣는다.
element_layer.style.width = width + 'px';
element_layer.style.height = height + 'px';
element_layer.style.border = borderWidth + 'px solid';
// 실행되는 순간의 화면 너비와 높이 값을 가져와서 중앙에 뜰 수 있도록 위치를 계산한다.
element_layer.style.left = (((window.innerWidth || document.documentElement.clientWidth) - width)/2 - borderWidth) + 'px';
element_layer.style.top = (((window.innerHeight || document.documentElement.clientHeight) - height)/2 - borderWidth) + 'px';
}
</script>
<!-- 우편번호 스크립트 끝 -->
</div>
<!-- 중앙 컨텐츠 끝 -->
member
폴더에 새 JSP 파일 memberLogin.jsp
생성<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<!-- 중앙 컨텐츠 시작 -->
<div class="page-main">
<h2>회원 로그인</h2>
<form:form modelAttribute="memberVO" action="login.do" id="login_form">
<form:errors element="div" cssClass="error-color"/>
<ul>
<li>
<form:label path="id">아이디</form:label>
<form:input path="id"/>
<form:errors path="id" cssClass="error-color"/>
</li>
<li>
<form:label path="passwd">비밀번호</form:label>
<form:password path="passwd"/>
<form:errors path="passwd" cssClass="error-color"/>
</li>
</ul>
<div class="align-center">
<form:button>로그인</form:button>
<input type="button" value="홈으로" onclick="location.href = '${pageContext.request.contextPath}/main/main.do';">
</div>
</form:form>
</div>
<!-- 중앙 컨텐츠 끝 -->
member
폴더에 새 JSP 파일 memberView.jsp
생성<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!-- 중앙 컨텐츠 시작 -->
<script type="text/javascript" src="${pageContext.request.contextPath}/resources/js/jquery-3.6.0.min.js"></script>
<script type="text/javascript">
$(function() {
// 프로필 사진 업로드
$('#photo_btn').click(function() {
$('#photo_choice').show();
$(this).hide();
}); // end of click
// 처음 화면에 보여지는 이미지 읽기
let photo_path = $('.my-photo').attr('src');
let my_photo;
$('#upload').change(function() {
my_photo = this.files[0];
if(!my_photo) { // 아무것도 선택하지 않은 경우
$('.my-photo').attr('src', photo_path); // 미리보기 초기화
return;
}
if(my_photo.size>1024 * 1024) { // 선택한 파일이 지정한 크기를 초과한 경우
alert(Math.round(my_photo.size/1024) + 'kbytes (1024kbytes까지만 업로드 가능)');
$('.my-photo').attr('src', photo_path); // 미리보기 초기화
$(this).val(''); // 선택한 파일 초기화
return;
}
// 이미지 미리보기
var reader = new FileReader();
reader.readAsDataURL(my_photo);
reader.onload = function() {
$('.my-photo').attr('src', reader.result);
};
}); // end of change
// 이미지를 서버에 전송
$('#photo_submit').click(function() {
if($('#upload').val()=='') {
alert('파일을 선택하세요!');
$('#upload').focus();
return;
}
// 파일 전송
var form_data = new FormData();
form_data.append('upload', my_photo); // upload는 자바빈에서 MultipartFile 자료형의 프로퍼티명
$.ajax({
url:'updateMyPhoto.do',
type:'post',
data:form_data,
dataType:'json',
contentType:false,
enctype:'multipart/form-data',
processData:false,
success:function(param) {
if(param.result=='logout') {
alert('로그인 후 사용하세요!');
}
else if(param.result=='success') {
alert('프로필 사진이 수정되었습니다.');
photo_path = $('.my-photo').attr('src'); // 미리보기 갱신
$('#upload').val(''); // 선택한 파일 초기화
$('#photo_choice').hide();
$('#photo_btn').show();
}
else {
alert('파일 전송 오류 발생!');
}
},
error:function() {
alert('네트워크 오류 발생!');
}
}); // end of ajax
}); // end of click
$('#photo_reset').click(function() {
$('.my-photo').attr('src', photo_path); // 미리보기 초기화
$('#upload').val(''); // 선택한 파일 초기화
$('#photo_choice').hide();
$('#photo_btn').show();
}); // end of click
});
</script>
<div class="page-main">
<h2>프로필 사진</h2>
<ul>
<li>
<c:if test="${empty member.photo_name}">
<img src="${pageContext.request.contextPath}/resources/images/face.png" width="200" height="200" class="my-photo">
</c:if>
<c:if test="${!empty member.photo_name}">
<img src="${pageContext.request.contextPath}/member/photoView.do" width="200" height="200" class="my-photo">
</c:if>
</li>
<li>
<div class="align-center">
<input type="button" value="수정" id="photo_btn">
</div>
<div id="photo_choice" style="display: none;">
<input type="file" id="upload" accept="image/jpeg, image/png, image/gif">
<input type="button" value="전송" id="photo_submit">
<input type="button" value="취소" id="photo_reset">
</div>
</li>
</ul>
<h2>회원 상세 정보</h2>
<ul>
<li>이름 : ${member.name}</li>
<li>전화번호 : ${member.phone}</li>
<li>이메일 : ${member.email}</li>
<li>우편번호 : ${member.zipcode}</li>
<li>주소 : ${member.address1}</li>
<li>상세 주소 : ${member.address2}</li>
<li>가입일 : ${member.reg_date}</li>
<c:if test="${!empty member.modify_date}">
<li>최근 정보 수정일 : ${member.modify_date}</li>
</c:if>
</ul>
<hr size="1" width="100%" noshade>
<p class="align-right">
<input type="button" value="회원 정보 수정" onclick="location.href = 'update.do';">
<input type="button" value="비밀번호 변경" onclick="location.href = 'changePassword.do';">
<input type="button" value="회원 탈퇴" onclick="location.href = 'delete.do';">
</p>
</div>
<!-- 중앙 컨텐츠 끝 -->
member
폴더에 새 JSP 파일 memberModify.jsp
생성<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<!-- 중앙 컨텐츠 시작 -->
<div class="page-main">
<h2>회원 정보 수정</h2>
<form:form modelAttribute="memberVO" action="update.do" id="modify_form">
<form:errors element="div" cssClass="error-color"/>
<ul>
<li>
<form:label path="name">이름</form:label>
<form:input path="name"/>
<form:errors path="name" cssClass="error-color"/>
</li>
<li>
<form:label path="phone">주소</form:label>
<form:input path="phone"/>
<form:errors path="phone" cssClass="error-color"/>
</li>
<li>
<form:label path="email">이메일</form:label>
<form:input path="email"/>
<form:errors path="email" cssClass="error-color"/>
</li>
<li>
<form:label path="zipcode">우편번호</form:label>
<form:input path="zipcode"/>
<input type="button" onclick="sample2_execDaumPostcode()" value="우편번호 찾기">
<form:errors path="zipcode" cssClass="error-color"/>
</li>
<li>
<form:label path="address1">주소</form:label>
<form:input path="address1"/>
<form:errors path="address1" cssClass="error-color"/>
</li>
<li>
<form:label path="address2">상세 주소</form:label>
<form:input path="address2"/>
<form:errors path="address2" cssClass="error-color"/>
</li>
</ul>
<div class="align-center">
<form:button>수정</form:button>
<input type="button" value="홈으로" onclick="location.href = '${pageContext.request.contextPath}/main/main.do';">
</div>
</form:form>
<!-- 우편번호 스크립트 시작 -->
<!-- iOS에서는 position:fixed 버그가 있음, 적용하는 사이트에 맞게 position:absolute 등을 이용하여 top,left값 조정 필요 -->
<div id="layer" style="display:none;position:fixed;overflow:hidden;z-index:1;-webkit-overflow-scrolling:touch;">
<img src="//t1.daumcdn.net/postcode/resource/images/close.png" id="btnCloseLayer" style="cursor:pointer;position:absolute;right:-3px;top:-3px;z-index:1" onclick="closeDaumPostcode()" alt="닫기 버튼">
</div>
<script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
<script>
// 우편번호 찾기 화면을 넣을 element
var element_layer = document.getElementById('layer');
function closeDaumPostcode() {
// iframe을 넣은 element를 안보이게 한다.
element_layer.style.display = 'none';
}
function sample2_execDaumPostcode() {
new daum.Postcode({
oncomplete: function(data) {
// 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분.
// 각 주소의 노출 규칙에 따라 주소를 조합한다.
// 내려오는 변수가 값이 없는 경우엔 공백('')값을 가지므로, 이를 참고하여 분기 한다.
var addr = ''; // 주소 변수
var extraAddr = ''; // 참고항목 변수
//사용자가 선택한 주소 타입에 따라 해당 주소 값을 가져온다.
if (data.userSelectedType === 'R') { // 사용자가 도로명 주소를 선택했을 경우
addr = data.roadAddress;
} else { // 사용자가 지번 주소를 선택했을 경우(J)
addr = data.jibunAddress;
}
// 사용자가 선택한 주소가 도로명 타입일때 참고항목을 조합한다.
if(data.userSelectedType === 'R'){
// 법정동명이 있을 경우 추가한다. (법정리는 제외)
// 법정동의 경우 마지막 문자가 "동/로/가"로 끝난다.
if(data.bname !== '' && /[동|로|가]$/g.test(data.bname)){
extraAddr += data.bname;
}
// 건물명이 있고, 공동주택일 경우 추가한다.
if(data.buildingName !== '' && data.apartment === 'Y'){
extraAddr += (extraAddr !== '' ? ', ' + data.buildingName : data.buildingName);
}
// 표시할 참고항목이 있을 경우, 괄호까지 추가한 최종 문자열을 만든다.
if(extraAddr !== ''){
extraAddr = ' (' + extraAddr + ')';
}
//(주의)address1에 참고항목이 보여지도록 수정
// 조합된 참고항목을 해당 필드에 넣는다.
//(수정) document.getElementById("address2").value = extraAddr;
}
//(수정) else {
//(수정) document.getElementById("address2").value = '';
//(수정) }
// 우편번호와 주소 정보를 해당 필드에 넣는다.
document.getElementById('zipcode').value = data.zonecode;
//(수정) + extraAddr를 추가해서 address1에 참고항목이 보여지도록 수정
document.getElementById("address1").value = addr + extraAddr;
// 커서를 상세주소 필드로 이동한다.
document.getElementById("address2").focus();
// iframe을 넣은 element를 안보이게 한다.
// (autoClose:false 기능을 이용한다면, 아래 코드를 제거해야 화면에서 사라지지 않는다.)
element_layer.style.display = 'none';
},
width : '100%',
height : '100%',
maxSuggestItems : 5
}).embed(element_layer);
// iframe을 넣은 element를 보이게 한다.
element_layer.style.display = 'block';
// iframe을 넣은 element의 위치를 화면의 가운데로 이동시킨다.
initLayerPosition();
}
// 브라우저의 크기 변경에 따라 레이어를 가운데로 이동시키고자 하실때에는
// resize이벤트나, orientationchange이벤트를 이용하여 값이 변경될때마다 아래 함수를 실행 시켜 주시거나,
// 직접 element_layer의 top,left값을 수정해 주시면 됩니다.
function initLayerPosition(){
var width = 300; //우편번호서비스가 들어갈 element의 width
var height = 400; //우편번호서비스가 들어갈 element의 height
var borderWidth = 5; //샘플에서 사용하는 border의 두께
// 위에서 선언한 값들을 실제 element에 넣는다.
element_layer.style.width = width + 'px';
element_layer.style.height = height + 'px';
element_layer.style.border = borderWidth + 'px solid';
// 실행되는 순간의 화면 너비와 높이 값을 가져와서 중앙에 뜰 수 있도록 위치를 계산한다.
element_layer.style.left = (((window.innerWidth || document.documentElement.clientWidth) - width)/2 - borderWidth) + 'px';
element_layer.style.top = (((window.innerHeight || document.documentElement.clientHeight) - height)/2 - borderWidth) + 'px';
}
</script>
<!-- 우편번호 스크립트 끝 -->
</div>
<!-- 중앙 컨텐츠 끝 -->
kr.spring.member.controller
생성하고 새 클래스 MemberController
생성package kr.spring.member.controller;
import javax.servlet.http.HttpSession;
import javax.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import kr.spring.member.service.MemberService;
import kr.spring.member.vo.MemberVO;
import kr.spring.util.AuthCheckException;
@Controller
public class MemberController {
private static final Logger logger = LoggerFactory.getLogger(MemberController.class);
@Autowired
private MemberService memberService;
// 자바빈 초기화
@ModelAttribute
private MemberVO initCommand() {
return new MemberVO();
}
// 회원 가입 폼 호출
@GetMapping("/member/registerUser.do")
public String form() {
// Tiles 설정 반환
return "memberRegister";
}
// 회원 가입 처리
@PostMapping("/member/registerUser.do")
public String submit(@Valid MemberVO memberVO, BindingResult result) {
logger.info("<<회원 가입>> : " + memberVO);
// 유효성 검증 결과 오류가 있으면 폼 호출
if(result.hasErrors()) {
return form();
}
// 회원 가입
memberService.insertMember(memberVO);
return "redirect:/main/main.do";
}
// 로그인 폼
@GetMapping("/member/login.do")
public String formLogin() {
// Tiles 설정 반환
return "memberLogin";
}
// 로그인 처리
@PostMapping("/member/login.do")
public String submitLogin(@Valid MemberVO memberVO, BindingResult result, HttpSession session) {
logger.info("<<회원 로그인>> : " + memberVO);
// 유효성 검증 결과 오류가 있으면 폼 호출
// id와 passwd 필드만 검증
if(result.hasFieldErrors("id") || result.hasFieldErrors("passwd")) {
return formLogin();
}
// 아이디와 비밀번호 일치 여부 검증
try {
MemberVO member = memberService.selectCheckMember(memberVO.getId());
boolean check = false;
if(member!=null) { // 존재하는 아이디면
// 비밀번호 일치 여부 검증
check = member.isCheckedPassword(memberVO.getPasswd());
}
if(check) { // 인증 성공시
// 로그인 처리
session.setAttribute("user_num", member.getMem_num());
session.setAttribute("user_id", member.getId());
session.setAttribute("user_auth", member.getAuth());
session.setAttribute("user_photo", member.getPhoto());
return "redirect:/main/main.do";
}
// 인증 실패시
throw new AuthCheckException();
}
catch(AuthCheckException e) {
// 폼에서 보여질 메시지 생성
result.reject("invalidIdOrPassword");
// 폼 호출
return formLogin();
}
}
// 로그아웃
@RequestMapping("/member/logout.do")
public String processLogout(HttpSession session) {
// 로그아웃 처리
session.invalidate();
return "redirect:/main/main.do";
}
// MY 페이지
@RequestMapping("/member/myPage.do")
public String process(HttpSession session, Model model) {
Integer user_num = (Integer)session.getAttribute("user_num");
MemberVO member = memberService.selectMember(user_num);
logger.info("<<회원 상세 정보>> : " + member);
model.addAttribute("member", member);
// Tiles 설정 반환
return "memberView";
}
// 회원 정보 수정 폼
@GetMapping("/member/update.do")
public String formUpdate(HttpSession session, Model model) {
Integer user_num = (Integer)session.getAttribute("user_num");
MemberVO memberVO = memberService.selectMember(user_num);
model.addAttribute("memberVO", memberVO);
// Tiles 설정 반환
return "memberModify";
}
// 회원 정보 수정 처리
@PostMapping("/member/update.do")
public String submitUpdate(@Valid MemberVO memberVO, BindingResult result, HttpSession session) {
logger.info("<<회원 정보 수정>> : " + memberVO);
// 유효성 검증 결과 오류가 있으면 폼 호출
if(result.hasErrors()) {
return "memberModify";
}
Integer user_num = (Integer)session.getAttribute("user_num");
memberVO.setMem_num(user_num);
// 회원 정보 수정
memberService.updateMember(memberVO);
return "redirect:/member/myPage.do";
}
}
MemberAjaxController
생성package kr.spring.member.controller;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import kr.spring.member.service.MemberService;
import kr.spring.member.vo.MemberVO;
@Controller
public class MemberAjaxController {
private static final Logger logger = LoggerFactory.getLogger(MemberAjaxController.class);
@Autowired
private MemberService memberService;
@RequestMapping("/member/confirmId.do")
@ResponseBody
public Map<String, String> process(@RequestParam String id) {
logger.info("<<id>> : " + id);
Map<String, String> map = new HashMap<String, String>();
MemberVO member = memberService.selectCheckMember(id);
if(member!=null) { // 아이디가 중복된 경우
map.put("result", "idDuplicated");
}
else { // 아이디가 중복되지 않은 경우
if(!Pattern.matches("^[A-Za-z0-9]{4,12}$", id)) { // 패턴 불일치
map.put("result", "notMatchPattern");
}
else { // 패턴 일치
map.put("result", "idNotFound");
}
}
return map;
}
}
src/main/java
폴더 오른쪽 클릭하여 새 패키지 kr.spring.member.vo
생성하고 새 클래스 MemberVO
생성package kr.spring.member.vo;
import java.io.IOException;
import java.sql.Date;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import org.springframework.web.multipart.MultipartFile;
public class MemberVO {
private int mem_num;
@Pattern(regexp="^[A-Za-z0-9]{4,12}$") // 영문 대소문자 및 숫자만 사용 가능, 최소 4자 최대 12자
private String id;
private int auth;
@NotEmpty
private String name;
@Pattern(regexp="^[A-Za-z0-9]{4,12}$") // 영문 대소문자 및 숫자만 사용 가능, 최소 4자 최대 12자
private String passwd;
@NotEmpty
private String phone;
@Email @NotEmpty
private String email;
@Size(min=5, max=5)
private String zipcode;
@NotEmpty
private String address1;
@NotEmpty
private String address2;
private MultipartFile upload;
private byte[] photo;
private String photo_name;
private Date reg_date;
private Date modify_date;
// 비밀번호 변경시 현재 비밀번호를 저장하는 용도로 사용
@Pattern(regexp="^[A-Za-z0-9]{4,12}$") // 영문 대소문자 및 숫자만 사용 가능, 최소 4자 최대 12자
private String now_passwd;
// 비밀번호 일치 여부 검증
public boolean isCheckedPassword(String userPasswd) {
if(auth>1 && passwd.equals(userPasswd)) {
return true;
}
return false;
}
// 이미지 BLOB 처리
public void setUpload(MultipartFile upload) throws IOException {
this.upload = upload;
// MultipartFile을 byte[]로 변환
setPhoto(upload.getBytes());
// 파일 이름 저장
setPhoto_name(upload.getOriginalFilename());
}
public int getMem_num() {
return mem_num;
}
public void setMem_num(int mem_num) {
this.mem_num = mem_num;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public int getAuth() {
return auth;
}
public void setAuth(int auth) {
this.auth = auth;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getZipcode() {
return zipcode;
}
public void setZipcode(String zipcode) {
this.zipcode = zipcode;
}
public String getAddress1() {
return address1;
}
public void setAddress1(String address1) {
this.address1 = address1;
}
public String getAddress2() {
return address2;
}
public void setAddress2(String address2) {
this.address2 = address2;
}
public MultipartFile getUpload() {
return upload;
}
public byte[] getPhoto() {
return photo;
}
public void setPhoto(byte[] photo) {
this.photo = photo;
}
public String getPhoto_name() {
return photo_name;
}
public void setPhoto_name(String photo_name) {
this.photo_name = photo_name;
}
public Date getReg_date() {
return reg_date;
}
public void setReg_date(Date reg_date) {
this.reg_date = reg_date;
}
public Date getModify_date() {
return modify_date;
}
public void setModify_date(Date modify_date) {
this.modify_date = modify_date;
}
// 비밀번호 변경시 현재 비밀번호를 저장하는 용도로 사용
public String getNow_passwd() {
return now_passwd;
}
public void setNow_passwd(String now_passwd) {
this.now_passwd = now_passwd;
}
// byte[] 자료형의 데이터를 출력시 양이 많아 프로그램이 느려지므로 photo를 제외한 다른 필드들만 출력하도록 toString() 재정의
@Override
public String toString() {
return "MemberVO [mem_num=" + mem_num + ", id=" + id + ", auth=" + auth + ", name=" + name + ", passwd="
+ passwd + ", phone=" + phone + ", email=" + email + ", zipcode=" + zipcode + ", address1=" + address1
+ ", address2=" + address2 + ", upload=" + upload + ", photo_name=" + photo_name + ", reg_date="
+ reg_date + ", modify_date=" + modify_date + ", now_passwd=" + now_passwd + "]";
}
}
src/main/java
폴더 오른쪽 클릭하여 새 패키지 kr.spring.member.dao
생성하고 새 인터페이스 MemberMapper
생성package kr.spring.member.dao;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import kr.spring.member.vo.MemberVO;
public interface MemberMapper {
// 사용자
@Select("SELECT spmember_seq.NEXTVAL FROM dual")
public int selectMem_num();
@Insert("INSERT INTO spmember (mem_num, id) VALUES (#{mem_num}, #{id})")
public void insertMember(MemberVO member);
@Insert("INSERT INTO spmember_detail (mem_num, name, passwd, phone, email, zipcode, address1, address2) VALUES (#{mem_num}, #{name}, #{passwd}, #{phone}, #{email}, #{zipcode}, #{address1}, #{address2})")
public void insertMember_detail(MemberVO member);
@Select("SELECT mem_num, id, auth, passwd, photo FROM spmember LEFT JOIN spmember_detail USING (mem_num) WHERE id=#{id}")
public MemberVO selectCheckMember(String id);
@Select("SELECT * FROM spmember LEFT JOIN spmember_detail USING (mem_num) WHERE mem_num=#{mem_num}")
public MemberVO selectMember(Integer mem_num);
@Update("UPDATE spmember_detail SET name=#{name}, phone=#{phone}, email=#{email}, zipcode=#{zipcode}, address1=#{address1}, address2=#{address2}, modify_date=SYSDATE WHERE mem_num=#{mem_num}")
public void updateMember(MemberVO member);
@Update("UPDATE spmember_detail SET passwd=#{passwd} WHERE mem_num=#{mem_num}")
public void updatePassword(MemberVO member);
@Update("UPDATE spmember SET auth=0 WHERE mem_num=#{mem_num}")
public void deleteMember(Integer mem_num);
@Delete("DELETE FROM spmember_detail WHERE mem_num=#{mem_num}")
public void deleteMember_detail(Integer mem_num);
@Update("UPDATE spmember_detail SET photo=#{photo}, photo_name=#{photo_name} WHERE mem_num=#{mem_num}")
public void updateProfile(MemberVO member);
// 관리자
public List<MemberVO> selectList(Map<String, Object> map);
public int selectRowCount(Map<String, Object> map);
@Update("UPDATE spmember SET auth=#{auth} WHERE mem_num=#{mem_num}")
public void updateByAdmin(MemberVO member);
@Update("UPDATE spmember_detail SET name=#{name}, phone=#{phone}, email=#{email}, zipcode=#{zipcode}, address1=#{address1}, address2=#{address2}, modify_date=SYSDATE WHERE mem_num=#{mem_num}")
public void updateDetailByAdmin(MemberVO member);
}
src/main/java
폴더 오른쪽 클릭하여 새 패키지 kr.spring.member.service
생성하고 새 인터페이스 MemberService
생성package kr.spring.member.service;
import java.util.List;
import java.util.Map;
import kr.spring.member.vo.MemberVO;
public interface MemberService {
// 사용자
public void insertMember(MemberVO member);
public MemberVO selectCheckMember(String id);
public MemberVO selectMember(Integer mem_num);
public void updateMember(MemberVO member);
public void updatePassword(MemberVO member);
public void deleteMember(Integer mem_num);
public void updateProfile(MemberVO member);
// 관리자
public List<MemberVO> selectList(Map<String, Object> map);
public int selectRowCount(Map<String, Object> map);
public void updateByAdmin(MemberVO member);
}
MemberServiceImpl
생성package kr.spring.member.service;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import kr.spring.member.dao.MemberMapper;
import kr.spring.member.vo.MemberVO;
@Service
@Transactional
public class MemberServiceImpl implements MemberService {
@Autowired
private MemberMapper memberMapper;
@Override
public void insertMember(MemberVO member) {
member.setMem_num(memberMapper.selectMem_num());
memberMapper.insertMember(member);
memberMapper.insertMember_detail(member);
}
@Override
public MemberVO selectCheckMember(String id) {
return memberMapper.selectCheckMember(id);
}
@Override
public MemberVO selectMember(Integer mem_num) {
return memberMapper.selectMember(mem_num);
}
@Override
public void updateMember(MemberVO member) {
memberMapper.updateMember(member);
}
@Override
public void updatePassword(MemberVO member) {
memberMapper.updatePassword(member);
}
@Override
public void deleteMember(Integer mem_num) {
memberMapper.deleteMember(mem_num);
memberMapper.deleteMember_detail(mem_num);
}
@Override
public void updateProfile(MemberVO member) {
memberMapper.updateProfile(member);
}
@Override
public List<MemberVO> selectList(Map<String, Object> map) {
return memberMapper.selectList(map);
}
@Override
public int selectRowCount(Map<String, Object> map) {
return memberMapper.selectRowCount(map);
}
@Override
public void updateByAdmin(MemberVO member) {
memberMapper.updateByAdmin(member);
memberMapper.updateDetailByAdmin(member);
}
}
kr.spring.interceptor
생성하고 새 클래스 LoginCheckInterceptor
생성package kr.spring.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
public class LoginCheckInterceptor extends HandlerInterceptorAdapter {
private static final Logger logger = LoggerFactory.getLogger(LoginCheckInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
logger.info("<<LoginCheckInterceptor 진입>>");
// 로그인 여부 검사
HttpSession session = request.getSession();
if(session.getAttribute("user_num")==null) { // 로그인되어 있지 않은 경우
response.sendRedirect(request.getContextPath() + "/member/login.do");
return false;
}
return true;
}
}