커뮤니티 - 프로필 업로드, 삭제 기능 (23.07.25)

·2023년 7월 25일
0

Server

목록 보기
30/35
post-thumbnail

📝 프로필 업로드 기능


💡 VS Code

🔎 myPage-profile.jsp

<%@ 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/4dca1921b4.js" crossorigin="anonymous"></script>

</head>
<body>

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

			<!-- 사이드메뉴 include  -->
			<jsp:include page="/WEB-INF/views/member/sideMenu.jsp"/>

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

                <h1 class="myPage-title">프로필</h1>
                <span class="myPage-explanation">프로필 이미지를 변경할 수 있습니다.</span>

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

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

                    2) multipart/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>
                            
                    </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>

                </form>

            </section>

        </section>

    </main>

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

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

🔎 index.jsp

...
            		<%-- 로그인이 되어 있는 경우 --%>
            		<c:otherwise>
            			<article class='login-area'>
            			
            				<!-- 회원 프로필 이미지 -->
            				<a href="${contextPath}/member/myPage/profile">

								<c:if test="${empty loginMember.profileImage}">
									<img src="${contextPath}/resources/images/user.png" id="member-profile">
								</c:if>

								<c:if test="${!empty loginMember.profileImage}">
									<img src="${contextPath}${loginMember.profileImage}" id="member-profile">
								</c:if>

            				</a>
...

🔎 myPage.js

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

    if(inputImage.value == ""){ // 빈 문자열 == 파일 선택 X
        alert("이미지를 선택한 후 변경 버튼을 클릭해 주세요.");
        return false;
    }

    return true;
}

💡 Eclipse

🔎 MyPageProfileServlet.java

package edu.kh.community.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.oreilly.servlet.MultipartRequest;

import edu.kh.community.common.MyRenamePolicy;
import edu.kh.community.member.model.service.MemberService;
import edu.kh.community.member.model.vo.Member;

@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);
	}
	
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		
		try {
			// 프로필 이미지 업로드 -> 변경
			
			// System.out.println( req.getParameter("profileImage") ); // null
			
			// 파라미터가 얻어와지지 않는 이유
			// 1. enctype="multipart/form-data" -> 인코딩 안 되어 있어 파라미터가 인지되지 않음
			// 2. input type="file" -> 파일 형태의 데이터
			
			// --> multipart/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 Module Without Publishing 체크
			//	  (업로드되는 파일이 webapp 폴더 내부에 저장될 수 있음)
			
			// 2-2. memberProfile 폴더까지의 절대 경로 얻어오기
			HttpSession session = req.getSession(); // session 얻어오는 것은 지장 없음(사용 가능)
			
			// 최상위 경로("/" == webapp 폴더)의 컴퓨터상의 실제 절대 경로를 얻어옴
			String root = session.getServletContext().getRealPath("/");
			// C:\workspace\5_Server\community\src\main\webapp
			
			// 실제 파일이 저장되는 폴더의 경로
			String folderPath = "/resources/images/memberProfile/";
			
			// memberProfile 폴더까지의 절대 경로
			String filePath = root + folderPath;
			
			// 3. 저장되는 파일의 파일명 중복 방지를 위한 파일명 변경 규칙
			// -> cos.jar에서 제공하는 FileRenamePolicy 인터페이스 상속 클래스 생성
			// --> myRenamePolicy 클래스 생성
			
			// 4. 파일 이외의 파라미터들의 문자 인코딩 지정
			String encoding = "UTF-8";
			
			// 5. MultipartRequest 생성
			// **** (중요) ****
			// MultipartRequest 객체가 생성됨가 동시에 지정된 경로에
			// 지정된 파일명 변경 정책에 맞게 이름이 바뀐 파일이 저장된다.
			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"));
			
			String profileImage = folderPath + mpReq.getFilesystemName("profileImage");
			
			// 프로필 이미지 변경 Service 호출 후 결과 반환 받기
			MemberService service = new MemberService();
			
			int result = service.updateProfileImage(memberNo, profileImage);
			
			String path = null;
					
			if(result > 0) { // 성공
				session.setAttribute("message", "프로필 이미지가 변경되었습니다.");
				
				// DB의 프로필 이미지 정보는 변경되었으나
				// Session에 저장된 로그인 정보 중 프로필 이미지는 변경되지 않음
				// -> 동기화 작업 진행(내 정보 수정)
				
				loginMember.setProfileImage(profileImage); // 세션의 값이 변경됨

			} else {
				session.setAttribute("message", "프로필 이미지 변경 실패 ㅠ.ㅠ");
			}
			
			// 성공/실패 관계 없이 프로필 화면 재요청(redirect)
			// /member/myPage/profile (POST)
			
			resp.sendRedirect("profile"); // 상대경로(GET)
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

🔎 MyRenamePolicy.java

package edu.kh.community.common;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;

import com.oreilly.servlet.multipart.FileRenamePolicy;

// 파일명 변경 정책
public class MyRenamePolicy implements FileRenamePolicy {

	@Override
	public File rename(File originalFile) {
		
		long currentTime = System.currentTimeMillis();
		// 1970년 1월 1일 오전 9시부터 현재 시간까지의 경과한  ms를 반환
		
	      SimpleDateFormat ft = new SimpleDateFormat("yyyyMMddHHmmss");
	      
	      int ranNum = (int) (Math.random() * 100000); // 5자리 랜덤 숫자 생성
	      					// 0 <= 난수 <= 99999
	                        
	      String str = "_" + String.format("%05d", ranNum);
	      				  // user.png
	      				  // 20230725102517_00100.png
	                           
	      // String.format : 문자열을 지정된 패턴의 형식으로 변경하는 메소드
	      // %05d : 오른쪽 정렬된 십진 정수(d) 5자리(5)형태로 변경. 빈자리는 0으로 채움(0)

	      // 파일명을 변경해도 확장자를 유지하기 위하여
	      // 별도로 확장자 명 가져오기
	      String name = originalFile.getName();
	      String ext = null;

	      int dot = name.lastIndexOf(".");

	      if (dot != -1) {
	         // dot 포함해서 확장자 추출 (ext)
	         ext = name.substring(dot);
	      } else {
	         // 확장자가 없는 경우
	         ext = "";
	      }

	      String fileName = ft.format(new Date(currentTime)) + str + ext;
	      					// 20230725102910 + _01234 + .png
	                              
	      File newFile = new File(originalFile.getParent(), fileName);

	      return newFile;
	}
	
}

🔎 MemberService.java

	/** 프로필 이미지 변경 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) commit(conn);
		else		   rollback(conn);
		
		close(conn);
		
		return result;
	}

🔎 MemberDAO.java

	/** 프로필 이미지 변경 Service
	 * @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;
	}

🔎 member-sql-ash.xml

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

📌 출력 화면

프로필 사진이 변경된 모습을 볼 수 있다!
또한 이미지 선택이 되지 않은 상태에서 '변경하기' 버튼을 클릭하면

위 사진과 같은 알림창이 뜨는 모습을 볼 수 있다.


📝 프로필 삭제 기능


💡 VS Code

🔎 myPage-profile.jsp

...
                <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>
...

🔎 myPage-style.css

...
/* 프로필 이미지 삭제 버튼 */
#delete-image{
    position: absolute;
    top : 0;
    right: 0;
    cursor: pointer;
}

🔎 myPage.js

...
// 회원 프로필 이미지 변경(미리보기)
const inputImage = document.getElementById("input-image");

if(inputImage != null){ // inputImage 요소가 화면에 존재할 때
...

                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; // 눌러진 것으로 인식
        
    } 

})

💡 Eclipse

🔎 MyPageProfileServlet.java

...
			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;
			}
...

📌 출력 화면

프로필 우측 x버튼을 클릭하면 기존 프로필 이미지가 삭제되고 기본 이미지로 대체된다.

profile
풀스택 개발자 기록집 📁

1개의 댓글

comment-user-thumbnail
2023년 7월 25일

좋은 정보 얻어갑니다, 감사합니다.

답글 달기