Luver Duck 2022. 9. 9. 13:23

회원 관련 기능 

- 회원 가입 : day29 - 0902

- 회원 목록 : day32 - 0907

- 회원 상세 정보 : day32 - 0907

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

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

 

- 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 + "]";
	}
}

 

로그인 / 로그아웃 기능

- 로그인 기능은 단순히 입력값과 단일 조회의 결과를 비교하면 되므로 DAO에서 메소드를 정의할 필요가 없다

- 로그인 시간을 갱신하기 위해서는 DAO에서 메소드를 정의해야 한다

- 로그인 / 로그아웃 시 메인 페이지로 강제 이동(redirect)하므로 jsp는 필요없다

 

** HttpSession 인터페이스 명령

Object getAttribute(String name) 지정한 이름으로 바인딩된 Object를 반환한다
void setAttribute(String name, Object value) 지정한 이름으로 해당 Object를 바인딩한다
void invalidate() 세션을 무효화하고 모든 Object의 바인딩을 해제한다  (세션 파과)

 

로그인 절차

1) 로그인 페이지에서 로그인 아이디와 비밀번호를 전달받는다

2) 로그인이 가능한지 검사한다

  i) DB에 입력한 아이디와 일치하는 회원 아이디를 가지고 있는 데이터가 있는지 확인

  ii) 일치하는 데이터가 있다면 입력한 비밀번호와 해당 데이터의 비밀번호가 일치하는지 확인

  iii) 비밀번호가 일치한다면 Session에 해당 회원 아이디를 저장하고 마지막 로그인 시간 갱신

 

로그아웃

- 로그아웃 시 Session에 저장된 회원 아이디를 삭제

 

MemberDao

public interface MemberDao {
 
	// 추상 메소드 - 로그인 시간 갱신
	boolean updateLoginTime(String memberId);
}

 

MemberDaoImpl

@Repository
public class MemberDaoImpl implements MemberDao {

	// 의존성 주입
	@Autowired
	private JdbcTemplate jdbcTemplate;
	
	// 추상 메소드 오버라이딩 - 로그인 시간 갱신
	@Override
	public boolean updateLoginTime(String memberId) {
		String sql = "update member set member_login = sysdate where member_id = ?";
		Object[] param = {memberId};
		return jdbcTemplate.update(sql, param) > 0;
	}
}

 

MemberController

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

	// 의존성 주입
	@Autowired
	private MemberDao memberDao;
	
	// 6. 로그인
	// 6-1. 로그인 페이지 Mapping
	@GetMapping("/login")
	public String login() {
		// 로그인 페이지(login.jsp)로 이동
		// return "/WEB-INF/views/member/login.jsp";
		return "member/login";
	}
	
	// 6-2. 로그인 Mapping에 DTO 전달
	// - View에서 전달받은 inputDto에는 사용자가 입력한 아이디(memberId)와 비밀번호(memberPw)가 존재한다
	@PostMapping("/login")
	public String login(@ModelAttribute MemberDto inputDto, HttpSession session) {
		// inputDto의 아이디(memberId)로 단일 조회 selectOne(String memberId)를 실행
		MemberDto findDto = memberDao.selectOne(inputDto.getMemberId());
		// 조회 결과가 없다면 (null) 
		if(findDto == null) {
			// 로그인 에러 페이지로 강제 이동(redirect)
			return "redirect:login?error";
		}
		// 조회 결과가 있다면 (해당하는 아이디가 있다면)
		// inputDto의 비밀번호(memberPw)와 단일 조회의 결과인 findDto의 비밀번호(memberPw)를 비교
		boolean passwordMatch = inputDto.getMemberPw().equals(findDto.getMemberPw());
		if(passwordMatch) {	// inputDto와 findDto의 비밀번호가 일치한다면
			// HttpSession에 loginId라는 이름으로 memberId의 값을 저장
			session.setAttribute("loginId", inputDto.getMemberId());
			// HttpSession에 mg라는 이름으로 findDto의 memberGrade의 값을 저장 (***)
			session.setAttribute("mg", findDto.getMemberGrade());
			// 로그인 시간 갱신
			memberDao.updateLoginTime(inputDto.getMemberId());
			// 메인 페이지 Mapping으로 강제 이동(redirect)
			return "redirect:/";
		}
		else {	// inputDto와 findDto의 비밀번호가 일치하지 않는다면
			// 로그인 에러 Mapping으로 강제 이동(redirect)
			return "redirect:login?error";
		}
	}
	
	// 7. 로그아웃 
	// 로그아웃 Mapping
	@GetMapping("/logout")
	public String logout(HttpSession session) {
		// HttpSession에 저장된 loginId 삭제
		session.removeAttribute("loginId");
		// HttpSession에 mg라는 이름으로 memberGrade의 값 삭제 (***)
		session.removeAttribute("mg");
		// 메인 페이지 Mapping으로 강제 이동(redirect)
		return "redirect:/";
	}

 

login.jsp

<%@ 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>
<meta charset="UTF-8">
<title>로그인</title>
</head>
<body>

<div align = "center">

<h1>회원 로그인</h1>

<!-- 정상/이상 접근일 때 모두에서 나오는 화면 -->
<form action = "login" method = "post">
	아이디 : <input type = "text" name = "memberId" placeholder = "아이디" required><br><br>
	비밀번호 : <input type = "password" name = "memberPw" placeholder = "비밀번호" required><br><br>
	<button type = "submit">로그인</button>
</form>

<!-- 이상한 경우만 추가로 나오는 문구 -->
<c:if test = "${param.error != null}">
	<h2 style = "color:red">입력한 정보가 맞지 않습니다</h2>
</c:if>
	
</div>

</body>
</html>

 


 

템플릿 페이지 - Header와 Footer 구현

 

Header.jsp

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

<%-- 로그인 상태인지를 판별 : Session에 저장된 값은 이름을 입력하는 것만으로도 그 값을 불러올 수 있다 --%>
<c:set var = "login" value = "${loginId != null}"></c:set>

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

<body>

<div align = "center">

<%-- 로그인과 상관없이 나오는 메뉴 --%>
<%-- 네비게이션 메뉴는 반드시 절대 경로로 작성해야 한다 --%>
<a href = "/">홈</a>
<a href = "/pocketmon/list">포켓몬스터</a>
<a href = "/guestbook/list">방명록</a>
<a href = "/music/list">음원관리</a>
<a href = "/board/list">게시판</a>


<%-- 로그인 상태인지(login !- null)를 변수화 --%>
<c:set var = "login" value = "${loginId != null}"></c:set>
<%-- 또는 <c:set var = "login" value = "${sessionScope.loginId != null}"></c:set> --%>

<%-- 로그인을 할 경우에만 보이는 메뉴 --%>
<c:choose>
	<c:when test="${login}">
		<a href = "/member/logout">로그아웃</a>
		<a href = "/member/mypage">마이페이지</a>
	</c:when>
	<c:otherwise>
		<a href = "/member/insert">회원가입</a>
		<a href = "/member/login">로그인</a>
	</c:otherwise>
</c:choose>

</div>

<hr>

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

 

Footer.jsp

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

<hr>

<div align = "center">

	<h2>KH 정보교육원 웹 개발 수업자료</h2>
	
	<%-- 로그인 정보를 출력하도록 --%>
	loginId : ${sessionScope.loginId}<br>
	로그인 상태인가? : ${sessionScope.loginId != null}<br>
	
</div>

</body>
</html>

 

** HttpSession 인터페이스 명령 추가

Object getAttribute(String name) 지정한 이름으로 바인딩된 Object를 반환한다
void setAttribute(String name, Object value) 지정한 이름으로 해당 Object를 바인딩한다
void invalidate() Session을 무효화하고 모든 Object의 바인딩을 해제한다  (세션 파과)
String getId() Session의 고유 번호를 문자열로 반환 (32자리 16진수)
boolean isNew() 새로 생성된 Session(신규 접속)일 경우에만 true를 반환 

 

** pageContext : 페이지 내로 탐색 범위를 제한

세션 ID : ${pageContext.session.getId()}
신규 세션인가? : ${pageContext.session.isNew()}

 

마이 페이지

- Session에서 로그인 중인 회원 아이디를 반환하여 단일 조회 결과를 마이 페이지에 표시한다

 

MemberController

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

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

	// 8. 마이 페이지 - 현재 로그인한 회원의 정보를 화면에 출력
	// 마이 페이지 Mapping
	@GetMapping("/mypage")
	public String mypage(Model model, HttpSession session) { 
		// HttpSession의 loginId에 저장된 값(로그인 중인 아이디)을 반환
		// - .getAttribute(String name) : 지정한 이름으로 바인딩된 Object를 반환한다
		// - .getAttribute(String name)의 반환형은 Object 형태이므로 String으로 형 변환(Down Casting)해야 한다
		String memberId = (String) session.getAttribute("loginId");
		// HttpSession에서 반환한 아이디(memberId)로 단일 조회 selectOne(String memberId)를 실행
		model.addAttribute("memberDto", memberDao.selectOne(memberId));	
		// 마이 페이지(mypage.jsp)로 이동
		// return "/WEB-INF/views/member/mypage.jsp";
		return "/member/mypage";
	}	
}

 

mypage.jsp

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

<%-- header.jsp 적용 --%>
<jsp:include page = "/WEB-INF/views/template/header.jsp">
	<jsp:param name = "title" value = "마이 페이지"/>
</jsp:include>

<h1>마이 페이지</h1>
		
<h3>아이디 : "${memberDto.getMemberId()}"</h3>
<h3>닉네임 : ${memberDto.getMemberNick()}</h3>
<h3>생년월일 : ${memberDto.getMemberBirth()}</h3>
<h3>연락처 : ${memberDto.getMemberTel()}</h3>
<h3>이메일 : ${memberDto.getMemberEmail()}</h3>
<h3>우편주소 : ${memberDto.getMemberPost()}</h3>
<h3>기본주소 : ${memberDto.getMemberBaseAddress()}</h3>
<h3>상세주소 : ${memberDto.getMemberDetailAddress()}</h3>
<h3>포인트 : ${memberDto.getMemberPoint()}</h3>
<h3>등급 : ${memberDto.getMemberGrade()}</h3>
<h3>가입일 : <fmt:formatDate value="${memberDto.memberJoin}" pattern="y년 M월 d일 E a h시 m분 s초"/></h3>
<h3>최종 로그인 : <fmt:formatDate value="${memberDto.memberLogin}" pattern="y년 M월 d일 E a h시 m분 s초"/></h3>

<br>

<h2><a href = "member/list">목록 보기</a></h2>
<h3><a href = "edit?memberId=${memberDto.getMemberId()}">회원 정보 변경</a></h3>

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

 

회원 등급에 따른 처리

- 회원 등급에 따라 jsp에서 보이는 메뉴를 다르게 할 수 있다

- 이를 위해 가장 먼저 로그인시 Session에 해당 회원의 회원 등급을 저장하도록 해야 한다

 

MemberController 수정(***)

- 로그인시 Session에 해당 회원의 아이디(memberId) 뿐만 아니라 회원 등급(memberGrade)도 저장 

- 로그아웃시 Session에 저장된 해당 회원의 아이디(memberId) 뿐만 아니라 회원 등급(memberGrade)도 삭제

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

	// 의존성 주입
	@Autowired
	private MemberDao memberDao;
	
	// 로그인
	// 1) 로그인 페이지 Mapping
	@GetMapping("/login")
	public String login() {
		
		// 로그인 페이지(login.jsp)로 이동
		// return "/WEB-INF/views/member/login.jsp";
		return "member/login";
	}
	
	// 2) 로그인 Mapping에 DTO 전달
	// - View에서 전달받은 inputDto에는 사용자가 입력한 아이디(memberId)와 비밀번호(memberPw)가 존재한다
	@PostMapping("/login")
	public String login(@ModelAttribute MemberDto inputDto, HttpSession session) {
		
		// inputDto의 아이디(memberId)로 단일 조회 selectOne(String memberId)를 실행
		MemberDto findDto = memberDao.selectOne(inputDto.getMemberId());
		
		// 조회 결과가 없다면 (null) 
		if(findDto == null) {
			// 로그인 에러 페이지로 강제 이동(redirect)
			return "redirect:login?error";
		}
		
		// 조회 결과가 있다면 (해당하는 아이디가 있다면)
		// inputDto의 비밀번호(memberPw)와 단일 조회의 결과인 findDto의 비밀번호(memberPw)를 비교
		boolean passwordMatch = inputDto.getMemberPw().equals(findDto.getMemberPw());
		
		if(passwordMatch) { // inputDto와 findDto의 비밀번호가 일치한다면 (로그인 성공)
			// Session에 loginId라는 이름으로 memberId의 값을 저장
			session.setAttribute("loginId", inputDto.getMemberId());
			// Session에 mg라는 이름으로 findDto의 memberGrade의 값을 저장 (***)
			session.setAttribute("mg", inputDto.getMemberGrade());
			// 로그인 시간 갱신
			memberDao.updateLoginTime(inputDto.getMemberId());
			// 메인 페이지 Mapping으로 강제 이동(redirect)
			return "redirect:/";
		}
		else { // inputDto와 findDto의 비밀번호가 일치하지 않는다면
			// 로그인 에러 Mapping으로 강제 이동(redirect)
			return "redirect:login?error";
		}
	}

	// 로그아웃 
	// 로그아웃 Mapping
	@GetMapping("/logout")
	public String logout(HttpSession session) {
		
		// Session에 loginId의 값 삭제
		session.removeAttribute("loginId");

		// Session에 mg의 값 삭제 (***)
		session.removeAttribute("mg");

		// 메인 페이지 Mapping으로 강제 이동(redirect)
		return "redirect:/";
	}
}

 

header.jsp 수정

- 로그인한 회원 등급에 따라 보이는 메뉴가 다르도록 수정

<%@ 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이라는 변수의 값이 있다면(null이 아니면) 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 = "/member/list">회원목록</a>
</c:if>

</div>

<hr>

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

 

footer.jsp 수정

- 로그인한 회원의 등급을 확인할 수 있도록 추가

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

<hr>

<div align = "center">

	<h2>KH 정보교육원 웹 개발 수업자료</h2>
	
	loginId : ${sessionScope.loginId}<br>
	로그인 상태인가? : ${sessionScope.loginId != null}<br>

	<%-- 로그인시 저장한 회원 등급(mg) --%>
	mg : ${sessionScope.mg}<br>
	관리자인가? : ${sessionScope.mg == '관리자'}<br>
	
</div>

</body>
</html>

 

music 패키지의 insert.jsp 수정

- 관리자에게만 음원 등록이 보이도록 수정

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

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

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

<jsp:include page = "/WEB-INF/views/template/header.jsp">
	<jsp:param name = "title" value = "전체 음원 목록"/>
</jsp:include>

<!-- 제목 -->
<h1>음원 목록 조회</h1>

<!-- 음원 검색창 (검색일 떄는 method를 get으로 한다) -->
<form action = "list" method = "get">
	<!-- 항목 -->
	<select name = "type" required>
		<option value = "music_title">음원명</option>
		<option value = "music_artist">가수명</option>
		<option value = "music_album">앨범명</option>
	</select>
	<!-- 키워드 -->
	<input name = "keyword" placeholder = "검색어" required><br><button>검색</button>
</form>

<hr>

<!-- 목록 출력 -->
<table border = "1" width = "600">
	<thead>
		<tr>
			<th>음원번호</th>
			<th>음원명</th>
			<th>가수명</th>
			<th>앨범명</th>
			<th>재생수</th>
			<th>발매일</th>
		</tr>
	</thead>
	
	<tbody>
		<!-- 반복문 -->
		<c:forEach var="musicDto" items="${list}">
			<tr height = "10" valign = "top">
				<td>${musicDto.getMusicNo()}</td>
				<td>
					<a href = "detail?musicNo=${musicDto.getMusicNo()}">
						${musicDto.getMusicTitle()}
					</a>
					<!-- 만약 조회수가 3 이상이면 인기곡이라는 문구가 붙도록 -->
					<c:if test="${musicDto.musicPlay >= 3}">
						<img src = "/image/hot.png" width="20" height="20"> 
					</c:if>
				</td>
				<td>${musicDto.getMusicArtist()}</td>
				<td>${musicDto.getMusicAlbum()}</td>
				<td>${musicDto.getMusicPlay()}</td>
				<td>${musicDto.getReleaseTitle()}</td>
			</tr>
		</c:forEach>
	</tbody>
	
	<c:if test = "${mg == '관리자'}">
		<tfoot align = "right">
			<tr>
				<td colspan = "6">
					<a href = "insert">음원 등록</a><br>
				</td>
			</tr>
		</tfoot>
	</c:if>
	
</table>

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

 

member 패키지의 detail.jsp 수정

- 회원용 메뉴와 관리자용 메뉴 분리

- 회원용 메뉴의 하이퍼링크는 임시로 만들어 둔 것임

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

<jsp:include page = "/WEB-INF/views/template/header.jsp">
	<jsp:param name = "title" value = "회원 정보"/>
</jsp:include>
	
<h1>${memberDto.getMemberId()} 회원 상세 정보</h1>

<table border = "1" width = 400>
	<tbody>
		<tr>
			<th width = "25%">아이디</th>
			<td>${memberDto.getMemberId()}</td>
		</tr>
		<tr>
			<th>닉네임</th>
			<td>${memberDto.getMemberNick()}</td>
		</tr>
		<tr>
			<th>생년월일</th>
			<td>${memberDto.getMemberBirth()}</td>
		</tr>
		<tr>
			<th>전화번호</th>
			<td>${memberDto.getMemberTel()}</td>
		</tr>
		<tr>
			<th>이메일</th>
			<td>${memberDto.getMemberEmail()}</td>
		</tr>
		<tr>
			<th>주소</th>
			<td>
				<c:if test="${memberDto.getMemberPost() != null}">
					[${memberDto.getMemberPost()}]
					${memberDto.getMemberBaseAddress()}
					${memberDto.getMemberDetailAddress()}
				</c:if>					
			</td>
		</tr>
		<tr>
			<th>포인트</th>
			<td>
				<fmt:formatNumber pattern="#,##0">${memberDto.getMemberPoint()}</fmt:formatNumber>
			</td>
		</tr>
		<tr>
			<th>등급</th>
			<td>${memberDto.getMemberGrade()}</td>
		</tr>
		<tr>
			<th>가입일</th>
			<td>
				<fmt:formatDate value = "${memberDto.getMemberJoin()}" pattern = "y년 M월 d일 E a h시 m분 s초"/>
			</td>
		</tr>
		<tr>
			<th>마지막 로그인</th>
			<td>
				<fmt:formatDate value = "${memberDto.getMemberLogin()}" pattern = "y년 M월 d일 E a h시 m분 s초"/>
			</td>
		</tr>
	</tbody>
</table>

<c:choose>
	<c:when test = "${mg == 관리자}">
		<%-- 관리자용 메뉴 --%>
		<h3><a href = "list">회원 목록</a></h3>
		<h3><a href = "edit?memberId=${memberDto.getMemberId()}">회원 정보 변경</a></h3>
		<h3><a href = "delete?memberId=${memberDto.getMemberId()}">회원 탈퇴</a></h3>
	</c:when>
	<c:otherwise>
		<%-- 회원용 메뉴 --%>
		<h3><a href = "#">비밀번호 변경</a></h3>
		<h3><a href = "#">개인정보 변경</a></h3>
		<h3><a href = "#">회원 탈퇴</a></h3>
	</c:otherwise>
</c:choose>

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

 

인터셉터(Interceptor)

- Controller의 Handler를 호출하기 전과 후의 요청과 응답을 참조하거나 가공할 수 있는 일종의 필터

- Controller로 들어오는 HttpRequest와 Controller의 응답인 HttpResponse를 가로채서 추가적인 처리를 할 수 있다

 

 

인터셉터 클래스 생성

- @Component 를 붙여 빈(Bean)으로 등록한다

- HandlerInterceptor의 구현(implement)을 통해 생성할 수 있다

- 3개의 메소드를 오버라이딩할 수 있다

 

HandlerInterceptor의 추상 메소드

boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)

- preHandle : Handler를 실행하기 전 - return이 true이면 통과 / false이면 차단

- postHandle : Handler를 실행한 직후 

- afterCompletion : 요청 처리가 완료된 후 (View 생성 후)

 

** 컴포넌트(Component)

- 단일 기능을 수행하는 독립적인 단위 모듈

@Component 해당 클래스를 빈(Bean)으로 등록

 

인터셉터 생성 예시

@Component
public class TestImterceptor implements HandlerInterceptor {

	@Override
	public boolean preHandle(HttpServletRequest request, 	// 사용자 요청 정보
				HttpServletResponse response, 	// 사용자 응답 정보
				Object handler			// 실행될 Controller의 메소드 정보
				) throws Exception {
		System.out.println("preHandle");
		// DispatcherServlet의 HandlerMapping이 찾아준 Controller의 메서드를 참조할 수 있는 HandlerMethod 객체
		System.out.println("-> handler = " + handler);
		return true;	// true일 때 통과 / false
	}

	@Override
	public void postHandle(HttpServletRequest request, 	// 사용자 요청 정보
				HttpServletResponse response, 	// 사용자 응답 정보
				Object handler,			// 처리한 Controller의 메소드 정보
				ModelAndView modelAndView	// 모델과 뷰 정보
				) throws Exception {
		System.out.println("postHandle");
		System.out.println("-> handler = " + handler);
		System.out.println("-> M & V = " + modelAndView);
	}

	@Override
	public void afterCompletion(HttpServletRequest request, 	// 사용자 요청 정보
					HttpServletResponse response, 	// 사용자 응답 정보
					Object handler, 		// 처리한 Controller의 메소드 정보
					Exception ex			// 예외 여부(발생한 오류 정보)
					) throws Exception {
		System.out.println("afterCompletion");
		System.out.println("-> ex = " + ex);
		if(ex == null) { // 처리 과정에서 예외가 생겼다면
			
		}
	}
}

 

인터셉터 설정 클래스 생성

- @Configuration 을 붙여 빈(Bean)으로 등록한다 

- 인터셉터의 설정(configuration)과 관련된 역할을 한다 (application.properties에서 하기 어려운 복잡한 설정을 구현)

- WebMvcConfigurer의 구현(implement)을 통해 생성할 수 있다

- addInterceptors(InterceptorRegistry registry) 메소드를 오버라이딩하여 인터셉터를 등록할 수 있다

 

WebMvcConfigurer(인터페이스)의 추상 메소드 중 일부

default void addInterceptors(InterceptorRegistry registry) Spring MVC lifecycle interceptor를 생성한다

 

InterceptorRegistry(클래스)의 메소드 중 일부

InterceptorRegistration addInterceptor(HandlerInterceptor interceptor) 해당 HandlerInterceptor를 추가한다

 

InterceptorRegistration(클래스)의 메소드 중 일부

InterceptorRegistration addPathPatterns(String  ... patterns) Interceptor의 간섭을 받을 경로를 추가한다
InterceptorRegistration excludePathPatterns(String ... patterns) Interceptor가 간섭을 받지 않을 경로를 추가한다

 

인터셉터 설정 생성 예시

@Configuration
public class InterceptorConfiguration implements WebMvcConfigurer {

	// 의존성 주입
	@Autowired
	TestInterceptor testInterceptor;

	// 추상 메소드 오버라이딩 - addInterceptors(InterceptorRegistry registry)
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(testInterceptor)
				.addPathPatterns("Interceptor의 간섭을 받을 경로")
				.excludePathPatterns("Interceptor의 간섭을 받지 않을 경로");
	}
	
	
}

 

** 감시 경로의 와일드카드(*) 특징

/* 해당 end point(/)의 경로만 포함
/** 해당 end point(/)의 경로와 그 하위 경로를 모두 포함

ex) 와일드카드(*)와 Interceptor 간섭 여부

  Interceptor의 간섭 여부 (O / X)
  /member/* /member/**
/member/join O O
/member/login O O
/member X O
/member/ O O
/member/info/password X O

 

회원에 대한 Interceptor 생성

MemberInterceptor 생성

1) 로그인 상태인지 검사

  - HttpSession에서 loginId란 값이 있는지(null이 아닌지) 검사

2) 상태에 따라 통과/차단 설정

  - 로그인 상태(null이 아닐 경우)라면 통과(true)

  - 로그인 상태가 아니라면 강제 이동(redirect) 후 차단(false)

@Component
public class MemberInterceptor implements HandlerInterceptor {

	@Override
	public boolean preHandle(HttpServletRequest request, 	// 사용자 요청 정보
				HttpServletResponse response, 	// 사용자 응답 정보
				Object handler			// 실행될 Controller의 메소드 정보
				) throws Exception {
		
		// HttpServletRequest에서 HttpSession 반환
		HttpSession session = request.getSession();
		
		// Session에서 로그인 중인 회원의 아이디(loginId) 반환
		String loginId = (String) session.getAttribute("loginId");
		if(loginId != null) { // loginId가 있을 경우 로그인 상태
			return true;	// 통과
		}
		else { // 그렇지 않을 경우 비로그인 상태
			// 1) 로그인 페이지로 강제 이동(redirect)
			response.sendRedirect("/member/login");
			// 2) HTTP 상태 코드 중 미인증(401)을 반환
			//response.sendError(401);
			return false;	// 차단
		}
	}
}

 

InterceptorConfiguration에 MemberInterceptor의 설정 추가

- 회원가입과 로그인을 제외한 모든 페이지에서 MemberInterceptor 간섭을 받아

  로그인 상태일 경우 통과 / 비로그인 상태일 경우 차단

@Configuration
public class InterceptorConfiguration implements WebMvcConfigurer {

	// 의존성 주입
	@Autowired
	MemberInterceptor memberInterceptor;

	// 추상 메소드 오버라이딩 - addInterceptors(InterceptorRegistry registry)
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		
		// MemberInterceptor에 대한 설정
		registry.addInterceptor(memberInterceptor)
				.addPathPatterns(
						"/pocketmon/**",	// 포켓몬 전부
						"/music/detail",	// 음원 상세
						"/member/**"		// 회원 전체
						)
				.excludePathPatterns(
						"/member/join",		// 회원가입
						"/member/join_success",	// 회원가입 완료
						"/member/login"		// 로그인
						);
	}
}

 

관리자에 대한 Interceptor 생성

AdminInterceptor 생성

@Component
public class AdminInterceptor implements HandlerInterceptor {

	@Override
	public boolean preHandle(HttpServletRequest request, 	// 사용자 요청 정보
				HttpServletResponse response, 	// 사용자 응답 정보
				Object handler			// 실행될 Controller의 메소드 정보
				) throws Exception {
		
		// HttpServletRequest에서 HttpSession 반환
		HttpSession session = request.getSession();
		
		// Session에서 로그인 중인 회원의 등급(mg) 반환
		String mg = (String) session.getAttribute("mg");
		
		// mg가 존재하면서(로그인 상태) mg가 관리자인지
		boolean admin = mg != null && mg.equals("관리자");
		
		if(admin) { // mg가 관리자일 경우
			return true;	// 통과
		}
		else { // 그렇지 않을 경우 비로그인 상태
			// 1) 로그인 페이지로 강제 이동(redirect)
			response.sendRedirect("/member/login");
			// 2) HTTP 상태 코드 중 미인증(401)을 반환
			//response.sendError(401);
			return false;	// 차단
		}
	}
}

 

InterceptorConfiguration에 AdminInterceptor의 설정 추가

@Configuration
public class InterceptorConfiguration implements WebMvcConfigurer {

	// 의존성 주입
	@Autowired
	AdminInterceptor adminInterceptor;

	// 추상 메소드 오버라이딩 - addInterceptors(InterceptorRegistry registry)
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		
		// AdminInterceptor에 대한 설정
		registry.addInterceptor(adminInterceptor)
				.addPathPatterns(
						"/guestbook/edit",	// 방명록 수정
						"/guestbook/delete",	// 방명록 상세
						"/music/**",		// 음원 전체
						"/member/list",		// 회원 목록
						"/member/detail",	// 회원 상세
						"/member/delete",	// 회원 삭제
						"/member/edit*"		// 회원 정보 수정
						)
				.excludePathPatterns(
						"/music/list",		// 음원 목록
						"/music/detail"		// 음원 상세
						);
	}
}