Luver Duck 2022. 9. 14. 01:01

회원 관련 기능 

- 회원 가입 : day29 - 0902

- 회원 목록 : day32 - 0907

- 회원 상세 정보 : day32 - 0907

- 회원 정보 수정 (관리자 기능) : day 33 - 0908

- 회원 탈퇴 (관리자 기능) : day 33 - 0908

- 로그인/로그아웃 : day34 - 0913

- 마이페이지 : day34 - 0913

 

** 회원 Interceptor 설정 : day34 - 0913

** 관리자 Interceptor 설정 : day34 - 0913

 

준비

- MEMBER 테이블 생성

 - 회원 아이디 : member_id, 5 ~ 20자 영문 소문자, 숫자와 특수기호(_), (-)만 사용 가능
 - 회원 비밀번호 : member_pw, 8~16자 영문 대 소문자, 숫자, 특수문자(!, @, #, $)를 각각 1개 이상 사용
 - 닉네임 : member_nick, 한글로 시작하며 한글 or 숫자 가능, 총 10자 이내
 - 생년월일 : member_birth, YYYY-MM-DD, 날짜 형식
 - 전화번호 : member_tel, 대시 제외하고 010XXXXXXXX 형태
 - 이메일 : member_email, 100byte 이내, @ 반드시 포함
 - 주소
    - 우편 주소 : member_post, 5 ~ 6자리
    - 기본 주소 : member_base_address, 한글 50자
    - 상세 주소 : member_detail_address, 한글 50자
 - 포인트 : member_point, 0 이상 설정
 - 등급 : member_grade, 일반, VIP, 관리자 중 하나
 - 가입일시 : member_join, 날짜이며 현재 시간으로 자동 설정
 - 접속일시 : member_login, 로그인 할 때의 시간으로 자동 설정
 ** 회원 아이디가 기본키
 ** 전화번호, 이메일, 주소는 선택 입력 가능
 ** 접속일시는 가입시 설정하지 않음

 

-- 테이블 생성
create table member(
member_id varchar2(20) primary key
check(regexp_like(member_id, '^[a-z0-9-_]{5,20}$')),
member_pw varchar2(16) not null check(
					regexp_like(member_pw, '^[a-zA-Z0-9!@#$]{8,16}$')
					and
					regexp_like(member_pw, '[a-z]')
					and
					regexp_like(member_pw, '[A-Z]')
					and
					regexp_like(member_pw, '[0-9]')
					and
					regexp_like(member_pw, '[!@#$]')
					),
member_nick varchar2(30) not null unique check(regexp_like(member_nick, '^[가-힣][가-힣0-9]{0,9}$')),
member_birth date not null,
member_tel char(11) check(regexp_like(member_tel, '^010[0-9]{8}$')),
member_email varchar2(100) check(regexp_like(member_email, '@')),
member_post varchar2(6) check(regexp_like(member_post, '^[0-9]{5,6}$')),
member_base_address varchar2(150),
member_detail_address varchar2(150),
member_point number default 0 not null check(member_point >= 0),
member_grade varchar2(9) default '일반' not null check(member_grade in ('일반', 'VIP', '관리자')),
member_join date default sysdate not null,
member_login date
);

 

 

- Model이 될 클래스 정의 (DTO 또는 VO)

package com.kh.springhome.entity;

import java.sql.Date;
 
public class MemberDto {

	// 필드
	private String memberId;
	private String memberPw;
	private String memberNick;
	private Date memberBirth;
	private String memberTel;
	private String memberEmail;
	private String memberPost;
	private String memberBaseAddress;
	private String memberDetailAddress;
	private int memberPoint;
	private String memberGrade;
	private Date memberJoin;
	private Date memberLogin;
	
	// 생성자
	public MemberDto() {
		super();
	}
	
	// getter & setter
	public String getMemberId() {
		return memberId;
	}
	public void setMemberId(String memberId) {
		this.memberId = memberId;
	}
	public String getMemberPw() {
		return memberPw;
	}
	public void setMemberPw(String memberPw) {
		this.memberPw = memberPw;
	}
	public String getMemberNick() {
		return memberNick;
	}
	public void setMemberNick(String memberNick) {
		this.memberNick = memberNick;
	}
	public Date getMemberBirth() {
		return memberBirth;
	}
	public void setMemberBirth(Date memberBirth) {
		this.memberBirth = memberBirth;
	}
	public String getMemberTel() {
		return memberTel;
	}
	public void setMemberTel(String memberTel) {
		this.memberTel = memberTel;
	}
	public String getMemberEmail() {
		return memberEmail;
	}
	public void setMemberEmail(String memberEmail) {
		this.memberEmail = memberEmail;
	}
	public String getMemberPost() {
		return memberPost;
	}
	public void setMemberPost(String memberPost) {
		this.memberPost = memberPost;
	}
	public String getMemberBaseAddress() {
		return memberBaseAddress;
	}
	public void setMemberBaseAddress(String memberBaseAddress) {
		this.memberBaseAddress = memberBaseAddress;
	}
	public String getMemberDetailAddress() {
		return memberDetailAddress;
	}
	public void setMemberDetailAddress(String memberDetailAddress) {
		this.memberDetailAddress = memberDetailAddress;
	}
	public int getMemberPoint() {
		return memberPoint;
	}
	public void setMemberPoint(int memberPoint) {
		this.memberPoint = memberPoint;
	}
	public String getMemberGrade() {
		return memberGrade;
	}
	public void setMemberGrade(String memberGrade) {
		this.memberGrade = memberGrade;
	}
	public Date getMemberJoin() {
		return memberJoin;
	}
	public void setMemberJoin(Date memberJoin) {
		this.memberJoin = memberJoin;
	}
	public Date getMemberLogin() {
		return memberLogin;
	}
	public void setMemberLogin(Date memberLogin) {
		this.memberLogin = memberLogin;
	}

	// toString 오버라이딩 - memberPw(비밀번호)는 보안을 위해 제외할 것
	@Override
	public String toString() {
		return "MemberDto [memberId=" + memberId 
 					+ ", memberNick=" + memberNick
 					+ ", memberBirth=" + memberBirth 
 					+ ", memberTel=" + memberTel 
 					+ ", memberEmail=" + memberEmail
 					+ ", memberPost=" + memberPost 
 					+ ", memberBaseAddress=" + memberBaseAddress 
 					+ ", memberDetailAddress=" + memberDetailAddress 
 					+ ", memberPoint=" + memberPoint 
 					+ ", memberGrade=" + memberGrade
 					+ ", memberJoin=" + memberJoin 
 					+ ", memberLogin=" + memberLogin + "]";
	}
}

 

회원 비밀번호 변경 (회원 기능)

- View에서 기존 비밀번호(pwNow) 뿐만 아니라 새로운 비밀번호(pwNew), 비밀번호 확인(pwNewCheck)를 입력받는다 

- Controller에서 해당 값들을 받을 수 있도록 DTO를 생성한다 (PasswordDto)

- 가장 먼저 새로운 비밀번호(pwNew)와 비밀번호 확인(pwNewCheck)의 일치 여부를 검사한다

- 일치한다면 기존 비밀번호와 새로운 비밀번호의 일치 여부를 검사해야 한다

- HttpSession에서 현재 로그인 중인 아이디를 반환한 후 이를 이용하여 단일 조회를 해야 한다

- 단일 조회의 결과(memberDto)에서 비밀번호(memberPw)를 반환하여 기존 비밀번호(pwNow)와의 일치 여부를 검사한다

- 일치한다면 memberDto의 비밀번호를 새로운 비밀번호로 설정한다

- 설정이 끝난 memberDto를 매개변수로 하여 수정(UPDATE)를 실행한다

 

PasswordDto

- 기존 비밀번호(pwNow), 새로운 비밀번호(pwNew), 비밀번호 확인(pwNewCheck)

package com.kh.springhomeSelfStudy.entity;

public class PasswordDto {

	// 필드
	String pwNow;		// 기존 비밀번호
	String pwNew;		// 새로운 비밀번호
	String pwNewCheck;	// 비밀번호 확인
	
	// 생성자
	public PasswordDto() {
		super();
	}

	// getter & setter
	public String getPwNow() {
		return pwNow;
	}

	public void setPwNow(String pwNow) {
		this.pwNow = pwNow;
	}

	public String getPwNew() {
		return pwNew;
	}

	public void setPwNew(String pwNew) {
		this.pwNew = pwNew;
	}

	public String getPwNewCheck() {
		return pwNewCheck;
	}

	public void setPwNewCheck(String pwNewCheck) {
		this.pwNewCheck = pwNewCheck;
	}
}

 

MemberDao

public interface MemberDao {

	// 추상 메소드 - 비밀번호 변경(회원 기능)
	boolean changePassword(String memberId, String memberPw);
}

 

MemberDaoImpl

@Repository
public class MemberDaoImpl implements MemberDao {

	// 의존성 주입
	@Autowired
	private JdbcTemplate jdbcTemplate;
	
	// 추상 메소드 오버라이딩 - 비밀번호 변경(회원 기능)
	@Override
	public boolean changePassword(String memberId, String memberPw) {
		String sql = "update member set member_pw = ? where member_id = ?";
		Object[] param = {memberPw, memberId};
		return jdbcTemplate.update(sql, param) > 0;
	}
}

 

MemberController

- View로부터 PasswordDto의 형태로 값을 받는다 (@ModelAttribute)

@Controller
@RequestMapping("/member")
public class MemberController {

	// 의존성 주입
	@Autowired
	private MemberDao memberDao;
	
	// 회원 비밀번호 변경
	// 회원 비밀번호 변경 Mapping
	@GetMapping("/password")
	public String changePassword() {
		// 회원 비밀번호 변경 페이지(password.jsp)로 연결
		return "/member/password";
	}
	
	// 9. 회원 비밀번호 변경
	// 9-1. 회원 비밀번호 변경 Mapping
	@GetMapping("/password")
	public String changePassword() {
		// 회원 비밀번호 변경 페이지(password.jsp)로 연결
		return "/member/password";
	}
	
	// 9-2. 회원 비밀번호 변경 Mapping에 DB처리 
	@PostMapping("/password")
	public String changePassword(HttpSession session, @ModelAttribute PasswordDto passwordDto) {
		// HttpSession의 loginId에 저장된 값(로그인 중인 아이디)을 반환
		String memberId = (String) session.getAttribute("loginId");
		// 반환된 아이디로 단일 조회 selectOne(String memberId) 실행
		MemberDto memberDto = memberDao.selectOne(memberId);
		// 단일 조회의 결과로 얻은 memberDto에서 비밀번호(memberPw) 반환
		String memberPw = memberDto.getMemberPw();
		// i) passwordDto의 변경 비밀번호(pwNew)와 비밀번호 확인(pwNewCheck)가 같은지 판별
		boolean pwNewEqual = passwordDto.getPwNew().equals(passwordDto.getPwNewCheck());
		// ii) memberDto의 비밀번호(memberPw)와 passwordDto의 기존 비밀번호(pwNow)가 같은지 판별
		boolean pwEqual = memberPw.equals(passwordDto.getPwNow());
		
		if(pwNewEqual && pwEqual) {	// i)와 ii)가 모두 true라면
			// memberDto에서 비밀번호(memberPw)만 passwordDto의 변경 비밀번호(pwNew)로 설정
			memberDto.setMemberPw(passwordDto.getPwNew());
			// 설정이 끝난 memberDto를 매개변수로 하여 수정(UPDATE) 실행 
			memberDao.update(memberDto);
			// 수정(UPDATE) 실행 후 회원 비밀번호 변경 완료 Mapping으로 강제 이동(redirect)
			return "redirect:password_change";
		}
		else {	// 그렇지 않다면
			// 회원 비밀번호 변경 Mapping으로 강제 이동(redirect)
			return "redirect:password";
		}
	}
	
	// 9-3. 회원 비밀번호 변경 완료 Mapping
	@GetMapping("/password_change")
	public String changePasswordSuccess() {
		// 회원 비밀번호 변경 페이지(passwordSuccess.jsp)로 연결
		return "/member/passwordSuccess";
	}
}

 

password.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<jsp:include page = "/WEB-INF/views/template/header.jsp">
	<jsp:param name = "title" value = "비밀번호 변경"/>
</jsp:include>

<h1>비밀번호 변경</h1>
		
<form action = "password" method = "post">
	현재 비밀번호 : <input type = "password" name = "pwNow" placeholder = "현재 비밀번호"><br>
	변경 비밀번호 : <input type = "password" name = "pwNew" placeholder = "변경 비밀번호"><br>
	비밀번호 확인 : <input type = "password" name = "pwNewCheck" placeholder = "비밀번호 확인"><br>
	<button type = "submit">변경</button>
</form>

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

 

개인정보 변경(회원기능)

- HttpSession에서 현재 로그인 중인 아이디를 반환한 후 이를 이용하여 단일 조회를 해야 한다

- 단일 조회의 결과를 View에 전달하여 <form>으로 비밀번호를 제외한 수정 전 값을 표시한다

- View의 사용자 입력을 MemberDto의 형태로 받는다 (@ModelAttribute)

- 단일 조회의 결과인 findDto와 사용자 입력인 inputDto의 비밀번호(memberPw)를 각각 반환하여

  두 값이 일치할 경우 수정(UPDATE)을 실행한다 

 

- 수정할 수 없는 회원 정보가 있다

  1) 회원 아이디(memberId), 생년월일(memberBirth), 회원 가입일(memberJoin)은 수정할 수 없다

  2) 포인트(memberPoint), 회원 등급(memberGrade)는 관리자가 수정해야 할 항목이다

  3) 마지막 로그인(memberLogin)은 로그인할 때만 갱신되어야 한다

 

MemberDao

public interface MemberDao {
 
	// 추상 메소드 - 개인정보 변경(회원 기능)
	boolean changeInformation(MemberDto memberDto);
}

 

MemberDaoImpl

@Repository
public class MemberDaoImpl implements MemberDao {

	// 의존성 주입
	@Autowired
	private JdbcTemplate jdbcTemplate;
	
	// 추상 메소드 오버라이딩 - 개인정보 변경(회원 기능)
	@Override
	public boolean changeInformation(MemberDto memberDto) {
		String sql = "update member set member_nick = ?, "
						+ "member_tel = ?, "
						+ "member_email = ?, "
						+ "member_post = ?, "
						+ "member_base_address = ?, "
						+ "member_detail_address = ? "
					+ "where member_id = ?";
		Object[] param = {memberDto.getMemberNick(), 
					memberDto.getMemberTel(), 
					memberDto.getMemberEmail(), 
					memberDto.getMemberPost(), 
					memberDto.getMemberBaseAddress(), 
					memberDto.getMemberDetailAddress(), 
					memberDto.getMemberId()};
		return jdbcTemplate.update(sql, param) > 0;
	}
}

 

MemberController

// 10. 개인정보 변경
	// 10-1. 개인정보 변경 Mapping
	@GetMapping("/information")
	public String information(HttpSession session, Model model) {
		// HttpSession의 loginId에 저장된 값(로그인 중인 아이디)을 반환
		String memberId = (String) session.getAttribute("loginId");
		// 반환된 아이디로 단일 조회 selectOne(String memberId) 실행
		MemberDto memberDto = memberDao.selectOne(memberId);
		// model에 단일 조회의 결과를 첨부
		model.addAttribute("memberDto", memberDto);
		// 회원 정보 변경 페이지(information.jsp)로 연결
		return "/member/information";
	}
	
	// 10-2. 개인정보 변경 Mapping에 DB처리
	// - View에서 입력받은 inputDto에는 회원 아이디(memberId)가 없다
	@PostMapping("/information")
	public String information(HttpSession session, @ModelAttribute MemberDto inputDto) {
		// 비밀번호 일치 여부 판별
		// HttpSession의 loginId에 저장된 값(로그인 중인 아이디)을 반환
		String memberId = (String) session.getAttribute("loginId");
		// 반환된 아이디로 단일 조회 selectOne(String memberId) 실행
		MemberDto findDto = memberDao.selectOne(memberId);
		// 단일 조회의 결과로 얻은 findDto와 View에서 입력받은 inputDto에서 
		// 각각 비밀번호(memberPw)를 반환하여 그 값이 같은지 판별
		boolean passwordMatch = inputDto.getMemberPw().equals(findDto.getMemberPw());
		if(passwordMatch) {	// 비밀번호가 일치할 경우
			// inputDto의 회원 아이디(memberId)를 HttpSession에서 반환한 아이디로 설정
			inputDto.setMemberId(memberId);
			// inputDto를 매개변수로 하여 수정(UPDATE) 실행
			memberDao.changeInformation(inputDto);
			// 수정(UPDATE) 실행 후 회원 마이페이지 Mapping으로 강제 이동(redirect)
			return "redirect:mypage";
		}
		else {	// 비밀번호가 틀린 경우
			// 정보 변경 실패 Mapping으로 강제 이동(redirect)
			return "redirect:information?error";
		}
	}
	
	// 10-3. 정보 변경 실패 Mapping
	@GetMapping("/information_fail")
	public String informationFail() {
		// 수정 실패 페이지(editFail.jsp)으로 이동 (jsp 재활용)
		return "redirect:editFail";
	}

 

회원 탈퇴(회원기능)

- View에서 회원 비밀번호(memberPw), 비밀번호 확인(memberPwCheck)를 입력받는다 (@RequestParam)

- 회원 비밀번호(memberPw)와 비밀번호 확인(memberPwCheck)의 일치 여부를 판별한다

- 두 값이 일치한다면 회원 탈퇴(관리자 전용) 메소드인 delete(String memberId)를 실행한다

- 메소드 실행 후 로그아웃시킨다

  (비회원은 회원 관련 모든 페이지에서 Interceptor의 간섭을 받으므로 회원 탈퇴 후 로그아웃을 시켜야 한다)

- 회원 탈퇴 결과 Mapping은 로그아웃을 위해 Interceptor의 간섭을 받지 않도록 해야 한다

 

MemberDao, MemberDaoImpl

- 기존에 작성했던 회원 탈퇴(관리자 전용) 메소드인 delete(String memberId)을 사용한다

 

MemberController

@Controller
@RequestMapping("/member")
public class MemberController {

	// 의존성 주입
	@Autowired
	private MemberDao memberDao;

	// 11. 회원 탈퇴 (회원 기능)
	// 11-1. 회원 탈퇴 Mapping
	@GetMapping("/goodbye")
	public String goodbye() {
		// 회원 탈퇴 페이지(goodbye.jsp)로 연결
		return "/member/goodbye";
	}
	
	// 11-2. 회원 탈퇴 Mapping에서 DB 처리
	@PostMapping("/goodbye")
	public String goodbye(HttpSession session, @RequestParam String inputPw, @RequestParam String inputPwCheck) {
		// 1) 입력 비밀번호 일치 여부
		boolean inputMatch = inputPw.equals(inputPwCheck);
		// 2) 비밀번호 일치 여부
		// HttpSession의 loginId에 저장된 값(로그인 중인 아이디)을 반환
		String memberId = (String) session.getAttribute("loginId");
		// 반환된 아이디로 단일 조회 selectOne(String memberId) 실행
		MemberDto memberDto = memberDao.selectOne(memberId);
		// 단일 조회의 결과로 얻은 memberDto의 비밀번호(memberPw)와 입력 비밀번호(inputPw)가 같은지 판별
		boolean passwordMatch = memberDto.getMemberPw().equals(inputPw);
		if(inputMatch && passwordMatch) {	// 둘 다 일치할 경우
			// memberId를 매개변수로 하여 삭제(DELETE) 실행
			memberDao.delete(memberId);
			// HttpSession의 모든 정보(로그인 회원 아이디, 회원 등급) 삭제
			session.removeAttribute("loginId");
			session.removeAttribute("mg");
			// 회원 탈퇴 성공 페이지 Mapping으로 강제 이동(redirect)
			return "redirect:goodbye_result";
		}
		else {	// 그렇지 않을 경우
			// 회원 탈퇴 에러 Mapping으로 강제 이동(redirect)
			return "redirect:goodbye?error";
		}
	}
	
	// 11-3. 회원 탈퇴 성공 페이지
	@GetMapping("/goodbye_result")
	public String goodbyeResult() {
		// 회원 탈퇴 페이지(goodbyeResult.jsp)로 연결
		return "/member/goodbyeResult";
	}
}

 

goodbye.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<jsp:include page = "/WEB-INF/views/template/header.jsp">
	<jsp:param name = "title" value = "회원 탈퇴"/>
</jsp:include>

	<div align = "center">
		<h1>회원 탈퇴</h1><br><br>
		<form action = "goodbye" method = "post">
			비밀번호 : <input type = "password" name = "inputPw" required placeholder = "비밀번호"><br><br>
			비밀번호 확인 : <input type = "password" name = "inputPwCheck" required placeholder = "비밀번호 확인"><br><br>
			<button type = "submit">탈퇴하기</button>
		</form>
	</div>

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

 

Interceptor 설정 추가

- 회원 탈퇴 결과 Mapping 제외(exclude)

@Configuration
public class InterceptorConfiguration implements WebMvcConfigurer {

	// 의존성 주입
	@Autowired
	TestInterceptor testInterceptor;
	
	// 의존성 주입
	@Autowired
	MemberInterceptor memberInterceptor;
	
	// 의존성 주입
	@Autowired
	AdminInterceptor adminInterceptor;

	// 추상 메소드 오버라이딩 - addInterceptors(InterceptorRegistry registry)
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		
		// TestInterceptor에 대한 설정
		registry.addInterceptor(testInterceptor)
				.addPathPatterns("/**");
		
		// MemberInterceptor에 대한 설정
		registry.addInterceptor(memberInterceptor)
				.addPathPatterns(
						"/pocketmon/**",		// 포켓몬 전부
						"/music/detail",		// 음원 상세
						"/member/**"			// 회원 전체
						)
				.excludePathPatterns(
						"/member/join",			// 회원가입
						"/member/insert_success",	// 회원 가입 완료
						"/member/login",		// 로그인
						"/member/goodbye_result"	// 탈퇴 완료
						);
		
		// AdminInterceptor에 대한 설정
		registry.addInterceptor(memberInterceptor)
				.addPathPatterns(
						"/guestbook/edit",		// 방명록 수정
						"/guestbook/delete",		// 방명록 상세
						"/music/**",			// 음원 전체
						"/member/list",			// 회원 목록
						"/member/detail",		// 회원 상세
						"/member/delete",		// 회원 삭제
						"/member/edit*"			// 회원 정보 수정
						)
				.excludePathPatterns(
						"/music/list",			// 음원 목록
						"/music/detail"			// 음원 상세
						);
	}
}

 

관리자페이지

- 관리자만 접근할 수 있는 페이지

- 관리자는 회원 기능 뿐만 아니라 관리자만의 기능을 사용할 수 있다

- 관리자 페이지는 관리자 외에는 접근하지 못하도록 Interceptor로 차단해야 한다

 

AdminController

@Controller
@RequestMapping("/admin")
public class AdminController {

	@GetMapping("/home")
	public String home() {
		return "admin/home";
	}
}

 

home.jsp

- home에서 관리자 페이지로 이동할 수 있도록 하이퍼링크 설정

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<html>
<head>
<title>
<c:choose>
    <%-- title이라는 변수의 값이 있다면 title에 입력될 값은 해당 title 변수의 값으로  --%>
    <c:when test = "${param.title != null}">
        ${param.title}
    </c:when>
    <c:otherwise>
        홈페이지
    </c:otherwise>
</c:choose>
</title>
</head>

<body>

<div align = "center">

<%-- 로그인 상태인지를 판별 --%>
<c:set var = "login" value = "${loginId != null}"></c:set>

<%-- 로그인한 회원의 등급이 '관리자'인지 판별 --%>
<c:set var = "admin" value = "${mg == '관리자'}"></c:set>

<%-- 로그인을 할 경우에만 보이는 메뉴 --%>
<c:choose>
	<%-- 회원에게만 보이는 메뉴 --%>
	<c:when test = "${login}">
		<a href = "/">홈</a>
		<a href = "/guestbook/list">방명록</a>
		<a href = "/pocketmon/list">포켓몬스터</a>
		<a href = "/music/list">음원관리</a>
		<a href = "/member/logout">로그아웃</a>
		<a href = "/member/mypage">마이페이지</a>
	</c:when>
	<c:otherwise>
		<a href = "/">홈</a>
		<a href = "/guestbook/list">방명록</a>
		<a href = "/member/join">회원가입</a>
		<a href = "/member/login">로그인</a>
	</c:otherwise>	
</c:choose>

<%-- 관리자에게만 보이는 메뉴 --%>
<c:if test = "${login && admin}">
	<a href="/admin/home">관리자페이지</a>
</c:if>

</div>

<hr>

<%-- 미리 <div> 태그를 써서 Main Content는 자동으로 가운데 정렬이 되도록 한다 --%>
<%-- 나머지 </div>는 footer에 작성하여 Main Content를 감싸도록 한다 --%>
<div align = "center" style = "min-height : 400px">

 

Interceptor 설정 추가

- 관리자 페이지 Mapping에 대한 Interceptor 추가

@Configuration
public class InterceptorConfiguration implements WebMvcConfigurer {

	// 의존성 주입
	@Autowired
	TestInterceptor testInterceptor;
	
	// 의존성 주입
	@Autowired
	MemberInterceptor memberInterceptor;
	
	// 의존성 주입
	@Autowired
	AdminInterceptor adminInterceptor;

	// 추상 메소드 오버라이딩 - addInterceptors(InterceptorRegistry registry)
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		
		// TestInterceptor에 대한 설정
		registry.addInterceptor(testInterceptor)
				.addPathPatterns("/**");
		
		// MemberInterceptor에 대한 설정
		registry.addInterceptor(memberInterceptor)
				.addPathPatterns(
						"/pocketmon/**",		// 포켓몬 전부
						"/music/detail",		// 음원 상세
						"/member/**"			// 회원 전체
						)
				.excludePathPatterns(
						"/member/join",			// 회원가입
						"/member/insert_success",	// 회원 가입 완료
						"/member/login",		// 로그인
						"/member/goodbye_result"	// 탈퇴 완료
						);
		
		// AdminInterceptor에 대한 설정
		registry.addInterceptor(memberInterceptor)
				.addPathPatterns(
						"/guestbook/edit",		// 방명록 수정
						"/guestbook/delete",		// 방명록 상세
						"/music/**",			// 음원 전체
						"/member/list",			// 회원 목록
						"/member/detail",		// 회원 상세
						"/member/delete",		// 회원 삭제
						"/member/edit*",		// 회원 정보 수정
						"/admin/**"			// 관리자 페이지
						)
				.excludePathPatterns(
						"/music/list",			// 음원 목록
						"/music/detail"			// 음원 상세
						);
	}
}

 

관리자템플릿페이지

adminHeader.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<%-- 기존의 Header를 재사용 --%>
<jsp:include page="/WEB-INF/views/template/header.jsp">
	<jsp:param name = "title" value = "${param.title}"/>
</jsp:include>

<%--관리자 메뉴 추가 --%>
<a href = "/member/list">회원목록</a>
<a href = "/admin/pocketmon">포켓몬 현황</a>
<a href = "/admin/music/play">음원 재생 순위</a>
<a href = "/admin/music/release">월별 판매 현황</a>

<hr>

 

adminFooter.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<%-- 기존의 Footer를 재사용 --%>
<jsp:include page = "/WEB-INF/views/template/footer.jsp"></jsp:include>

 


 

DTO (Data Transfer Object)

- 계층 간(Controller, View, Business Layer) 데이터 교환을 위한 객체

- field, getter & setter 메소드, 기본 생성자만 가진 클래스

 

VO (Value Object)

- DTO와 비슷하나 내부의 값을 변경할 수 없는(immutable) 읽기 전용(read-only) 객체

- field, getter 메소드, 기본 생성자만 가진 클래스

  (과정에서는 DB 테이블의 전체 컬럼 중 일부의 값만을 가지고 올 때 VO를 사용하였다)

 


 

VO 활용 1. 관리자 페이지에 속성별 포켓몬의 수 조회

- 타입(type)과 타입별 수(cnt)를 조회하여 View에 표시한다

- 테이블의 전체 컬럼 중 일부 컬럼의 값만 조회할 때 VO를 정의하여 사용한다

 

PocketMonsterVO

public class PocketMonsterCountVO {
	
	// 필드
	private String type;
	private int cnt;
	
	// 생성자
	public PocketMonsterCountVO() {
		super();
	}

	// getter & setter
	public String getType() {
		return type;
	}

	public void setType(String type) {
		this.type = type;
	}

	public int getCnt() {
		return cnt;
	}

	public void setCnt(int cnt) {
		this.cnt = cnt;
	}
	
	// toString 오버라이딩
	@Override
	public String toString() {
		return "PocketMonsterCountVO [type=" + type + ", cnt=" + cnt + "]";
	}
}

 

PocketMonsterDao

public interface PocketMonsterDao {

	// 추상 메소드 - 타입별 포켓몬 수
	List<PocketMonsterCountVO> selectCountList();
}

 

PocketMonsterDaoImpl

@Repository
public class PocketMonsterDaoImpl implements PocketMonsterDao {

	// 의존성 주입
	@Autowired
	private JdbcTemplate jdbcTemplate;

	// PocketMonsterCountVO에 대한 RowMapper
	private RowMapper<PocketMonsterCountVO> countMapper = new RowMapper<>() {
		@Override
		public PocketMonsterCountVO mapRow(ResultSet rs, int rowNum) throws SQLException {
			PocketMonsterCountVO vo = new PocketMonsterCountVO();
			vo.setType(rs.getString("type"));
			vo.setCnt(rs.getInt("cnt"));
			return vo;
		}
	};
	
	// 추상 메소드 오버라이딩 - 타입별 포켓몬 수
	@Override
	public List<PocketMonsterCountVO> selectCountList() {
		String sql = "select type, count(*) cnt from pocket_monster group by type";
		return jdbcTemplate.query(sql, countMapper);
	}
}

 

PocketMonsterController

@Controller
@RequestMapping("/admin")
public class AdminController {

	// 의존성 주입
	@Autowired
	private PocketMonsterDao pocketMonsterDao;

	// 포켓몬 현황 Mapping
	@GetMapping("/pocketmon")
	public String pocketmon(Model model) {
		// model에 타입별 포켓몬 수 조회 결과를 첨부
		model.addAttribute("list", pocketMonsterDao.selectCountList());
		// 포켓몬 현황 페이지(pocketmon.jsp)로 연결
		return "admin/pocketmon";
	}
}

 

pocketmon.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<jsp:include page = "/WEB-INF/views/template/adminHeader.jsp">
	<jsp:param name = "title" value = "관리자 페이지"/>
</jsp:include>

<h1>포켓몬 현황</h1>

<table border = "1" width = "300">
	<thead>
		<tr>
			<th>순위</th>
			<th>속성</th>
			<th>개체수</th>
		</tr>
	</thead>
	<tbody align = "center">
		<c:forEach var = "vo" items = "${list}" varStatus = "status">
			<tr>
				<td>${status.count}</td>
				<td>${vo.getType()}</td>
				<td>${vo.cnt}</td>
			</tr>
		</c:forEach>
	</tbody>
</table>

<jsp:include page = "/WEB-INF/views/template/adminFooter.jsp"></jsp:include>

- <c:forEach></c:forEach>의 varStatus 속성을 사용하면 반복문의 상태를 알 수 있다

count 출력 중인 데이터의 순서(갯수, 1번부터 시작)
index 출력 중인 데이터의 인덱스(위치, 0번부터 시작)
first 처음인지 아닌지 반환
last 마지막인지 아닌지 반환