[SpringBoot] TIL 078 - 23.11.14

유진·2023년 11월 13일
0

SpringBoot : 마이페이지, 파일 업로드

sideMenu.html

<!-- 왼쪽 사이드 메뉴 -->
<section class="left-side">
    사이드메뉴

    <ul class="list-group">
        <li> <a th:href="@{/myPage/profile}">프로필</a> </li>
        
        <li> <a th:href="@{/myPage/info}">내 정보</a> </li>
        
        <li> <a th:href="@{/myPage/changePw}">비밀번호 변경</a> </li>
        
        <li> <a th:href="@{/myPage/secession}">회원 탈퇴</a> </li>
    </ul>
    
</section>

myPage-changePw.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>My Page</title>


    <link rel="stylesheet" th:href="@{/css/myPage/myPage-style.css}"> <!-- static 하위 폴더 기준 -->


</head>
<body>
    <main>
       
       <th:block th:replace="~{/common/header}"></th:block>

        
        <!-- 마이페이지 - 내 정보 -->
        <section class="myPage-content">
            
			<!-- 사이드메뉴 include -->
			<th:block th:replace="~{/myPage/sideMenu}"></th:block>


            <!-- 오른쪽 마이페이지 주요 내용 부분 -->
            <section class="myPage-main">

                <h1 class="myPage-title">비밀번호 변경</h1>
                <span class="myPage-subject">현재 비밀번호가 일치하는 경우 새 비밀번호로 변경할 수 있습니다.</span>

                <form th:action="@{changePw}" method="POST" name="myPageFrm" id="changePwFrm">

                    <div class="myPage-row">
                        <label>현재 비밀번호</label>
                        <input type="password" name="currentPw" id="currentPw" maxlength="30" >              
                    </div>

                    <div class="myPage-row">
                        <label>새 비밀번호</label>
                        <input type="password" name="newPw" id="newPw" maxlength="30">              
                    </div>

                    <div class="myPage-row">
                        <label>새 비밀번호 확인</label>
                        <input type="password" name="newPwConfirm" id="newPwConfirm" maxlength="30">              
                    </div>

                    <button class="myPage-submit">변경하기</button>

                </form>

            </section>

        </section>

    </main>

	<th:block th:replace="~{/common/footer}"></th:block>
	
	<script th:src="@{/js/myPage/myPage.js}"></script>
	
</body>
</html>

myPage-info.html

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>마이페이지</title>

    <link rel="stylesheet" th:href="@{/css/myPage/myPage-style.css}">
</head>
<body>
    <main>
        <th:block th:replace="~{/common/header}"></th:block>

        <section class="myPage-content">

			<!-- 사이드메뉴 include -->
            <th:block th:replace="~{/myPage/sideMenu}"></th:block>

            <section class="myPage-main">

                <h1 class="myPage-title">내 정보</h1>
                <span class="myPage-subject">원하는 회원 정보를 수정할 수 있습니다.</span>

                <form th:action="@{info}" method="POST" name="myPageFrm" id="updateInfo">

                    <div class="myPage-row">
                        <label>닉네임</label>
                        <input type="text" name="memberNickname"  maxlength="10"
                        	value="" id="memberNickname"
                        >
                    </div>

                    <div class="myPage-row">
                        <label>전화번호</label>
                        <input type="text" name="memberTel"  maxlength="11"
                        	value="" id="memberTel"
                        >
                    </div>

                    <div class="myPage-row info-title">
                        <span>주소</span>
                    </div>
                    
					
                    <div class="myPage-row info-address">
                        <input type="text" name="memberAddress" placeholder="우편번호"
                        	id="postcode" value=""
                        >
                        <button type="button" onclick="postCode()">검색</button>
                    </div>

                    <div class="myPage-row info-address">
                        <input type="text" name="memberAddress"  placeholder="도로명/지번 주소"
                        	id="address" value=""
                        >                
                    </div>

                    <div class="myPage-row info-address">
                        <input type="text" name="memberAddress"  placeholder="상세 주소"
                        	id="detailAddress" value=""
                        >                
                    </div>

                    <button class="myPage-submit">수정하기</button>
                </form>

            </section>

        </section>

    </main>
    <th:block th:replace="~{/common/footer}"></th:block>

    <!-- 다음 주소 api 추가 -->
    <script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
	<script>
	    function postCode() {
	        new daum.Postcode({
	            oncomplete: function(data) {
	                // 팝업에서 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분.
	
	                // 각 주소의 노출 규칙에 따라 주소를 조합한다.
	                // 내려오는 변수가 값이 없는 경우엔 공백('')값을 가지므로, 이를 참고하여 분기 한다.
	                var addr = ''; // 주소 변수
	
	                //사용자가 선택한 주소 타입에 따라 해당 주소 값을 가져온다.
	                if (data.userSelectedType === 'R') { // 사용자가 도로명 주소를 선택했을 경우
	                    addr = data.roadAddress;
	                } else { // 사용자가 지번 주소를 선택했을 경우(J)
	                    addr = data.jibunAddress;
	                }
	
	                // 우편번호와 주소 정보를 해당 필드에 넣는다.
	                document.getElementById('postcode').value = data.zonecode;
	                document.getElementById("address").value = addr;
	                // 커서를 상세주소 필드로 이동한다.
	                document.getElementById("detailAddress").focus();
	            }
	        }).open();
	    }
	</script>
    
   	<script th:src="@{/js/myPage/myPage.js}"></script>
</body>
</html>

myPage-profile.html

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>My Page</title>

    <link rel="stylesheet" th:href="@{/css/myPage/myPage-style.css}">
</head>
<body>
    <main>
       <th:block th:replace="~{/common/header}"></th:block>

        
        <!-- 마이페이지 - 내 정보 -->
        <section class="myPage-content">
            
			<!-- 사이드메뉴 include -->
            <th:block th:replace="~{/myPage/sideMenu}"></th:block>

            <!-- 오른쪽 마이페이지 주요 내용 부분 -->
            <section class="myPage-main">

                <h1 class="myPage-title">프로필</h1>
                <span class="myPage-subject">프로필 이미지를 변경할 수 있습니다.</span>
                
                <form th:action="@{profile}" method="POST" name="myPageFrm" id="profileFrm" enctype="multipart/form-data">

                    <div class="profile-image-area">
                    
                    	<!-- 프로필 이미지가 없으면 기본 이미지 -->
                    	<img th:src="@{/images/user.png}" id="profileImage">
                    	
                    	<!-- 프로필 이미지가 있으면 저장된 이미지 -->
                    	
                    </div>
                    
                    <span id="deleteImage">x</span>

                    <div class="profile-btn-area">
                        <label for="imageInput">이미지 선택</label>
                        <input type="file" name="profileImage" id="imageInput" accept="image/*">
                        <button>변경하기</button>
                    </div>
                    
                    <div class="myPage-row">
                        <label>이메일</label>
                        <span>로그인 회원 이메일</span>
                    </div>
                    
                    <div class="myPage-row">
                        <label>가입일</label>
                        <span>로그인 회원 가입일</span>
                    </div>
                    
                </form>

                

            </section>

        </section>

    </main>

	<th:block th:replace="~{/common/footer}"></th:block>
	
	<script th:src="@{/js/myPage/myPage.js}"></script>

</body>
</html>

myPage-secession.html

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>My Page</title>

    <link rel="stylesheet" th:href="@{/css/myPage/myPage-style.css}">

</head>
<body>
    <main>
       <th:block th:replace="~{/common/header}"></th:block>

        
        <!-- 마이페이지 - 내 정보 -->
        <section class="myPage-content">
            
			<!-- 사이드메뉴 include -->
            <th:block th:replace="~{/myPage/sideMenu}"></th:block>


            <!-- 오른쪽 마이페이지 주요 내용 부분 -->
            <section class="myPage-main">

                <h1 class="myPage-title">회원 탈퇴</h1>
                <span class="myPage-subject">현재 비밀번호가 일치하는 경우 탈퇴할 수 있습니다.</span>

                <form th:action="@{secession}" method="POST" name="myPageFrm" id="secessionFrm">

                    <div class="myPage-row">
                        <label>비밀번호</label>
                        <input type="password" name="memberPw" id="memberPw" maxlength="30">              
                    </div>

                    
                    <div class="myPage-row info-title">
                        <label>회원 탈퇴 약관</label>
                    </div>

                    <pre class="secession-terms">
제1조
이 약관은 샘플 약관입니다.

① 약관 내용 1

② 약관 내용 2

③ 약관 내용 3

④ 약관 내용 4


제2조
이 약관은 샘플 약관입니다.

① 약관 내용 1

② 약관 내용 2

③ 약관 내용 3

④ 약관 내용 4

                    </pre>

                    <div>
                        <input type="checkbox" name="agree" id="agree">
                        <label for="agree">위 약관에 동의합니다.</label>
                    </div>


                    <button class="myPage-submit">탈퇴</button>

                </form>

            </section>

        </section>

    </main>

	<th:block th:replace="~{/common/footer}"></th:block>
	
	<script th:src="@{/js/myPage/myPage.js}"></script>

</body>
</html>

MyPageController.java

package edu.kh.project.myPage;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttribute;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import edu.kh.project.member.model.dto.Member;
import jakarta.servlet.http.HttpSession;

@SessionAttributes({"loginMember"})
// 1) Model에 세팅된 값의 key와 {} 작성된 값이 일치하면 session scope로 이동
// 2) Session으로 올려둔 값을 해당 클래스에서 얻어와 사용 가능하게함
//	-> @SessionAttribute(key)로 사용
@RequestMapping("/myPage")
@Controller
public class MyPageController {
	
	@Autowired
	private MypageService service;
	
	// 내 정보 페이지로 이동
	@GetMapping("/info")
	public String info() {
		return "myPage/myPage-info";
	}
	
	// 프로필 페이지 이동
	@GetMapping("/profile")
	public String profile() {
		return "myPage/myPage-profile";
	}
	
	
	// 비밀번호 변경 페이지 이동
	@GetMapping("/changePw")
	public String changePw() {
		return "myPage/myPage-changePw";
	}
	
	// 비밀번호 변경
	@PostMapping("/changePw")
	public String changePwUpdate(@SessionAttribute("loginMember") Member loginMember,
								String newPw,
								RedirectAttributes ra,
								SessionStatus status) {
		
		int result = service.changePwUpdate(loginMember, newPw);
		
		String message = null;
		
		if(result > 0) {
			System.out.println("비밀번호 변경 성공");
			
			message = "비밀번호 변경 성공";
			
		} else {
			System.out.println("비밀번호 변경 실패");
			
			message = "비밀번호 변경 실패";
		}
		
		ra.addFlashAttribute("message", message);
		
		status.setComplete();
		
		return "redirect:/";
	}
	
	// 탈퇴 페이지 이동
	@GetMapping("/secession")
	public String secession() {
		return "myPage/myPage-secession";
	}
	
	// 회원 탈퇴
	@PostMapping("/memberDelete")
	public String memberDelete(@SessionAttribute("loginMember") Member loginMember,
								RedirectAttributes ra,
								SessionStatus status) {
		
		int result = service.memberDelete(loginMember);
		
		String message = null;
		
		if(result > 0) {
			System.out.println("회원 탈퇴 성공");
			
			message = "회원 탈퇴 성공";
			
		} else {
			System.out.println("회원 탈퇴 실패");
			
			message = "회원 탈퇴 실패";
			
		}
		
		ra.addFlashAttribute("message", message);
		
		status.setComplete();
	    
		return "redirect:/";
	}
	
	
	// 회원 정보 수정
	@PostMapping("/info")
	public String updateInfo(@SessionAttribute("loginMember") Member loginMember,
							Member updateMember,
							String[] memberAddress,
							RedirectAttributes ra) {
		
		/*
		 * @SessionAttribute("loginMember") Member loginMember
		 * 	: Session에서 얻어온 "loginMember"에 해당하는 객체를
		 * 	  매개변수 Member loginMember에 저장
		 * 
		 * 
		 * Member updateMember
		 * 	: 수정할 닉네임, 전화번호 담긴 커맨드 객체
		 * 
		 * 
		 * String[] memberAddress
		 * 	: name="memberAddress"인 input 3개의 값(주소)
		 * 
		 * 
		 * RedirectAttributes ra : 리다이렉트 시 값 전달용 객체
		 * 
		 * */
		
		// 주소 하나로 합치자 (우편번호^^^주소^^^상세주소)
		if(updateMember.getMemberAddress().equals(",,")) {
			updateMember.setMemberAddress(null);
		} else {
			// updateMember 에 주소문자열 세팅
			String addr = String.join("^^^", memberAddress);
			updateMember.setMemberAddress(addr);
		}
		
		// 로그인한 회원의 번호를 updateMember에 세팅
		updateMember.setMemberNo( loginMember.getMemberNo() );
		
		
		// DB 회원 정보 수정 (update) 서비스 호출
		int result = service.updateInfo(updateMember);
		
		String message = null;
		
		// 결과값으로 성공
		if(result > 0) {
			// -> 성공 시 Session에 로그인된 회원 정보도 수정(동기화)
			System.out.println("내 정보 수정 성공"); // 확인용 콘솔창
			
			loginMember.setMemberNickname( updateMember.getMemberNickname() );
			loginMember.setMemberTel( updateMember.getMemberTel() );
			loginMember.setMemberAddress( updateMember.getMemberAddress() );
			
			message = "회원 정보 수정 성공";
			
		} else {
			// 실패에 따른 처리
			System.out.println("내 정보 수정 실패"); // 확인용 콘솔창
			
			message = "회원 정보 수정 실패";
		}
		
		ra.addFlashAttribute("message", message);
		
		return "redirect:info"; // 상대경로 (절대경로로 작성하려면? /myPage/myPage-info)
	}
	
	
	/* MultipartFile : input type="file"로 제출된 파일을 저장한 객체
	 * 
	 * [ 제공하는 메서드 ]
	 * - transferTo() : 파일을 지정된 경로에 저장(메모리 -> HDD/SSD)
	 * - getOriginalFileName() : 파일 원본명
	 * - getSize() : 파일 크기
	 * 
	 * 
	 * */
	
	
	
	// 프로필 이미지 수정
	@PostMapping("/profile")
	public String updateProfile(
			@RequestParam("profileImage") MultipartFile profileImage // 업로드 파일
			, HttpSession session // 세션 객체
			, @SessionAttribute("loginMember") Member loginMember
			, RedirectAttributes ra // 리다이렉트 시 메세지 전달
			) throws Exception{
		
		// 웹 접근 경로 (webapp 기준)
		String webPath = "/resources/images/member/";
		
		// 실제로 이미지 파일이 저장되어야하는 서버컴퓨터 경로
		String filePath = session.getServletContext().getRealPath(webPath);
		// C:\workspace\7_Framework\boardProject\src\main\webapp\resources\images\member
		
		
		// 프로필 이미지 수정 서비스 호출
		int result = service.updateProfile(profileImage, webPath, filePath, loginMember);
		
		
		String message = null;
		if(result > 0) message = "프로필 이미지가 변경되었습니다";
		else			message = "프로필 변경 실패";
		
		ra.addFlashAttribute("message", message);
		
		return "redirect:profile";
	}
	
	
	
	// Ctrl + Shift + O(영어) -> import 할 수 있는 거 다 뜸! 한 번에 import 가능!

}


-> 사이드메뉴 클릭 시, 각자 맞는 페이지로 이동하면 성공!


myPage-info.html

1)

2)

  • 타임리프 로직처리 = js or BackEnd에서만!


myPage-info.html

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>마이페이지</title>

    <link rel="stylesheet" th:href="@{/css/myPage/myPage-style.css}">
</head>
<body>
    <main>
        <th:block th:replace="~{/common/header}"></th:block>

        <section class="myPage-content">

			<!-- 사이드메뉴 include -->
            <th:block th:replace="~{/myPage/sideMenu}"></th:block>

            <section class="myPage-main">

                <h1 class="myPage-title">내 정보</h1>
                <span class="myPage-subject">원하는 회원 정보를 수정할 수 있습니다.</span>

                <form th:action="@{info}" th:object="${session.loginMember}" method="POST" name="myPageFrm" id="updateInfo">

                    <div class="myPage-row">
                        <label>닉네임</label>
                        <input type="text" name="memberNickname"  maxlength="10"
                        	th:value="*{memberNickname}" id="memberNickname"
                        >
                    </div>

                    <div class="myPage-row">
                        <label>전화번호</label>
                        <input type="text" name="memberTel"  maxlength="11"
                        	th:value="*{memberTel}" id="memberTel"
                        >
                    </div>

                    <div class="myPage-row info-title">
                        <span>주소</span>
                    </div>
                    
					
                    <div class="myPage-row info-address">
                        <input type="text" name="memberAddress" placeholder="우편번호"
                        	id="postcode" value=""
                        >
                        <button type="button" onclick="postCode()">검색</button>
                    </div>

                    <div class="myPage-row info-address">
                        <input type="text" name="memberAddress"  placeholder="도로명/지번 주소"
                        	id="address" value=""
                        >                
                    </div>

                    <div class="myPage-row info-address">
                        <input type="text" name="memberAddress"  placeholder="상세 주소"
                        	id="detailAddress" value=""
                        >                
                    </div>

                    <button class="myPage-submit">수정하기</button>
                </form>

            </section>

        </section>

    </main>
    <th:block th:replace="~{/common/footer}"></th:block>

    <!-- 다음 주소 api 추가 -->
    <script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
	<script>
	    function postCode() {
	        new daum.Postcode({
	            oncomplete: function(data) {
	                // 팝업에서 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분.
	
	                // 각 주소의 노출 규칙에 따라 주소를 조합한다.
	                // 내려오는 변수가 값이 없는 경우엔 공백('')값을 가지므로, 이를 참고하여 분기 한다.
	                var addr = ''; // 주소 변수
	
	                //사용자가 선택한 주소 타입에 따라 해당 주소 값을 가져온다.
	                if (data.userSelectedType === 'R') { // 사용자가 도로명 주소를 선택했을 경우
	                    addr = data.roadAddress;
	                } else { // 사용자가 지번 주소를 선택했을 경우(J)
	                    addr = data.jibunAddress;
	                }
	
	                // 우편번호와 주소 정보를 해당 필드에 넣는다.
	                document.getElementById('postcode').value = data.zonecode;
	                document.getElementById("address").value = addr;
	                // 커서를 상세주소 필드로 이동한다.
	                document.getElementById("detailAddress").focus();
	            }
	        }).open();
	    }
	</script>
	
	<script th:inline="javascript">
		// th:inline="javascript"
		// - 타임리프를 script에서 사용 가능
		// - 타임리프에서 출력되는 값을 JS에 맞는 자료형으로 변경  // 타임리프도 결국엔 java..
		
		// Natural Template
		// 1) js 문법 에러 발생 X
		// 2) html 파일만 단독으로 열었을 때 발생하는 에러 방지
		const loginMember = /*[[${session.loginMember}]]*/ "로그인한 회원정보"; // -> 이 형식으로 작성하면 js에서 타임리프 쓸 때 컴파일 에러 안남!
		
		if(loginMember.memberAddress != null) {
			const arr = loginMember.memberAddress.split("^^^");
			
			document.querySelectorAll("input[name='memberAddress']").forEach( (item, i) => {
				item.value = arr[i];
			} )
		}
		
	</script>
    
   	<script th:src="@{/js/myPage/myPage.js}"></script>
</body>
</html>

MypageServiceImpl.java

package edu.kh.project.myPage.model.service;

import java.io.File;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import edu.kh.project.common.utility.Util;
import edu.kh.project.member.model.dto.Member;
import edu.kh.project.myPage.model.dao.MyPageMapper;

@Service
public class MyPageServiceImpl implements MyPageService{
	
	@Autowired
	private MyPageMapper mapper;
	
	@Autowired // BCryptPasswordEncoder 의존성 주입(DI)
	private BCryptPasswordEncoder bcrypt;
	

	// 회원 정보 수정 서비스
	@Transactional
	@Override
	public int updateInfo(Member updateMember) {
		return mapper.updateInfo(updateMember);
	}

	// 프로필 이미지 수정 서비스
	@Override
	public int updateProfile(MultipartFile profileImage, String webPath, String filePath, Member loginMember) throws Exception {
		
		// 프로필 이미지 변경 실패 대비
		String temp = loginMember.getProfileImage(); // 기존에 가지고 있던 이전 이미지 저장
		
		
		String rename = null; // 변경 이름 저장 변수
		
		if(profileImage.getSize() > 0) { // 업로드된 이미지가 있을 경우
			
			// 1) 파일 이름 변경
			rename = Util.fileRename(profileImage.getOriginalFilename());
			
			// 2) 바뀐 이름 loginMember에 세팅
			loginMember.setProfileImage(webPath + rename);
			
			
			
		} else { // 업로드된 이미지가 없는 경우 (x버튼) 
			
			loginMember.setProfileImage(null);
			
		}
		
		
		// 프로필 이미지 수정 DAO 메서드 호출
		int result = mapper.updateProfileImage(loginMember);
		
		
		if(result > 0) { // DB에 이미지 경로 업데이트 성공했다면
			
			// 업로드된 새 이미지가 있을 경우
			if(rename != null) {
				
				// 메모리에 임시 저장되어있는 파일을 서버에 진짜로 저장하는 것
				profileImage.transferTo(new File(filePath + rename));
			}
			
			
		} else { // 실패
			
			// 이전 이미지로 프로필 다시 세팅
			loginMember.setProfileImage(temp);
			
			
		}
		
		
		return result;
		
		
		
		
	}


	// 비밀번호 변경 서비스
	@Transactional(rollbackFor = Exception.class)
	@Override
	public int changePw(String currentPw, String newPw, int memberNo) {
		
		// 1. 현재 비밀번호, DB에 저장된 비밀번호 비교
		// 1) 회원번호가 일치하는 MEMBER 테이블 행의 MEMBER_PW 조회
		String encPw = mapper.selectEncPw(memberNo);
		
		// 2) bcrypt.matches(평문, 암호문) -> 같으면 true -> 이 때 비번 수정
		if(bcrypt.matches(currentPw, encPw)) {
		
			Member member = new Member();
			member.setMemberNo(memberNo);
			member.setMemberPw(bcrypt.encode(newPw));
			
			// 2. 비밀번호 변경(UPDATE DAO 호출) -> 결과 반환
			return mapper.changePw( member );
		}
		
		// 3) 비밀번호가 일치하지 않으면 0 반환
		return 0;
	}


	// 회원 탈퇴 서비스
	@Transactional(rollbackFor = Exception.class)
	@Override
	public int secession(String memberPw, int memberNo) {
		
		// 1. 회원 번호가 일치하는 회원의 비밀번호 조회
		String encPw = mapper.selectEncPw(memberNo);
		
		// 2.비밀번호가 일치하면 
		if(bcrypt.matches(memberPw, encPw)) {
			// MEMBER_DEL_FL -> 'Y'로 바꾸고 1 반환
			return mapper.secession(memberNo);
		}
		
		//  3. 비밀번호가 일치하지 않으면 -> 0 반환
		return 0;
	}

}

myPageDAO.java ---> mapper로 변경하여 쓸 예정!

MyPageMapper.java

package edu.kh.project.myPage.model.dao;

import org.apache.ibatis.annotations.Mapper;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import edu.kh.project.member.model.dto.Member;

@Mapper
public interface MyPageMapper {


	/** 회원 정보 수정 DAO
	 * @param updateMember
	 * @return result
	 */
	public int updateInfo(Member updateMember);

	
	/** 프로필 이미지 수정
	 * @param loginMember
	 * @return result
	 */
	public int updateProfileImage(Member loginMember);
	
	
	/** 회원 비밀번호 조회
	 * @param memberNo
	 * @return encPw
	 */
	public String selectEncPw(int memberNo);


	/** 비밀번호 변경
	 * @param newPw
	 * @param memberNo
	 * @return result
	 */
	public int changePw(Member member);


	/** 회원 탈퇴 DAO
	 * @param memberNo
	 * @return result
	 */
	public int secession(int memberNo);
}

파일 업로드

외부에 파일을 따로 둠!

MyPageController.java

package edu.kh.project.myPage.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttribute;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import edu.kh.project.member.model.dto.Member;
import edu.kh.project.myPage.model.service.MyPageService;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;

@SessionAttributes({"loginMember"})
// 1) Model에 세팅된 값의 key와 {} 작성된 값이 일치하면 session scope로 이동
// 2) Session으로 올려둔 값을 해당 클래스에서 얻어와 사용 가능하게함
//	-> @SessionAttribute(key)로 사용
@RequestMapping("/myPage")
@Controller
public class MyPageController {
	
	@Autowired
	private MyPageService service;
	
	
	// 내 정보 페이지로 이동
	@GetMapping("/info")
	public String info() {
		return "myPage/myPage-info";
	}
	
	// 프로필 페이지 이동
	@GetMapping("/profile")
	public String profile() {
		return "myPage/myPage-profile";
	}
	
	
	// 비밀번호 변경 페이지 이동
	@GetMapping("/changePw")
	public String changePw() {
		return "myPage/myPage-changePw";
	}
	
	// 탈퇴 페이지 이동
	@GetMapping("/secession")
	public String secession() {
		return "myPage/myPage-secession";
	}
	
	
	// 회원 정보 수정
	@PostMapping("/info")
	public String updateInfo(@SessionAttribute("loginMember") Member loginMember,
							Member updateMember,
							String[] memberAddress,
							RedirectAttributes ra) {
		
		/*
		 * @SessionAttribute("loginMember") Member loginMember
		 *  : Session에서 얻어온 "loginMember"에 해당하는 객체를
		 *    매개변수 Member loginMember에 저장
		 * 
		 * Member updateMember
		 * : 수정할 닉네임, 전화번호 담긴 커맨드 객체
		 * 
		 * 
		 * String[] memberAddress 
		 * : name="memberAddress"인 input 3개의 값(주소)
		 * 
		 * 
		 * RedirectAttributes ra : 리다이렉트 시 값 전달용 객체
		 * 
		 * */
		
		// 주소 하나로 합치자 (a^^^b^^^c)
		if(updateMember.getMemberAddress().equals(",,")) {
			updateMember.setMemberAddress(null);
		}else {
			// updateMember 에 주소문자열 세팅
			String addr = String.join("^^^", memberAddress);
			updateMember.setMemberAddress(addr);
		}
		
		// 로그인한 회원의 번호를 updateMember에 세팅
		updateMember.setMemberNo( loginMember.getMemberNo() );
		
		
		// DB 회원 정보 수정 (update) 서비스 호출
		int result = service.updateInfo(updateMember);
		
		String message = null;
		
		// 결과값으로 성공
		if(result > 0) {
			// -> 성공 시 Session에 로그인된 회원 정보도 수정(동기화)
			loginMember.setMemberNickname( updateMember.getMemberNickname() );
			loginMember.setMemberTel( updateMember.getMemberTel() );
			loginMember.setMemberAddress( updateMember.getMemberAddress() );
			
			message = "회원 정보 수정 성공";
			
			
		} else {
			// 실패에 따른 처리 

			message = "회원 정보 수정 실패";
			
		}
		
		ra.addFlashAttribute("message", message);
		
		return "redirect:info"; // 상대경로 (/myPage/info)
	}
	
	
	/* MultipartFile : input type="file"로 제출된 파일을 저장한 객체
	 * 
	 * [ 제공하는 메서드 ]
	 * - transferTo() : 파일을 지정된 경로에 저장(메모리 -> HDD/SSD) 
	 * - getOriginalFileName() : 파일 원본명
	 * - getSize() : 파일 크기
	 * 
	 * 
	 * */
	
	
	
	// 프로필 이미지 수정
	@PostMapping("/profile")
	public String updateProfile(
			@RequestParam("profileImage") MultipartFile profileImage // 업로드 파일
			, @SessionAttribute("loginMember") Member loginMember
			, RedirectAttributes ra // 리다이렉 시 메세지 전달
			) throws Exception{
		
		
		// 프로필 이미지 수정 서비스 호출
		int result = service.updateProfile(profileImage, loginMember);
		
		
		String message = null;
		if(result > 0) message = "프로필 이미지가 변경되었습니다";
		else			message = "프로필 변경 실패";
		
		ra.addFlashAttribute("message", message);
		
		return "redirect:profile";
	}
	
	
	

	// 비밀번호 변경
	@PostMapping("/changePw")
	public String changePw(String currentPw, String newPw
		,@SessionAttribute("loginMember") Member loginMember
		,RedirectAttributes ra) {
		
		// 로그인한 회원 번호(DB에서 어떤 회원을 조회, 수정하는지 알아야 되니까)
		int memberNo = loginMember.getMemberNo();
		
		// 비밀번호 변경 서비스 호출
		int result = service.changePw(currentPw, newPw, memberNo);
		
		String path = "redirect:";
		String message = null;
		
		if(result > 0) { // 변경 성공
			message = "비밀번호가 변경 되었습니다.";
			path += "info";
			
		}else { // 변경 실패
			message = "현재 비밀번호가 일치하지 않습니다.";
			path += "changePw";
		}
		
		ra.addFlashAttribute("message", message);
		
		return path;
	}
	
	// 회원 탈퇴
	@PostMapping("/secession")
	public String secession(String memberPw
			,@SessionAttribute("loginMember") Member loginMember
			,SessionStatus status
			,HttpServletResponse resp
			,RedirectAttributes ra) {
		
		// String memberPw : 입력한 비밀번호
		// SessionStatus status : 세션 관리 객체
		// HttpServletResponse resp : 서버 -> 클라이언트 응답하는 방법 제공 객체
		// RedirectAttributes ra : 리다이렉트 시 request로 값 전달하는 객체
		
		// 1. 로그인한 회원의 회원 번호 얻어오기
		// @SessionAttribute("loginMember") Member loginMember
		int memberNo = loginMember.getMemberNo();
		
		// 2. 회원 탈퇴 서비스 호출
		//	- 비밀번호가 일치하면 MEMBER_DEL_FL -> 'Y'로 바꾸고 1 반환
		//  - 비밀번호가 일치하지 않으면 -> 0 반환
		int result = service.secession(memberPw, memberNo);
		
		String path = "redirect:";
		String message = null;
		
		// 3. 탈퇴 성공 시
		if(result > 0) {
			// - message : 탈퇴 되었습니다
			message = "탈퇴 되었습니다";
			
			// - 메인 페이지로 리다이렉트
			path += "/";
			
			// - 로그아웃 
			status.setComplete();
			
			// + 쿠키 삭제
			Cookie cookie = new Cookie("saveId", ""); 
			// 같은 쿠기가 이미 존재하면 덮어쓰기된다
			
			cookie.setMaxAge(0); // 0초 생존 -> 삭제
			cookie.setPath("/"); // 요청 시 쿠기가 첨부되는 경로
			resp.addCookie(cookie); // 요청 객체를 통해서 클라이언트에게 전달
									// -> 클라이언트 컴퓨터에 파일로 생성
			
		}
		
		// 4. 탈퇴 실패 시
		else {
			// - message : 현재 비밀번호가 일치하지 않습니다
			message = "현재 비밀번호가 일치하지 않습니다";
			
			// - 회원 탈퇴 페이지로 리다이렉트
			path += "secession";
		}
		
		ra.addFlashAttribute("message",message);
		
		return path;
	}
	
}

config.properties

MyPageService.java

package edu.kh.project.myPage.model.service;

import org.springframework.web.multipart.MultipartFile;

import edu.kh.project.member.model.dto.Member;

public interface MyPageService {

	/** 회원 정보 수정 서비스
	 * @param updateMember
	 * @return result
	 */
	int updateInfo(Member updateMember);
	
	/** 프로필 이미지 수정 서비스
	 * @param profileImage
	 * @param webPath
	 * @param filePath
	 * @param loginMember
	 * @return result
	 */
	int updateProfile(MultipartFile profileImage, Member loginMember) throws Exception;

	
	/** 비밀번호 변경 서비스
	 * @param currentPw
	 * @param newPw
	 * @param memberNo
	 * @return result
	 */
	int changePw(String currentPw, String newPw, int memberNo);


	/** 회원 탈퇴 서비스
	 * @param memberPw
	 * @param memberNo
	 * @return result
	 */
	int secession(String memberPw, int memberNo);

}

MyPageServiceImpl.java

package edu.kh.project.myPage.model.service;

import java.io.File;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import edu.kh.project.common.utility.Util;
import edu.kh.project.member.model.dto.Member;
import edu.kh.project.myPage.model.dao.MyPageMapper;

@Service
@PropertySource("classpath:/config.properties") // classpath -------> src/main/resource
public class MyPageServiceImpl implements MyPageService{
	
	@Value("${my.member.webpath}") // @Value --------> 필드에서만 사용 가능
	private String webPath;
	
	@Value("${my.member.location}")
	private String filePath;
	
	
	@Autowired
	private MyPageMapper mapper;
	
	@Autowired // BCryptPasswordEncoder 의존성 주입(DI)
	private BCryptPasswordEncoder bcrypt;
	
	

	// 회원 정보 수정 서비스
	@Transactional
	@Override
	public int updateInfo(Member updateMember) {
		return mapper.updateInfo(updateMember);
	}

	// 프로필 이미지 수정 서비스
	@Override
	public int updateProfile(MultipartFile profileImage, Member loginMember) throws Exception {
		
		// 프로필 이미지 변경 실패 대비
		String temp = loginMember.getProfileImage(); // 기존에 가지고 있던 이전 이미지 저장
		
		
		String rename = null; // 변경 이름 저장 변수
		
		if(profileImage.getSize() > 0) { // 업로드된 이미지가 있을 경우
			
			// 1) 파일 이름 변경
			rename = Util.fileRename(profileImage.getOriginalFilename());
			
			// 2) 바뀐 이름 loginMember에 세팅
			loginMember.setProfileImage(webPath + rename);
			
			
			
		} else { // 업로드된 이미지가 없는 경우 (x버튼) 
			
			loginMember.setProfileImage(null);
			
		}
		
		
		// 프로필 이미지 수정 DAO 메서드 호출
		int result = mapper.updateProfileImage(loginMember);
		
		
		if(result > 0) { // DB에 이미지 경로 업데이트 성공했다면
			
			// 업로드된 새 이미지가 있을 경우
			if(rename != null) {
				
				// 메모리에 임시 저장되어있는 파일을 서버에 진짜로 저장하는 것
				profileImage.transferTo(new File(filePath + rename));
			}
			
			
		} else { // 실패
			
			// 이전 이미지로 프로필 다시 세팅
			loginMember.setProfileImage(temp);
			
			
		}
		
		
		return result;
		
		
		
		
	}


	// 비밀번호 변경 서비스
	@Transactional(rollbackFor = Exception.class)
	@Override
	public int changePw(String currentPw, String newPw, int memberNo) {
		
		// 1. 현재 비밀번호, DB에 저장된 비밀번호 비교
		// 1) 회원번호가 일치하는 MEMBER 테이블 행의 MEMBER_PW 조회
		String encPw = mapper.selectEncPw(memberNo);
		
		// 2) bcrypt.matches(평문, 암호문) -> 같으면 true -> 이 때 비번 수정
		if(bcrypt.matches(currentPw, encPw)) {
		
			Member member = new Member();
			member.setMemberNo(memberNo);
			member.setMemberPw(bcrypt.encode(newPw));
			
			// 2. 비밀번호 변경(UPDATE DAO 호출) -> 결과 반환
			return mapper.changePw( member );
		}
		
		// 3) 비밀번호가 일치하지 않으면 0 반환
		return 0;
	}


	// 회원 탈퇴 서비스
	@Transactional(rollbackFor = Exception.class)
	@Override
	public int secession(String memberPw, int memberNo) {
		
		// 1. 회원 번호가 일치하는 회원의 비밀번호 조회
		String encPw = mapper.selectEncPw(memberNo);
		
		// 2.비밀번호가 일치하면 
		if(bcrypt.matches(memberPw, encPw)) {
			// MEMBER_DEL_FL -> 'Y'로 바꾸고 1 반환
			return mapper.secession(memberNo);
		}
		
		//  3. 비밀번호가 일치하지 않으면 -> 0 반환
		return 0;
	}

}

FileUploadConfig.java

package edu.kh.project.common.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.MultipartConfigFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.util.unit.DataSize;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import jakarta.servlet.MultipartConfigElement;

@Configuration
@PropertySource("classpath:/config.properties")
public class FileUploadConfig implements WebMvcConfigurer{
		// WebMvcConfigurer : 스프링에서 웹 관련 요청/응답 모든 설정들을 할 수 있는 메서드를
		// 기본 제공해주는 인터페이스
	
	// 파일을 hdd(하드디스크)에 저장하기 전 임시로 가지고 있을 메모리 용량
	@Value("${spring.servlet.multipart.file-size-threshold}")
	private long fileSizethreshold;
	
	// 파일 1개 크기 제한
	@Value("${spring.servlet.multipart.max-file-size}")
	private long maxFileSize;
	
	// 요청당 파일 크기 제한
	@Value("${spring.servlet.multipart.max-request-size}")
	private long maxRequestSize;
	
	@Bean // 개발자가 수동으로 Bean 등록(생성은 개발자, 관리는 Spring)
	public MultipartConfigElement configElement() {
		
		MultipartConfigFactory factory = new MultipartConfigFactory();
		
		factory.setFileSizeThreshold(DataSize.ofBytes(fileSizethreshold)); // long형 값을 byte 크기로 나타냄
		
		factory.setMaxFileSize(DataSize.ofBytes(maxFileSize));
		
		factory.setMaxFileSize(DataSize.ofBytes(maxRequestSize));
		
		return factory.createMultipartConfig();
		
	}
	
	
	@Bean
	public MultipartResolver multipartResolver() {
		// MultipartResolver : 파일은 파일로, 텍스트는 텍스트로 자동 구분
		return new StandardServletMultipartResolver(); // 부트용 Resolver 사용!
	}


	// 웹에서 사용하는 자원을 다루는 방법을 설정
	@Override
	public void addResourceHandlers(ResourceHandlerRegistry registry) {
		
		//   /images/ 로 시작되는 요청
		String webPath = "/images/**"; //  /images/ 로 시작되는 모든 요청
		
		// 실제로 자원이 저장되어있는 로컬 경로 ------> C Drive / uploadImages
		String resourcePath = "file:///C:/uploadImages/"; // 최상위 경로만 작성!
		
		// /images/로 시작하는 요청이 오면, C:/uploadImages/ 와 연결
		registry.addResourceHandler(webPath).addResourceLocations(resourcePath);
	}
	
	
	
	
	
}

myPage-profile.html

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>My Page</title>

    <link rel="stylesheet" th:href="@{/css/myPage/myPage-style.css}">
</head>
<body>
    <main>
        <th:block th:replace="~{/common/header}"></th:block>

        
        <!-- 마이페이지 - 내 정보 -->
        <section class="myPage-content">
            
			<!-- 사이드메뉴 include -->
			<th:block th:replace="~{/myPage/sideMenu}"></th:block>


            <!-- 오른쪽 마이페이지 주요 내용 부분 -->
            <section class="myPage-main">

                <h1 class="myPage-title">프로필</h1>
                <span class="myPage-subject">프로필 이미지를 변경할 수 있습니다.</span>
	
                <form th:action="@{profile}" th:object="${session.loginMember}" method="POST" name="myPageFrm" id="profileFrm" enctype="multipart/form-data">

                    <div class="profile-image-area">
                    
                    	<!-- 프로필 이미지가 없으면 기본 이미지 -->
                        <img th:unless="*{profileImage}" th:src="@{/images/user.png}" id="profileImage">
                        	<!-- unless = false일 때 실행 --> 
		
						<!-- 프로필 이미지가 있으면 저장된 이미지 -->
						<img th:if="*{profileImage}" th:src="*{profileImage}" id="profileImage">
						
                    </div>
                    
                    <span id="deleteImage">x</span>

                    <div class="profile-btn-area">
                        <label for="imageInput">이미지 선택</label>
                        <input type="file" name="profileImage" id="imageInput" accept="image/*">
                        <button>변경하기</button>
                    </div>
                    
                    <div class="myPage-row">
                        <label>이메일</label>
                        <span>로그인 회원 이메일</span>
                    </div>
                    
                    <div class="myPage-row">
                        <label>가입일</label>
                        <span>로그인 회원 가입일</span>
                    </div>
                    
                </form>

                

            </section>

        </section>

    </main>

 	<th:block th:replace="~{/common/footer}"></th:block>
	
 	<script th:src="@{/js/myPage/myPage.js}"></script>

</body>
</html>

0개의 댓글