김은옥씨가 지은 은노기의 JSP2.3 웹프로그래밍에 나오는 shoppingmall 프로그램을 돌려보기 위한 참고사항을 적어본다.
소스코드는 아래에 첨부한다.
이 프로그램은 데이터베이스로 MySQL을 사용하였다. 나는 v8.0.13을 사용하였다.
위 소스코드 중 WebContent/WEB-INF/sql/database.sql 파일을 참조한다.
다음과 같이 데이터베이스 및 사용자를 생성하고 권한을 부여해준다. (root 권한으로 실행한다.)
-- 은노기 shoppingmall 프로젝트
create database jsptest;
create user 'jspid'@'localhost' identified by 'jsppass';
create user 'jspid'@'%' identified by 'jsppass';
grant all privileges on jsptest.* to 'jspid'@'localhost';
grant all privileges on jsptest.* to 'jspid'@'%';
commit;
다음과 같이 테이블과 사용자 데이터를 추가한다.
create table member(
id varchar(50) not null primary key,
passwd varchar(16) not null,
name varchar(10) not null,
reg_date datetime not null
);
alter table member
add (address varchar(100) not null,
tel varchar(20) not null);
desc member;
insert into member(id, passwd, name, reg_date, address, tel)
values('kingdora@dragon.com','1234','김개동', now(), '서울시', '010-1111-1111');
insert into member(id, passwd, name, reg_date, address, tel)
values('hongkd@aaa.com','1111','홍길동', now(), '경기도', '010-2222-2222');
select * from member;
alter table member modify passwd varchar(60) not null;
create table board(
num int not null primary key auto_increment,
writer varchar(50) not null,
subject varchar(50) not null,
content text not null,
passwd varchar(60) not null,
reg_date datetime not null,
ip varchar(30) not null,
readcount int default 0,
ref int not null,
re_step smallint not null,
re_level smallint not null
);
desc board;
--쇼핑몰
create table manager(
managerId varchar(50) not null primary key,
managerPasswd varchar(60) not null
);
insert into manager(managerId,managerPasswd)
values('bookmaster@shop.com','123456');
insert into manager(managerId,managerPasswd)
values('ksseo63@naver.com','ekffksxm0');
create table book(
book_id int not null primary key auto_increment,
book_kind varchar(3) not null,
book_title varchar(100) not null,
book_price int not null,
book_count smallint not null,
author varchar(40) not null,
publishing_com varchar(30) not null,
publishing_date varchar(15) not null,
book_image varchar(16) default 'nothing.jpg',
book_content text not null,
discount_rate tinyint default 10,
reg_date datetime not null
);
create table qna(
qna_id int not null primary key auto_increment,
book_id int not null,
book_title varchar(100) not null,
qna_writer varchar(50) not null,
qna_content text not null,
group_id int not null,
qora tinyint not null,
reply tinyint default 0,
reg_date datetime not null
);
create table bank(
account varchar(30) not null,
bank varchar(10) not null,
name varchar(10) not null
);
insert into bank(account, bank, name)
values('11111-111-11111','내일은행','오내일');
create table cart(
cart_id int not null primary key auto_increment,
buyer varchar(50) not null,
book_id int not null,
book_title varchar(100) not null,
buy_price int not null,
buy_count tinyint not null,
book_image varchar(16) default 'nothing.jpg'
);
create table buy(
buy_id bigint not null,
buyer varchar(50) not null,
book_id varchar(12) not null,
book_title varchar(100) not null,
buy_price int not null,
buy_count tinyint not null,
book_image varchar(16) default 'nothing.jpg',
buy_date datetime not null,
account varchar(50) not null,
deliveryName varchar(10) not null,
deliveryTel varchar(20) not null,
deliveryAddress varchar(100) not null,
sanction varchar(10) default '상품준비중'
);
데이터베이스 연동을 위한 설정은 WebContent/META-INF/context.xml을 참조한다.
또한 MySQL JDBC Connector 파일도 자신이 사용하는 MySQL Server 버전에 맞추어 변경해 주어야 한다. 이것은 studyjsp와 shoppingmall 프로젝트의 WebContent/WEB-INF/lib 폴더 밑에 있는 JDBC Connecctor 라이브러리에 대하여 맞추어 주어야 한다. 나는 v8.0.13을 사용하므로 mysql-connector-java-8.0.13.jar로 변경해 주었다. (기존에는 5.x.x 버전을 사용)
사용자에 대한 암호를 암호화하기 위해서는 9장에 있는 cryptProcess.jsp를 구동해 주어야 한다. (http://localhost:8080/shoppingmall/ch09/cryptProcess.jsp)
이는 studyjsp 프로젝트 폴더에 있는 소스코드를 shoppingmall 프로젝트 폴더로 복사하여 사용하였다.
자바스크립트에서 회원가입 데이터에 대한 유효성 검사를 수행하고, 아이디 중복 확인 및 회원가입 요청을 서버로 전송한다.
$(document).ready(function() {
var idCheck = false; // 아이디 중복 검사 통과 여부
var passwordValid = false; // 비밀번호 유효성 검사 결과
var password2Valid = false; // 비밀번호 확인 유효성 검사 결과
var emailValid = false; // 이메일 유효성 검사 결과
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
var formObj = $(".form-join");
// 이메일 확인 유효성 검사
$("input[name='email']").on("keyup", function(e) {
var regExp = /^[-A-Za-z0-9_]+[-A-Za-z0-9_.]*[@]{1}[-A-Za-z0-9_]+[-A-Za-z0-9_.]*[.]{1}[A-Za-z]{2,5}$/;
if( !regExp.test($("input[name='email']").val()) ) {
$("input[name='email']").css("color", "#EE5656");
emailValid = false;
}
else {
$("input[name='email']").css("color", "blue");
emailValid = true;
}
});
// 비밀번호 유효성 검사
$("input[name='password']").on("keyup", function(e) {
var regExp = /^(?=.*[a-zA-Z])(?=.*[!@#$%^*+=-])(?=.*[0-9]).{5,15}$/;
if( !regExp.test($("input[name='password']").val()) ) {
$("input[name='password']").css("color", "#EE5656");
passwordValid = false;
}
else {
$("input[name='password']").css("color", "blue");
passwordValid = true;
}
});
// 비밀번호 확인 유효성 검사
$("input[name='password2']").on("keyup", function(e) {
var regExp = /^(?=.*[a-zA-Z])(?=.*[!@#$%^*+=-])(?=.*[0-9]).{5,15}$/;
if( !regExp.test($("input[name='password2']").val()) ) {
$("input[name='password2']").css("color", "#EE5656");
password2Valid = false;
}
else {
$("input[name='password2']").css("color", "blue");
password2Valid = true;
}
});
// 아이디 중복 확인
$(".btn-secondary").on("click", function(e) {
var email = $("input[name='email']").val();
if(email == null || email.length == 0) {
alert("아이디(이메일 형식)를 입력하세요");
return;
}
if(emailValid == false) {
alert("고유한 이메일 형식의 아이디를 사용하세요.");
return;
}
// 서버로 중복 확인 요청
$.ajax({
url: '/member/idCheck',
data: {email: email},
type: 'POST',
beforeSend: function(xhr) {
xhr.setRequestHeader(header, token);
},
success: function(result){
if(result == "ok") {
idCheck = true;
alert("사용할 수 있는 아이디 입니다.");
} else {
idCheck = false;
alert("사용할 수 없는 아이디 입니다.");
}
}
}); //$.ajax
});
$(".btn-success").on("click", function(e) {
e.preventDefault();
var email = $("input[name='email']").val();
if(email == null || email.length == 0) {
alert("아이디를 입력하세요");
email.focus();
return;
}
if(idCheck == false) {
alert("아이디 중복확인을 하세요");
return;
}
if(emailValid == false) {
alert("고유한 이메일 형식의 아이디를 사용하세요.");
return;
}
var password = $("input[name='password']").val();
if(password == null || password.length == 0) {
alert("암호를 입력하세요");
password.focus();
return;
}
if(passwordValid == false) {
alert("특수문자, 영문, 숫자의 조합으로 8자 이상 15자 이하를 사용하세요.");
return;
}
var password2 = $("input[name='password2']").val();
if(password2 == null || password2.length == 0) {
alert("암호 확인을 입력하세요");
password2.focus();
return;
}
if(password2Valid == false) {
alert("특수문자, 영문, 숫자의 조합으로 8자 이상 15자 이하를 사용하세요.");
return;
}
if(password != password2) {
alert("암호 확인을 입력하세요");
password2.focus();
return;
}
var name = $("input[name='name']").val();
if(name == null || name.length == 0) {
alert("이름을 입력하세요");
name.focus();
return;
}
console.log("submit clicked");
formObj.submit();
});
});
이메일 형식의 아이디를 입력하고 중복 확인 버튼을 클릭하면 자바스크립트 join.js에서 아이디를 입력 유무에 대한 유효성 검사를 수행한 후 Ajax를 사용하여 아이디 중복 확인(/member/idCheck)을 서버로 요청한다. 이때 아이디인 email을 파라미터로 전송한다. 또한 CSRF를 header에 전송하여야 한다. 아래 그림을 보면 X-CSRF-TOKEN 헤더에 CSRF 토큰이 전송됨을 확인할 수 있다.
MemberController에서는 /member/idCheck 요청에 대하여 처리를 한다.
MemberServiceImpl.java는 구현 객체로 다음과 같다. 데이터베이스 연동을 위해 MemberRepository를 주입받는다. MemberController로부터 idCheck() 요청을 받으면 MemberRepository로 email을 가진 사용자가 있는지 질의하여 아이디를 가진 사용자가 있는지 알 수 있다.
package com.example.sboot.member.service;
import java.util.Optional;
import javax.transaction.Transactional;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import com.example.sboot.member.dto.MemberDTO;
import com.example.sboot.member.entity.Member;
import com.example.sboot.member.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
@Service
@RequiredArgsConstructor
@Log4j2
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository;
private final PasswordEncoder pwencoder;
@Override
public boolean idCheck(String email) {
log.info("idcheck..." + email);
Optional<Member> member = memberRepository.findById(email);
return member.isEmpty();
}
@Transactional
@Override
public void join(MemberDTO memberDTO) {
String encPw = pwencoder.encode(memberDTO.getPassword());
memberDTO.setPassword(encPw);
Member member = dtoToEntity(memberDTO);
memberRepository.save(member);
}
}
회원정보를 입력하고 Join 버튼을 클릭하면, 자바스크립트 join.js에서 회원정보에 대한 유효성 검사를 수행한 후 이상이 없으면 /member/join POST 요청을 서버로 보낸다.
브라우저에서 개발자 도구에서 제공하는 network 탭을 이용하면 서버로 전송되는 데이터를 확인할 수 있다.
MemberController에서는 /member/join POST 요청을 받아 처리한다. MemberService로 회원정보의 저장을 요청하고, 로그인 창으로 리다이렉트를 수행하며, 로그인 절차에서 회원의 아이디를 한번만 활용할 수 있도록 RedirectAttributes로 전달한다.
이전에 구현한 것이지만 Toggle Menu 버튼을 누르면 왼쪽의 sidebar 메뉴가 토글되는 것을 역시 확인할 수 있다. 이 부분은 basic.html의 하단부에 있는 자바스크립트에서 메뉴에 대하여 이벤트를 등록하고 이벤트가 발생하면 sidebar 메뉴를 토글시켜줌으로써 동작시킬 수 있다.
주님이 나에게 맡기신 달란트(헬라어로는 Talanton)를 남기고자 합니다. 주님에게 칭찬받는 종이 되기 위하여 주님이 주신 달란트를 땅에 묻어 두지 아니하고, 2배, 5배, 아니 10배를 남기고자 합니다. 나는 부족하지만, 주님께서 함께 하신다면 가능합니다. 하루를 기도로