국비 60 - 회원 프로필 사진

냐아암·2023년 8월 7일
0

국비

목록 보기
76/114

회원 프로필 사진

📍 application/x-www-form-urlencoded : 모든 문자를 서버로 제출하기 전에 인코딩(모든 데이터가 문자)
(form 태그 기본값)

📍 mutipart/form-data : 제출할 때 인코딩을 하지 않음
-> 모든 데이터가 원본 형태를 유지(파일이 파일 상태로 서버로 제출)

📍 MultipartRequest

  • 준비 단계
  • 업로드 파일 크기 전체 합
  • 경로 지정
  • 파일명 중복 방지를 위한 파일명 변경 규칙
  • 파일 이외의 파라미터들의 문자 인코딩 지정
<li><a href="${contextPath}/member/myPage/profile">프로필</a></li>
@WebServlet("/member/myPage/profile")
public class MyPageProfileServlet extends HttpServlet {
	
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		
		// 메인페이지 -> 프로필 이미지 클릭
		// 마이페이지 -> 프로필 클릭
		
		String path = "/WEB-INF/views/member/myPage-profile.jsp";
		
		req.getRequestDispatcher(path).forward(req, resp);
		
	}

등록 버튼 눌렀을 때

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

<!-- 문자열 관련 함수(메소드) 제공 JSTL (EL 형식으로 작성) -->
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My page</title>

<link rel="stylesheet" href="${contextPath}/resources/css/main-style.css">
<link rel="stylesheet" href="${contextPath}/resources/css/myPage-style.css">

<script src="https://kit.fontawesome.com/8f020b2fa9.js"
   crossorigin="anonymous"></script>
</head>
<body>

   <main>
     <jsp:include page="/WEB-INF/views/common/header.jsp"></jsp:include>
      <!-- 마이페이지 - 내 정보 -->
      <section class="myPage-content">

		<!-- 사이드메뉴 include -->
        <jsp:include page="/WEB-INF/views/member/sideMenu.jsp"></jsp:include>
        
        <!-- 오른쪽 마이페이지 주요 내용 -->
        <section class="myPage-main">
            <h1 class="myPage-title">프로필</h1>
            <span class="myPage-explanation">프로필 이미지를 변경할 수 있습니다.</span>

            <!-- 

               enctype : form 태그가 데이터를 서버로 제출할 때
                         데이터의 인코딩 형식을 지정하는 속성

               1) application/x-www-form-urlencoded
                  - 모든 문자를 서버로 제출하기 전에 인코딩(모든 데이터가 문자)
                     (form 태그 기본값)
               
               2) mutipart/form-data : 제출할 때 인코딩을 하지 않음
                  -> 모든 데이터가 원본 형태를 유지(파일이 파일 상태로 서버로 제출)
                  (주의) multipart/form-data로 설정 시 method는 무조건 Post

             -->
            <form action="profile" method="POST" name="myPage-form"
               enctype="multipart/form-data" onsubmit="return profileValidate()">

               <div class="profile-image-area">
               
                  <c:if test="${empty loginMember.profileImage}">
                     <img src="${contextPath}/resources/images/user.png" id="profile-image">
                  </c:if>
               
                  <c:if test="${!empty loginMember.profileImage}">
                     <img src="${contextPath}${loginMember.profileImage}" id="profile-image">
                  </c:if>
                  
                  <!-- 프로필 이미지 삭제 버튼 -->
                  <span id="delete-image">x</span>

               </div>
               

               <div class="profile-btn-area">
                  <label for="input-image">이미지 선택</label>
                  <input type="file" name="profileImage" id="input-image" accept="image/*">
                  <!-- accept="image/*" : 이미지 파일 확장자만 선택 허용 -->
                  <!-- accept="video/*" : 동영상 파일 확장자만 선택 허용 -->
                  <!-- accept="pdf/*" : pdf 파일만 선택 허용 -->
                  <button type="submit">변경하기</button>
               </div>

                <div class="myPage-row">
                    <label>이메일</label>
                    <span>${loginMember.memberEmail}</span>
                </div>
                
                <div class="myPage-row">
                    <label>가입일</label>
                    <span>${loginMember.enrollDate}</span>
                </div>
                
                <!-- 삭제 버튼(X)이 눌러짐을 기록하는 숨겨진 input 태그 -->
                <!-- 0 : 안 눌러짐 -->
                <!-- 1 : 눌러짐 -->
                <input type="hidden" name="delete" id="delete" value="0">

            </form>
        </section>

      </section>  

   </main>

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

   <script>
      const contextPath = "${contextPath}"; // 최상위 경로를 JS 전역 변수로 선언
   </script>

   <!-- myPage.js 추가 -->
   <script src="${contextPath}/resources/js/member/myPage.js"></script>

</body>
</html>

// 회원 프로필 이미지 변경(미리보기)
const inputImage = document.getElementById("input-image");
if(inputImage != null){ // inputImage 요소가 화면에 존재할 때

    // input type ="file"요소는 파일이 선택될 때 change 이벤트가 발생한다.
    inputImage.addEventListener("change", function(){
        
        // this : 이벤트가 발생한 요소(input type = "file")
        
        // files : input type = "file"만 사용 가능한 속성으로
        //         선택된 파일 목록(배열)을 반환
        //console.log(this.files);
        //console.log(this.files[0]);

        if(this.files[0] != undefined) { // 파일이 선택되었을 때

            const reader = new FileReader();
            // 자바스크립트에서의 FileReader
            // - 웹 애플리케이션이 비동기적으로 데이터를 읽기 위하여 사용하는 객체

            reader.readAsDataURL(this.files[0]);
            // FileReader.readAsDataURL(파일)
            // - 지정된 파일의 내용을 읽기 시작함
            // - 읽어오는 게 완료되면 result 속성 data: 에
            //   읽어온 파일의 위치를 나타내는 URL이 포함된다.
            //  -> 해당 URL을 이용해서 브라우저에 이미지를 볼 수 있다.

            // FileReader.onload = function(){}
            // - FileReader가 파일을 다 읽어온 경우 함수를 수행
            reader.onload = function(e){ // 고전 이벤트 모델

                // e : 이벤트 발생 객체
                // e.target : 이벤트가 발생한 요소(객체) -> FileReader
                // e.target.result : FileReader가 읽어온 파일의 URL

                // 프로필 이미지의 src 속성의 값을 FileReader가 읽어온 파일의 URL로 변경
                const profileImage = document.getElementById("profile-image");

                profileImage.setAttribute("src", e.target.result);
                // -> setAttribute() 호출 시 중복되는 속성이 존재하면 덮어쓰기

                document.getElementById("delete").value == 0;
                // 새로운 이미지가 선택되었기 때문에 1 -> 0 (안 눌러짐 상태)로 변경
            }
        }
    })

}

function profileValidate(){

    const inputImage = document.getElementById("input-image");

    const del = document.getElementById("delete"); // hidden 타입

    if(inputImage.value == "" && del.value==0){ 
        // 빈문자열 == 파일 선택 X / del 값이 0 == x를 누르지도 않았다.
        // -> 아무것도 안 하고 변경버튼을 클릭한 경우

        alert("이미지를 선택한 후 변경 버튼을 클릭해주세요.");
        return false;
    }
    
    return true;

}

// 프로필 이미지 옆 X 버튼 클릭 시 
document.getElementById("delete-image").addEventListener("click", function(){

    // 0 : 안 눌러짐
    // 1 : 눌러짐
    const del = document.getElementById("delete");

    if(del.value==0){ // 눌러지지 않은 경우라면
        
        // 1) 프로필 이미지를 기본 이미지로 변경
        document.getElementById("profile-image").setAttribute("src", contextPath + "/resources/images/user.png");
    
        // 2) input type='file'에 저장된 값(value)에 "" 대입
        document.getElementById("input-image").value="";

        del.value = 1; // 눌러진 걸로 인식

    }



})

@WebServlet("/member/myPage/profile")
@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		
		try {
			
			// 프로필 이미지 업로드 -> 변경
			
			// System.out.println(req.getParameter("profileImage")); // null
			
			// 파라미터가 얻어와지지 않는 이유
			// 1. enctype="mutipart/form-data" -> 인코딩이 안 되어 있어서 파라미터가 인지되지 않는다.
			// 2. input type="file" -> 파일 형태의 데이터
			
			// -> mutilpart/form-data 형식의 요청을 처리할 수 있는 전용 request 객체가 필요
			
			// --> MultipartRequest  (cos.jar 라이브러리 이용 -  http://www.servlets.com/)
										// 파일 업로드 라이브러리
			
			// ****** MultipartRequest 사용을 위한 준비 단계 ******
			
			// 1. 업로드 되는 파일 크기의 전체 합을 지정(byte 지정)
			int maxSize = 1024 * 1024 * 20;
						// 1KB	 1MB	20MB
			
			// 2. 업로드 되는 파일이 어디에 저장될지 경로 지정
			//		-> 서버 컴퓨터 내부 폴더(절대 경로)
			
			// 2-1. server option 확인
			// servers -> 서버 설정 선택 -> overview -> server options
			//		   -> Serve Modules without Publishing 체크
			//			(업로드 되는 파일이 webapp 폴더 내부에 저장될 수 있음)
			
			// 2-2 memberProfile 폴더까지의 절대경로 얻어오기
			HttpSession session = req.getSession(); // session 얻어오는 것은 지장 없음(사용 가능)
			
			// 최상위 경로("/") == webapp 폴더의 컴퓨터상의 실제 절대 경로를 얻어옴
			String root = session.getServletContext().getRealPath("/");
			
			// 실제 파일이 저장되는 폴더의 경로
			String folderPath = "/resources/images/memberProfile/";
			
			// memberProfile 폴더까지의 절대 경로
			String filePath = root + folderPath;
			
			// 3. 저장되는 파일의 파일명 중복 방지를 위한 파일명 변경 규칙
			// -> cos.jar에서 제공하는 FileRenamePolicy 인터페이스 상속 클래스 생성
			// --> myRenamePolicy 클래스 생성
			
			// 4. 파일 이외의 파라미터들의 문자 인코딩 지정
			String encoding = "UTF-8";
			
			// 5. MultipartRequest 생성
			// ****(중요)****
			// MutilpartRequest 객체가 생성됨과 동시에 지정된 경로에
			// 지정된 파일명 변경 정책에 맞게 이름이 바뀐 파일이 저장(서버에 업로드)된다.
			MultipartRequest mpReq = new MultipartRequest(req, filePath, maxSize, encoding, new MyRenamePolicy());
			
			// 프로필 이미지 변경 Service 호출 시 필요한 값
			// 1) 로그인한 회원의 회원 번호
			Member loginMember = (Member)session.getAttribute("loginMember");
			int memberNo = loginMember.getMemberNo();
			
			// 2) 업로드된 프로필 이미지의 웹 접근 경로(folderPath + 변경된 파일명)
			
			// getOriginalFileName("input type='file'의 name 속성 값")
			// -> 원본 파일명
			//System.out.println(mpReq.getOriginalFileName("profileImage"));
			
			// getFileSystemName("input type='file'의 name 속성 값")
			// -> 변경된 파일명
			// System.out.println(mpReq.getFilesystemName("profileImage"));
			
			// DB에 삽입될 프로필 이미지 경로
			// 단, X 버튼이 클릭된 상태면 null을 가지게 한다.
			String profileImage = folderPath + mpReq.getFilesystemName("profileImage");
			
			// ** 프로필 이미지 삭제 **
			// 1) "delete" : input type = "hidden" 태그의 값(파라미터) 얻어오기
			//		(주의) multipart/form-data 형식의 요청을 처리 중이니
			//		req를 이용해서 파라미터를 얻어올 수 없다.
			//		-> mpReq를 이용하면 가능!
			int delete = Integer.parseInt( mpReq.getParameter("delete") );
			
			// 2) delete의 값이 1(눌러진 경우)이면 profileImage의 값을 null로 변경
			if(delete == 1) {
				profileImage = null;
			}
			
			// 프로필 이미지 변경 Service 호출 후 결과 반환 받기
			MemberService service = new MemberService();
			
			int result = service.updateProfileImage(memberNo, profileImage);
			
			if(result>0) {
				session.setAttribute("message", "프로필 변경 성공");
				
				// DB의 프로필 이미지 정보는 변경되었는데 
				// Session에 저장된 로그인 정보 중 프로필 이미지는 변경되지 않음
				// -> 동기화 작업 진행(내정보수정 참고)
				
				loginMember.setProfileImage(profileImage); // 세션의 값 변경
				
			} else {
				session.setAttribute("message", "프로필 변경 실패");
			}
			
			// 성공/ 실패 관계없이 프로필 화면 재요청
			// /member/myPage/profile (POST)
			// resp.sendRedirect(req.getContextPath()+"/member/myPage/profile");
			resp.sendRedirect("profile"); // 상대경로(GET)
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		
		
	}

/** 프로필 이미지 변경 Service
	 * @param memberNo
	 * @param profileImage
	 * @return result
	 * @throws Exception
	 */
	public int updateProfileImage(int memberNo, String profileImage) throws Exception{
		
		Connection conn = getConnection();
		
		int result = dao.updateProfileImage(conn, memberNo, profileImage);
		
		if(result>0)	conn.commit();
		else			conn.rollback();
		
		close(conn);
		
		
		return result;
	}

/** 프로필 이미지 변경 DAO
	 * @param conn
	 * @param memberNo
	 * @param profileImage
	 * @return result 
	 * @throws Exception
	 */
	public int updateProfileImage(Connection conn, int memberNo, String profileImage) throws Exception{
		
		int result = 0;
		
		try {
			
			String sql = prop.getProperty("updateProfileImage");
			
			pstmt = conn.prepareStatement(sql);
			
			pstmt.setString(1, profileImage);
			pstmt.setInt(2, memberNo);
			
			result = pstmt.executeUpdate();
			
		} finally {
			close(pstmt);
		}
		
		return result;
	}

<!-- 프로필 이미지 변경 -->
	<entry key="updateProfileImage">
		UPDATE MEMBER 
		SET PROFILE_IMG=?
		WHERE MEMBER_NO=?
	</entry>
profile
개발 일지

0개의 댓글