ㅁ 회원서비스
- 회원서비스 개발 세팅
- 회원서비스_로그인
- 회원서비스_로그아웃
- 회원서비스_회원가입페이지로이동
- 회원서비스_회원가입요청
- 회원서비스_마이페이지요청
- 회원서비스_정보변경요청 <- 여기까지 했음.
- 회원서비스_비번변경요청
- 회원서비스_회원탈퇴요청
ㅁ /webApp/src/main/webapp/views/member의 myInfo.jsp를 킨다.
- 아래는 myInfo.jsp다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<div class="container p-3">
<!-- Header, Nav start -->
<%@ include file="/views/common/header.jsp" %>
<!-- Header, Nav end -->
<!-- Section start -->
<section class="row m-3" style="min-height: 500px">
<div class="container border p-5 m-4 rounded">
<h2 class="m-4">마이페이지</h2>
<form action="<%= contextPath %>/update.me" method="post" class="m-4">
<table class="table">
<tr>
<th>* 아이디</th>
<td><input type="text" class="form-control" placeholder="Enter Your ID" name="userId" value="<%= loginUser.getUserId() %>" readonly></td>
<td></td>
</tr>
<tr>
<th>* 이름</th>
<td><input type="text" class="form-control" placeholder="Enter Your Name" name="userName" value="<%= loginUser.getUserName() %>" required></td>
<td></td>
</tr>
<tr>
<th> 전화번호</th>
<td><input type="text" class="form-control" placeholder="Enter Your Phone (- include)" name="phone" value='<%= loginUser.getPhone() == null ? "" : loginUser.getPhone() %>'></td>
<td></td>
</tr>
<tr>
<th> 이메일</th>
<td><input type="text" class="form-control" placeholder="Enter Your Email (@ include)" name="email" value='<%= loginUser.getEmail() == null ? "" : loginUser.getEmail() %>'></td>
<td></td>
</tr>
<tr>
<th> 주소</th>
<td><input type="text" class="form-control" placeholder="Enter Your Address" name="address" value='<%= loginUser.getAddress() == null ? "" : loginUser.getAddress() %>'></td>
<td></td>
</tr>
<tr>
<th> 관심분야</th>
<td>
<input type="checkbox" name="interest" value="운동" id="sports">
<label for="sports">운동</label>
<input type="checkbox" name="interest" value="등산" id="climibing">
<label for="climibing">등산</label>
<input type="checkbox" name="interest" value="낚시" id="fishing">
<label for="fishing">낚시</label>
<input type="checkbox" name="interest" value="요리" id="cooking">
<label for="cooking">요리</label>
<input type="checkbox" name="interest" value="게임" id="game">
<label for="game">게임</label>
<input type="checkbox" name="interest" value="영화" id="movie">
<label for="movie">영화</label>
</td>
<script>
$(function() {
let interest = '<%=loginUser.getInterest() == null ? "" : loginUser.getInterest()%>';
// "" | "운동,등산"
$(":checkbox").each(function(idx, el) { // el : 순차적으로 접근되는 체크박스 요소
// el.value : 접근되는 체크박스의 value값
if(interest.indexOf(el.value) != -1){
$(el).prop("checked", true);
}
})
})
</script>
<td></td>
</tr>
</table>
<br>
<div align="center">
<button type="submit" class="btn btn-outline-primary btn-sm">정보변경</button>
<button type="button" class="btn btn-outline-warning btn-sm" data-toggle="modal" data-target="#changePwdModal">비밀번호변경</button>
<button type="button" class="btn btn-outline-danger btn-sm" data-toggle="modal" data-target="#resignModal">회원탈퇴</button>
</div>
</form>
</div>
</section>
<!-- Section end -->
<!-- Footer start -->
<%@ include file="/views/common/footer.jsp" %>
<!-- Footer end -->
</div>
<!-- 비밀번호 변경용 Modal -->
<div class="modal" id="changePwdModal">
<div class="modal-dialog">
<div class="modal-content">
<!-- Modal Header -->
<div class="modal-header">
<h4 class="modal-title">비밀번호 변경</h4>
<button type="button" class="close" data-dismiss="modal">×</button>
</div>
<!-- Modal body -->
<div class="modal-body">
<form action="<%= contextPath %>/updatePwd.me" method="post">
<input type="hidden" name="userId" value="<%= loginUser.getUserId() %>">
<table align="center">
<tr>
<th>* 현재 비밀번호</th>
<td><input type="password" class="form-control" name="userPwd" required></td>
</tr>
<tr>
<th>* 변경할 비밀번호</th>
<td><input type="password" class="form-control" name="updatePwd" required></td>
</tr>
<tr>
<th>* 변경할 비밀번호 재입력</th>
<td><input type="password" class="form-control" required></td>
</tr>
<tr>
<td colspan="2" style="text-align:center; padding-top: 10px;">
<button type="submit" class="btn btn-warning btn-sm">비밀번호 변경</button>
</td>
</tr>
</table>
</form>
</div>
</div>
</div>
</div>
<!-- 회원탈퇴용 Modal -->
<div class="modal" id="resignModal">
<div class="modal-dialog">
<div class="modal-content">
<!-- Modal Header -->
<div class="modal-header">
<h4 class="modal-title">회원탈퇴</h4>
<button type="button" class="close" data-dismiss="modal">×</button>
</div>
<!-- Modal body -->
<div class="modal-body">
<form action="<%= contextPath %>/delete.me" method="post">
<table align="center">
<tr>
<th colspan="2">
탈퇴 후 복구가 불가능합니다. <br>
정말로 탈퇴하시겠습니까?
</th>
</tr>
<tr>
<th>현재 비밀번호</th>
<td><input type="password" class="form-control" name="userPwd" required></td>
</tr>
<tr>
<td colspan="2" style="text-align:center; padding-top: 10px;">
<button type="submit" class="btn btn-danger btn-sm">회원탈퇴</button>
</td>
</tr>
</table>
</form>
</div>
</div>
</div>
</div>
</body>
</html>
- "정보변경" 버튼은 마이페이지 전체를 감싸고 있는 <form>을 통해 서버에 요청을 보낼 수 있도록 작성되어 있다.
- "비밀번호 변경" 버튼과 "회원탈퇴" 버튼은 각각의 모달 창을 띄우도록 되어 있으며,
모달 안에서 별도의 <form>을 사용해 서버에 요청을 보냅니다.
- form 요소 내에 hidden 타입의 input 요소가 있다.
- form은 "/updatePwd.me"라는 url mapping 값을 가지는 서블릿 클래스를 호출하고 있다.
("/update.me"는 이미 있어서 다르게 줬다)
- 비밀번호 변경 팝업창에는 본인이 맞는지 확인하기 위해 비밀번호를 입력받고, 변경할 비밀번호와 변경할 비밀번호를 재입력하게 해놨다.
- '변경할 비밀번호 재입력'은 값을 넘길 필요가 없다.
'변경할 비밀번호 재입력'을 둔 이유는 화면단에서 이 두 개가 일치하는지 비교할 목적으로 둔 것 뿐이다.
- '비밀번호 변경' 버튼을 누른다고 바로 요청이 되어서는 안된다.
두 개가 일치하는지, 비밀번호 형식이 우리 사이트에서 요구하는 형식이 맞는지 유효성 체크를 진행한 이후에 요청이 되어야 한다.
- 만약 두 비밀번호가 일치하지 않거나 우리 사이트에서 요구하는 형식이 아니라면 요청이 되어서는 안 된다.
- 비밀번호 변경용 Modal의 form에 method 속성의 값으로 "post"를,
action 속성의 값으로 "<%= contextPath %>/updatePwd.me"를 부여한다.
- 비밀번호 변경용 Modal에 있는 input 요소들('현재 비밀번호', '변경할 비밀번호')에 name 속성을 다 부여해준다.
'변경할 비밀번호 재입력'은 값을 넘기지 않을 것이라 name 속성을 주지 않았다.
ㅁ /webApp/src/main/java/db/mappers/member-mapper.xml에 비밀번호 변경 쿼리를 먼저 작성해 본다.
<?xml version="1.0" encoding="UTF-8"?>
<properties>
<entry key="loginMember">
SELECT
USER_NO
, USER_ID
, USER_PWD
, USER_NAME
, PHONE
, EMAIL
, ADDRESS
, INTEREST
, ENROLL_DATE
, MODIFY_DATE
, STATUS
FROM
MEMBER
WHERE
USER_ID = ?
AND USER_PWD = ?
AND STATUS IN ('A', 'U')
</entry>
<entry key="insertMember">
INSERT
INTO MEMBER
(
USER_NO
, USER_ID
, USER_PWD
, USER_NAME
, PHONE
, EMAIL
, ADDRESS
, INTEREST
)
VALUES
(
SEQ_UNO.NEXTVAL
, ?
, ?
, ?
, ?
, ?
, ?
, ?
)
</entry>
<entry key="updateMember">
UPDATE
MEMBER
SET
USER_NAME = ?
, PHONE = ?
, EMAIL = ?
, ADDRESS = ?
, INTEREST = ?
, MODIFY_DATE = SYSDATE
WHERE
USER_ID = ?
</entry>
<entry key="selectMemberById">
SELECT
USER_NO
, USER_ID
, USER_PWD
, USER_NAME
, PHONE
, EMAIL
, ADDRESS
, INTEREST
, ENROLL_DATE
, MODIFY_DATE
, STATUS
FROM
MEMBER
WHERE
USER_ID = ?
</entry>
<entry key="updateMemberPwd">
UPDATE
MEMBER
SET
USER_PWD = ?
, MODIFY_DATE = SYSDATE
WHERE
USER_ID = ?
AND USER_PWD = ?
</entry>
</properties>
- 비밀번호 변경 요청시 실행할 쿼리를 먼저 작성해본다.
쿼리를 먼저 생각하면 어떤 데이터가 필요한지 파악할 수 있다.
- USER_ID는 PK는 아니지만 UNIQUE 제약조건이 설정되어 있으므로 식별자의 역할을 할 수 있다.
USER_NO을 써서 조건을 줘도 된다. 뭐가 됐든 그 회원만을 찾을 수 있게끔 조건을 작성하면 된다.
- 그리고 현재 본인이 맞는지 확인 차원에서 USER_PWD도 조건에 추가했다.
- 이 UPDATE 쿼리문을 실행하기 위해서 '변경할 비밀번호'가 필요하고, 또 'USER_ID'와 'USER_PWD'가 필요하다.
현재 myInfo.jsp의 비밀번호 변경 모달을 보면 '현재 비밀번호'와 '변경할 비밀번호'는 이미 넘어가게끔 되어있지만
회원의 아이디는 현재 요청시 넘어가고 있지 않다.
- <input type="hidden" name="userId" value="<%= loginUser.getUserId() %>">
회원 아이디를 몰래 넘겨주고 싶다면(화면에 노출시키지 않고) 몰래 숨겨서 데이터를 넘기고자 한다면
form 요소 내에 hidden 타입의 input 요소를 작성할 수 있다.
ㅁ /webApp/src/main/java/com/br/web/member/controller에 MemberPwdUpdateController.java 서블릿 클래스를만든다.
package com.br.web.member.controller;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.br.web.member.model.service.MemberService;
import com.br.web.member.model.vo.Member;
@WebServlet("/updatePwd.me")
public class MemberPwdUpdateController extends HttpServlet {
private static final long serialVersionUID = 1L;
public MemberPwdUpdateController() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 요청
/*
String userId = request.getParameter("userId"); // 몰래 숨겨서 넘긴 데이터
String userPwd = request.getParameter("userPwd");
String updatePwd = request.getParameter("updatePwd");
Map<String, String> map = new HashMap<>();
map.put("userId", userId);
map.put("userPwd", userPwd);
map.put("updatePwd", updatePwd);
*/
Map<String, String> map = new HashMap<>();
map.put("userId", request.getParameter("userId"));
map.put("userPwd", request.getParameter("userPwd"));
map.put("updatePwd", request.getParameter("updatePwd"));
Member updateMem = new MemberService().updateMemberPwd(map);
// 2. 응답
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
- 넘길 데이터가 3개밖에 없긴 한데 어딘가에 담아서 넘기려고 한다.
Member 객체에 updatePwd(변경할 비밀번호)를 담을 필드는 없어서 Map 객체를 이용해서 데이터를 담아서 service로 넘긴다.
key, value 모두 String으로 담는다. 1회용 vo 객체 같은 트낌이다.
- Map은 인터페이스라 new Map()할 수는 없다.
ㅁ /webApp/src/main/java/com/br/web/member/model/service의 MemberService.java에 updateMemberPwd() 메소드를 만든다.
package com.br.web.member.model.service;
import static com.br.web.common.template.JDBCTemplate.close;
import static com.br.web.common.template.JDBCTemplate.commit;
import static com.br.web.common.template.JDBCTemplate.getConnection;
import static com.br.web.common.template.JDBCTemplate.rollback;
import java.sql.Connection;
import java.util.Map;
import com.br.web.member.model.dao.MemberDao;
import com.br.web.member.model.vo.Member;
public class MemberService {
private MemberDao mDao = new MemberDao();
public Member loginMember(String userId, String userPwd) {
Connection conn = getConnection();
Member loginUser = mDao.loginMember(conn, userId, userPwd);
close(conn);
return loginUser;
}
public int insertMember(Member m) {
Connection conn = getConnection();
int result = mDao.insertMember(conn, m);
if(result > 0) {
commit(conn);
}else {
rollback(conn);
}
close(conn);
return result;
}
public Member updateMember(Member m) {
Connection conn = getConnection();
// 1. 회원정보 변경 (update)
int result = mDao.updateMember(conn, m);
Member updateMem = null;
if(result > 0) {
commit(conn);
// 2. 갱신된 회원 조회 (select)
updateMem = mDao.selectMemberById(conn, m.getUserId());
}else {
rollback(conn);
}
close(conn);
return updateMem; // null(정보변경실패) | 갱신된회원객체(정보변경성공)
}
public Member updateMemberPwd(Map<String, String> map) {
Connection conn = getConnection();
// 1. 비밀번호 변경 (update)
int result = mDao.updateMemberPwd(conn, map);
Member updateMem = null;
if(result > 0) {
commit(conn);
// 2. 갱신된 회원 조회 (select)
updateMem = mDao.selectMemberById(conn, map.get("userId"));
}else {
rollback(conn);
}
close(conn);
return updateMem;
}
}
- updateMemberPwd() 메소드를 만든다.
- 이 서비스 클래스의 한 메소드에서 비밀번호를 변경(update)하고 갱신된 회원 객체를 다시 조회(select)해 온다.
- dao 클래스와 member-mapper.xml에 이미 selectMemberById는 있다. 재사용 한다.
ㅁ /webApp/src/main/java/com/br/web/member/model/dao의 MemberDao.java에 updateMemberPwd() 메소드를 만든다.
package com.br.web.member.model.dao;
import static com.br.web.common.template.JDBCTemplate.close;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import java.util.Properties;
import com.br.web.member.model.vo.Member;
public class MemberDao {
private Properties prop = new Properties();
public MemberDao() {
String filePath = MemberDao.class.getResource("/db/mappers/member-mapper.xml").getPath();
try {
prop.loadFromXML(new FileInputStream(filePath));
} catch (IOException e) {
e.printStackTrace();
}
}
public Member loginMember(Connection conn, String userId, String userPwd) {
// select문 => ResultSet (한 행, 한 회원) => Member객체
Member m = null;
PreparedStatement pstmt = null;
ResultSet rset = null;
String sql = prop.getProperty("loginMember");
try {
pstmt = conn.prepareStatement(sql); // 미완성된 sql문
pstmt.setString(1, userId);
pstmt.setString(2, userPwd);
rset = pstmt.executeQuery();
if(rset.next()) {
m = new Member(rset.getInt("USER_NO")
, rset.getString("user_id")
, rset.getString("user_pwd")
, rset.getString("user_name")
, rset.getString("phone")
, rset.getString("email")
, rset.getString("address")
, rset.getString("interest")
, rset.getDate("enroll_date")
, rset.getDate("modify_date")
, rset.getString("status"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(rset);
close(pstmt);
}
return m;
}
public int insertMember(Connection conn, Member m) {
// insert => 처리된 행 수
int result = 0;
PreparedStatement pstmt = null;
String sql = prop.getProperty("insertMember");
try {
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, m.getUserId());
pstmt.setString(2, m.getUserPwd());
pstmt.setString(3, m.getUserName());
pstmt.setString(4, m.getPhone());
pstmt.setString(5, m.getEmail());
pstmt.setString(6, m.getAddress());
pstmt.setString(7, m.getInterest());
result = pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(pstmt);
}
return result;
}
public int updateMember(Connection conn, Member m) {
// update문 => 처리된 행수
int result = 0;
PreparedStatement pstmt = null;
String sql = prop.getProperty("updateMember");
try {
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, m.getUserName());
pstmt.setString(2, m.getPhone());
pstmt.setString(3, m.getEmail());
pstmt.setString(4, m.getAddress());
pstmt.setString(5, m.getInterest());
pstmt.setString(6, m.getUserId());
result = pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(pstmt);
}
return result;
}
public Member selectMemberById(Connection conn, String userId) {
// select => ResultSet (한행, 한회원) => Member
Member m = null;
PreparedStatement pstmt = null;
ResultSet rset = null;
String sql = prop.getProperty("selectMemberById");
try {
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, userId);
rset = pstmt.executeQuery();
if(rset.next()) {
m = new Member(rset.getInt("USER_NO")
, rset.getString("user_id")
, rset.getString("user_pwd")
, rset.getString("user_name")
, rset.getString("phone")
, rset.getString("email")
, rset.getString("address")
, rset.getString("interest")
, rset.getDate("enroll_date")
, rset.getDate("modify_date")
, rset.getString("status"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(rset);
close(pstmt);
}
return m;
}
public int updateMemberPwd(Connection conn, Map<String, String> map) {
// update => 처리된 행수
int result = 0;
PreparedStatement pstmt = null;
String sql = prop.getProperty("updateMemberPwd");
try {
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, map.get("updatePwd"));
pstmt.setString(2, map.get("userId"));
pstmt.setString(3, map.get("userPwd"));
result = pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(pstmt);
}
return result;
}
}
- 실행할 쿼리는 이미 만들어 놨었다.
ㅁ MemberPwdUpdateController.java 서블릿 클래스를 완성시킨다.
package com.br.web.member.controller;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.br.web.member.model.service.MemberService;
import com.br.web.member.model.vo.Member;
@WebServlet("/updatePwd.me")
public class MemberPwdUpdateController extends HttpServlet {
private static final long serialVersionUID = 1L;
public MemberPwdUpdateController() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 요청
/*
String userId = request.getParameter("userId"); // 몰래 숨겨서 넘긴 데이터
String userPwd = request.getParameter("userPwd");
String updatePwd = request.getParameter("updatePwd");
Map<String, String> map = new HashMap<>();
map.put("userId", userId);
map.put("userPwd", userPwd);
map.put("updatePwd", updatePwd);
*/
Map<String, String> map = new HashMap<>();
map.put("userId", request.getParameter("userId"));
map.put("userPwd", request.getParameter("userPwd"));
map.put("updatePwd", request.getParameter("updatePwd"));
Member updateMem = new MemberService().updateMemberPwd(map);
// 2. 응답
HttpSession session = request.getSession();
if(updateMem == null) { // 실패 (비밀번호를 잘못 입력한 경우)
// 응답페이지 : 마이페이지
// 응답데이터 : "비밀번호 변경 실패" alert 메세지
session.setAttribute("alertMsg", "비밀번호 변경 실패");
}else { // 성공
session.setAttribute("loginUser", updateMem); // session에 담겨있는 회원 객체 갱신
// 응답페이지 : 마이페이지
// 응답데이터 : "성공적으로 비밀번호가 변경되었습니다." alert 메세지
session.setAttribute("alertMsg", "성공적으로 비밀번호가 변경되었습니다.");
}
// 성공이든 실패든 /web/myinfo.me url 재요청 => 마이페이지로 포워딩
response.sendRedirect(request.getContextPath() + "/myinfo.me");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
- 비밀번호 변경에 실패해도 에러페이지가 아닌 마이페이지를 띄우고 alert 메세지를 띄울 예정이다.
- 비밀번호 변경 실패든 성공이든 redirect로 마이페이지를 띄운다. 응답 데이터를 session에 담는다.
- session.setAttribute("loginUser", updateMem);으로 session에 담겨있는 회원 객체를 갱신한다.
ㅁ (참고) header.jsp
<%@ page import="com.br.web.member.model.vo.Member" %>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!-- Bootstrap 사용을 위한 CDN -->
<!-- ------------------------- -->
<style>
header{height: 150px}
header a{color:black;}
</style>
<%
String contextPath = request.getContextPath(); // "/web"
Member loginUser = (Member)session.getAttribute("loginUser");
// 해당 구문이 실행되는 시점
// 로그인 요청 전 페이지 로드시 : null
// 로그인 성공 후 페이지 로드시 : 조회된 데이터가 담겨있는 Member객체
String alertMsg = (String)session.getAttribute("alertMsg");
// 해당 구문이 실행되는 시점
// 특정 서비스 요청 전 페이지 로드시 : null
// 특정 서비스 요청 성공 후 페이지 로드시 : alert로 띄워줄 메세지
%>
<% if(alertMsg != null) { %>
<script>
alert('<%=alertMsg%>');
</script>
<% session.removeAttribute("alertMsg"); } %>
<header class="row m-3">
<div class="col-3 d-flex justify-content-center align-items-center">
<a href="<%=contextPath%>"><img src="<%= contextPath %>/assets/image/goodee_logo.png"></a>
</div>
<div class="col-6"></div>
<div class="col-3 d-flex justify-content-center align-items-center">
<% if(loginUser == null) { %>
<!-- case1. 로그인전 -->
<form action="<%= contextPath %>/login.me" method="post">
<table>
<tr>
<th>ID</th>
<td><input type="text" name="userId" class="form-control form-control-sm" placeholder="Enter Your ID" required></td>
</tr>
<tr>
<th>PWD</th>
<td><input type="password" name="userPwd" class="form-control form-control-sm" placeholder="Enter Your PWD" required></td>
</tr>
<tr>
<td colspan="2" align="center">
<button type="submit" class="btn btn-secondary btn-sm">로그인</button>
<button type="button" class="btn btn-secondary btn-sm" id="signup-btn">회원가입</button>
<script>
document.getElementById("signup-btn").addEventListener("click", () => {
location.href = "<%=contextPath%>/signup.me";
})
</script>
</td>
</tr>
</table>
</form>
<% }else { %>
<!-- case2. 로그인후 -->
<div>
<b><%= loginUser.getUserName() %></b>님 환영합니다. <br><br>
<a href="<%=contextPath%>/myinfo.me">마이페이지</a>
<a href="<%=contextPath%>/logout.me">로그아웃</a>
</div>
<% } %>
</div>
</header>
<nav class="navbar m-3 navbar-expand-sm bg-dark navbar-dark d-flex justify-content-center">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="<%=contextPath%>">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="<%= contextPath %>/list.no">공지사항</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">일반게시판</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">사진게시판</a>
</li>
</ul>
</nav>
=========================================================================================
ㅁ 마이페이지 회원탈퇴 기능 구현
- 아래는 /webApp/src/main/webapp/views/member/myInfo.jsp이다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<div class="container p-3">
<!-- Header, Nav start -->
<%@ include file="/views/common/header.jsp" %>
<!-- Header, Nav end -->
<!-- Section start -->
<section class="row m-3" style="min-height: 500px">
<div class="container border p-5 m-4 rounded">
<h2 class="m-4">마이페이지</h2>
<form action="<%= contextPath %>/update.me" method="post" class="m-4">
<table class="table">
<tr>
<th>* 아이디</th>
<td><input type="text" class="form-control" placeholder="Enter Your ID" name="userId" value="<%= loginUser.getUserId() %>" readonly></td>
<td></td>
</tr>
<tr>
<th>* 이름</th>
<td><input type="text" class="form-control" placeholder="Enter Your Name" name="userName" value="<%= loginUser.getUserName() %>" required></td>
<td></td>
</tr>
<tr>
<th> 전화번호</th>
<td><input type="text" class="form-control" placeholder="Enter Your Phone (- include)" name="phone" value='<%= loginUser.getPhone() == null ? "" : loginUser.getPhone() %>'></td>
<td></td>
</tr>
<tr>
<th> 이메일</th>
<td><input type="text" class="form-control" placeholder="Enter Your Email (@ include)" name="email" value='<%= loginUser.getEmail() == null ? "" : loginUser.getEmail() %>'></td>
<td></td>
</tr>
<tr>
<th> 주소</th>
<td><input type="text" class="form-control" placeholder="Enter Your Address" name="address" value='<%= loginUser.getAddress() == null ? "" : loginUser.getAddress() %>'></td>
<td></td>
</tr>
<tr>
<th> 관심분야</th>
<td>
<input type="checkbox" name="interest" value="운동" id="sports">
<label for="sports">운동</label>
<input type="checkbox" name="interest" value="등산" id="climibing">
<label for="climibing">등산</label>
<input type="checkbox" name="interest" value="낚시" id="fishing">
<label for="fishing">낚시</label>
<input type="checkbox" name="interest" value="요리" id="cooking">
<label for="cooking">요리</label>
<input type="checkbox" name="interest" value="게임" id="game">
<label for="game">게임</label>
<input type="checkbox" name="interest" value="영화" id="movie">
<label for="movie">영화</label>
</td>
<script>
$(function() {
let interest = '<%=loginUser.getInterest() == null ? "" : loginUser.getInterest()%>';
// "" | "운동,등산"
$(":checkbox").each(function(idx, el) { // el : 순차적으로 접근되는 체크박스 요소
// el.value : 접근되는 체크박스의 value값
if(interest.indexOf(el.value) != -1){
$(el).prop("checked", true);
}
})
})
</script>
<td></td>
</tr>
</table>
<br>
<div align="center">
<button type="submit" class="btn btn-outline-primary btn-sm">정보변경</button>
<button type="button" class="btn btn-outline-warning btn-sm" data-toggle="modal" data-target="#changePwdModal">비밀번호변경</button>
<button type="button" class="btn btn-outline-danger btn-sm" data-toggle="modal" data-target="#resignModal">회원탈퇴</button>
</div>
</form>
</div>
</section>
<!-- Section end -->
<!-- Footer start -->
<%@ include file="/views/common/footer.jsp" %>
<!-- Footer end -->
</div>
<!-- 비밀번호 변경용 Modal -->
<div class="modal" id="changePwdModal">
<div class="modal-dialog">
<div class="modal-content">
<!-- Modal Header -->
<div class="modal-header">
<h4 class="modal-title">비밀번호 변경</h4>
<button type="button" class="close" data-dismiss="modal">×</button>
</div>
<!-- Modal body -->
<div class="modal-body">
<form action="<%= contextPath %>/updatePwd.me" method="post">
<input type="hidden" name="userId" value="<%= loginUser.getUserId() %>">
<table align="center">
<tr>
<th>* 현재 비밀번호</th>
<td><input type="password" class="form-control" name="userPwd" required></td>
</tr>
<tr>
<th>* 변경할 비밀번호</th>
<td><input type="password" class="form-control" name="updatePwd" required></td>
</tr>
<tr>
<th>* 변경할 비밀번호 재입력</th>
<td><input type="password" class="form-control" required></td>
</tr>
<tr>
<td colspan="2" style="text-align:center; padding-top: 10px;">
<button type="submit" class="btn btn-warning btn-sm">비밀번호 변경</button>
</td>
</tr>
</table>
</form>
</div>
</div>
</div>
</div>
<!-- 회원탈퇴용 Modal -->
<div class="modal" id="resignModal">
<div class="modal-dialog">
<div class="modal-content">
<!-- Modal Header -->
<div class="modal-header">
<h4 class="modal-title">회원탈퇴</h4>
<button type="button" class="close" data-dismiss="modal">×</button>
</div>
<!-- Modal body -->
<div class="modal-body">
<form action="<%= contextPath %>/delete.me" method="post">
<table align="center">
<tr>
<th colspan="2">
탈퇴 후 복구가 불가능합니다. <br>
정말로 탈퇴하시겠습니까?
</th>
</tr>
<tr>
<th>현재 비밀번호</th>
<td><input type="password" class="form-control" name="userPwd" required></td>
</tr>
<tr>
<td colspan="2" style="text-align:center; padding-top: 10px;">
<button type="submit" class="btn btn-danger btn-sm">회원탈퇴</button>
</td>
</tr>
</table>
</form>
</div>
</div>
</div>
</div>
</body>
</html>
- 회원번호 탈퇴용 Modal에도 사용자에게서 입력값을 받고 요청을 보내는 form이 있다.
그 form에 method 속성을 "post", action 속성의 값으로 "<%= contextPath %>/delete.me"를 부여한다.
- 비번 변경 요청 때처럼 form 요소 안에 hidden 타입인 input 요소를 둬서 userId를 숨겨서 값을 넘겨도 되지만,
이번엔 다른 방법을 써본다. 일단은 현재 비밀번호만 넘긴다.
ㅁ 회원번호 탈퇴 기능에 필요한 쿼리를 미리 작성해본다.
- 아래는 member-mapper.xml이다.
<?xml version="1.0" encoding="UTF-8"?>
<properties>
<entry key="loginMember">
SELECT
USER_NO
, USER_ID
, USER_PWD
, USER_NAME
, PHONE
, EMAIL
, ADDRESS
, INTEREST
, ENROLL_DATE
, MODIFY_DATE
, STATUS
FROM
MEMBER
WHERE
USER_ID = ?
AND USER_PWD = ?
AND STATUS IN ('A', 'U')
</entry>
<entry key="insertMember">
INSERT
INTO MEMBER
(
USER_NO
, USER_ID
, USER_PWD
, USER_NAME
, PHONE
, EMAIL
, ADDRESS
, INTEREST
)
VALUES
(
SEQ_UNO.NEXTVAL
, ?
, ?
, ?
, ?
, ?
, ?
, ?
)
</entry>
<entry key="updateMember">
UPDATE
MEMBER
SET
USER_NAME = ?
, PHONE = ?
, EMAIL = ?
, ADDRESS = ?
, INTEREST = ?
, MODIFY_DATE = SYSDATE
WHERE
USER_ID = ?
</entry>
<entry key="selectMemberById">
SELECT
USER_NO
, USER_ID
, USER_PWD
, USER_NAME
, PHONE
, EMAIL
, ADDRESS
, INTEREST
, ENROLL_DATE
, MODIFY_DATE
, STATUS
FROM
MEMBER
WHERE
USER_ID = ?
</entry>
<entry key="updateMemberPwd">
UPDATE
MEMBER
SET
USER_PWD = ?
, MODIFY_DATE = SYSDATE
WHERE
USER_ID = ?
AND USER_PWD = ?
</entry>
<entry key="deleteMember">
UPDATE
MEMBER
SET
STATUS = 'R'
, MODIFY_DATE = SYSDATE
WHERE
USER_ID = ?
AND USER_PWD = ?
</entry>
</properties>
- 회원 탈퇴요청을 보내도 delete 문으로 실제로 db에서 삭제하지 않는다.
탈퇴했어도 일정 기간동안 회원 데이터를 가지고 있는 경우가 많다.
- 탈퇴 기능도 결국 정보변경을 하는 거니까 MODIFY_DATE 컬럼도 SYSDATE로 기록되게끔 한다.
- 모든 회원을 전부 update 할 게 아니니까 회원 번호나 USER_ID를 조건에 써야 한다.
본인 확인을 위해 비밀번호도 조건에 넣는다.
ㅁ /webApp/src/main/java/com/br/web/member/controller에 MemberDeleteController.java라는 서블릿 클래스를 만든다.
package com.br.web.member.controller;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.br.web.member.model.service.MemberService;
import com.br.web.member.model.vo.Member;
@WebServlet("/delete.me")
public class MemberDeleteController extends HttpServlet {
private static final long serialVersionUID = 1L;
public MemberDeleteController() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 요청
String userPwd = request.getParameter("userPwd");
HttpSession session = request.getSession();
String userId = ((Member)session.getAttribute("loginUser")).getUserId(); // Member 클래스 import 필요.
int result = new MemberService().deleteMember(userId, userPwd);
// 2. 응답
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
- "/delete.me"라는 url mapping 값을 가지는 서블릿 클래스를 만든다.
- 서블릿 클래스(컨트롤러단)에서는 항상 1(요청)과 2(응답)에 대해 작성하면 된다.
- 회원 아이디는 굳이 화면에서 데이터를 넘기지 않아도 현재 로그인한 정보는 session에서 얼마든지 꺼내 쓸 수 있다.
- 회원탈퇴 요청을 보냈다는 것은 마이페이지에 들어왔단 소리고, 마이페이지에 들어와있다는 것은 로그인이 되어 있다는 뜻이다.
로그인이 되어 있다는 소리는 세션에 "loginUser"라는 key값으로 현재 로그인한 회원 정보가 Member 객체가 담겨있다는 소리다.
- 사실 form 안의 hidden 타입 input 요소로 데이터를 몰래 넘기는 것보다 백단으로 넘어와서 session에서 데이터를 꺼내는게 제일 좋다.
- 넘길 값이 겨우 2개 이므로 이번엔 Member 객체나 Map 객체에 담지 않고 그냥 넘긴다.
ㅁ MemberService.java에 deleteMember() 메소드를 만든다.
package com.br.web.member.model.service;
import static com.br.web.common.template.JDBCTemplate.close;
import static com.br.web.common.template.JDBCTemplate.commit;
import static com.br.web.common.template.JDBCTemplate.getConnection;
import static com.br.web.common.template.JDBCTemplate.rollback;
import java.sql.Connection;
import java.util.Map;
import com.br.web.member.model.dao.MemberDao;
import com.br.web.member.model.vo.Member;
public class MemberService {
private MemberDao mDao = new MemberDao();
public Member loginMember(String userId, String userPwd) {
Connection conn = getConnection();
Member loginUser = mDao.loginMember(conn, userId, userPwd);
close(conn);
return loginUser;
}
public int insertMember(Member m) {
Connection conn = getConnection();
int result = mDao.insertMember(conn, m);
if(result > 0) {
commit(conn);
}else {
rollback(conn);
}
close(conn);
return result;
}
public Member updateMember(Member m) {
Connection conn = getConnection();
// 1. 회원정보 변경 (update)
int result = mDao.updateMember(conn, m);
Member updateMem = null;
if(result > 0) {
commit(conn);
// 2. 갱신된 회원 조회 (select)
updateMem = mDao.selectMemberById(conn, m.getUserId());
}else {
rollback(conn);
}
close(conn);
return updateMem; // null(정보변경실패) | 갱신된회원객체(정보변경성공)
}
public Member updateMemberPwd(Map<String, String> map) {
Connection conn = getConnection();
// 1. 비밀번호 변경 (update)
int result = mDao.updateMemberPwd(conn, map);
Member updateMem = null;
if(result > 0) {
commit(conn);
// 2. 갱신된 회원 조회 (select)
updateMem = mDao.selectMemberById(conn, map.get("userId"));
}else {
rollback(conn);
}
close(conn);
return updateMem;
}
public int deleteMember(String userId, String userPwd) {
Connection conn = getConnection();
int result = mDao.deleteMember(conn, userId, userPwd);
if(result > 0) {
commit(conn);
}else {
rollback(conn);
}
close(conn);
return result;
}
}
- 서비스의 deleteMember 메소드에서 update 쿼리문 하나만 실행한다. select도 안한다.
ㅁ MemberDao.java에 deleteMember() 메소드를 만든다.
package com.br.web.member.model.dao;
import static com.br.web.common.template.JDBCTemplate.close;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import java.util.Properties;
import com.br.web.member.model.vo.Member;
public class MemberDao {
private Properties prop = new Properties();
public MemberDao() {
String filePath = MemberDao.class.getResource("/db/mappers/member-mapper.xml").getPath();
try {
prop.loadFromXML(new FileInputStream(filePath));
} catch (IOException e) {
e.printStackTrace();
}
}
public Member loginMember(Connection conn, String userId, String userPwd) {
// select문 => ResultSet (한 행, 한 회원) => Member객체
Member m = null;
PreparedStatement pstmt = null;
ResultSet rset = null;
String sql = prop.getProperty("loginMember");
try {
pstmt = conn.prepareStatement(sql); // 미완성된 sql문
pstmt.setString(1, userId);
pstmt.setString(2, userPwd);
rset = pstmt.executeQuery();
if(rset.next()) {
m = new Member(rset.getInt("USER_NO")
, rset.getString("user_id")
, rset.getString("user_pwd")
, rset.getString("user_name")
, rset.getString("phone")
, rset.getString("email")
, rset.getString("address")
, rset.getString("interest")
, rset.getDate("enroll_date")
, rset.getDate("modify_date")
, rset.getString("status"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(rset);
close(pstmt);
}
return m;
}
public int insertMember(Connection conn, Member m) {
// insert => 처리된 행 수
int result = 0;
PreparedStatement pstmt = null;
String sql = prop.getProperty("insertMember");
try {
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, m.getUserId());
pstmt.setString(2, m.getUserPwd());
pstmt.setString(3, m.getUserName());
pstmt.setString(4, m.getPhone());
pstmt.setString(5, m.getEmail());
pstmt.setString(6, m.getAddress());
pstmt.setString(7, m.getInterest());
result = pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(pstmt);
}
return result;
}
public int updateMember(Connection conn, Member m) {
// update문 => 처리된 행수
int result = 0;
PreparedStatement pstmt = null;
String sql = prop.getProperty("updateMember");
try {
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, m.getUserName());
pstmt.setString(2, m.getPhone());
pstmt.setString(3, m.getEmail());
pstmt.setString(4, m.getAddress());
pstmt.setString(5, m.getInterest());
pstmt.setString(6, m.getUserId());
result = pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(pstmt);
}
return result;
}
public Member selectMemberById(Connection conn, String userId) {
// select => ResultSet (한행, 한회원) => Member
Member m = null;
PreparedStatement pstmt = null;
ResultSet rset = null;
String sql = prop.getProperty("selectMemberById");
try {
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, userId);
rset = pstmt.executeQuery();
if(rset.next()) {
m = new Member(rset.getInt("USER_NO")
, rset.getString("user_id")
, rset.getString("user_pwd")
, rset.getString("user_name")
, rset.getString("phone")
, rset.getString("email")
, rset.getString("address")
, rset.getString("interest")
, rset.getDate("enroll_date")
, rset.getDate("modify_date")
, rset.getString("status"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(rset);
close(pstmt);
}
return m;
}
public int updateMemberPwd(Connection conn, Map<String, String> map) {
// update => 처리된 행수
int result = 0;
PreparedStatement pstmt = null;
String sql = prop.getProperty("updateMemberPwd");
try {
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, map.get("updatePwd"));
pstmt.setString(2, map.get("userId"));
pstmt.setString(3, map.get("userPwd"));
result = pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(pstmt);
}
return result;
}
public int deleteMember(Connection conn, String userId, String userPwd) {
// update문 => 처리된 행수
int result = 0;
PreparedStatement pstmt = null;
String sql = prop.getProperty("deleteMember");
try {
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, userId);
pstmt.setString(2, userPwd);
result = pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(pstmt);
}
return result;
}
}
ㅁ 서블릿 클래스 MemberDeleteController.java로 돌아와서 2(응답)에 대한 구문을 작성한다.
package com.br.web.member.controller;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.br.web.member.model.service.MemberService;
import com.br.web.member.model.vo.Member;
@WebServlet("/delete.me")
public class MemberDeleteController extends HttpServlet {
private static final long serialVersionUID = 1L;
public MemberDeleteController() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 요청
String userPwd = request.getParameter("userPwd");
HttpSession session = request.getSession();
String userId = ((Member)session.getAttribute("loginUser")).getUserId();
int result = new MemberService().deleteMember(userId, userPwd);
// 2. 응답
if(result > 0) { // 성공
// session에 담겨있는 회원데이터 지우기
session.removeAttribute("loginUser");
// 응답페이지 : 메인페이지
// 응답데이터 : "성공적으로 탈퇴" alert 메세지
session.setAttribute("alertMsg", "성공적으로 탈퇴되었습니다. 그동안 이용해 주셔서 감사합니다.");
response.sendRedirect(request.getContextPath());
}else { // 실패
// 응답페이지 : 마이페이지
// 응답데이터 : "회원탈퇴 실패" alert 메세지
session.setAttribute("alertMsg", "회원 탈퇴 실패");
response.sendRedirect(request.getContextPath() + "/myinfo.me");
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
- 응답은 항상 돌려받은 결과를 가지고 비교하면 된다.
- 성공적으로 탈퇴했다면 더이상 로그인이 되어서는 안 된다.
session.removeAttribute("loginUser");로 session의 "loginUser"라는 key값에 담긴 값을 null로 만든다.
이러면 메인페이지의 header 부분에 로그아웃된 화면이 보여진다.
- 메인페이지에서 header.jsp를 include하고 있다.
header.jsp에서는 session의 "loginUser"라는 key값에 담긴 값이 null인지 아닌지로 로그인 전 화면, 로그인 후 화면을 조건에 맞게 보여준다.
- 여기는 화면단이 아닌 백단이므로 header.jsp를 include한게 아니므로 "contextPath"라는 변수를 쓸 수 없다.
request.getContextPath()로 context path를 동적으로 가져온다.
ㅁ 탈퇴된 회원으로는 로그인이 진행되지 않는다.
- 데이터가 MEMBER 테이블에서 아예 지워진건 아니지만, 로그인하는 "loginMember" 쿼리가 STATUS 컬럼이 "A", "U"인 경우에만 로그인이 되게끔 작성해놨기 때문이다.
- 서버를 재시작하고, 메인페이지를 새로고침하고, 로그인 후 회원탈퇴 요청을 보내서 성공했다면,
sql developer로 MEMBER 테이블에 그 회원의 STATUS가 'R(탈퇴)'로 update까지 잘 됐는지 확인한다.