재밌고 어려운 IT를 이해해보자~!

Image Upload Update [TEAM PROJECT] 본문

코리아IT핀테크과정

Image Upload Update [TEAM PROJECT]

언제나즐거운IT 2024. 1. 20. 18:28

이미지파일 선택시 preview를 생성해서 유저에게 보여주고 이미지의 사이즈를 프로필 사이즈에 맞게 조정

 

main.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" import="model.member.*"%>
<%@ taglib tagdir="/WEB-INF/tags" prefix="stone"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<!DOCTYPE html>
<html>

<head>
    <!-- jQuery 추가 -->
    <script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
    <meta charset="UTF-8">
    <title>마이페이지</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
    <link rel="stylesheet" href="${pageContext.request.contextPath}/assets/css/main.css" />

    <noscript>
        <link rel="stylesheet" href="assets/css/noscript.css" />
    </noscript>
    <style>
        input::-webkit-input-placeholder {
            font-family: "Source Sans Pro", Helvetica, sans-serif;
        }

        a {
            font-family: "Source Sans Pro", Helvetica, sans-serif;
        }

        .field {
            margin-bottom: 50px;
        }

        .photo {
            width: 350px;
            height: 350px;
            border: 3px solid black;
            overflow: hidden;
        }

        .field input[type="text"] {
            display: inline-block;
            width: 60%;
            font-weight: bold;
        }

        .btn-upload {
            width: 150px;
            height: 50px;
            margin-top: 10px;
            background: #fff;
            border: 1px solid rgb(77, 77, 77);
            border-radius: 10px;
            font-weight: 500;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .btn-upload:hover {
            background: rgb(77, 77, 77);
            color: #fff;
        }

        #fileInput {
            display: none;
        }
    </style>
</head>

<body class="is-preload">

    <stone:printNav member='${member}'/>
	
    <div id="footer">

        <section>
            <div class="fields">
                <h1 style="font-size: 150px; text-align: center;">MyPage</h1>
                <form id="firstForm" action="profileUpload.do" method="post" enctype="multipart/form-data">
                    <!-- 프사 -->
                    <div class="field">
                        <div class="photo">
                            <img id="preview" alt="프로필 이미지" src="mimg/${memberDTO.profile}" onload="resizePreviewImage(this, 350, 350)">
                        </div>
                        <label for="fileInput" class="btn-upload">이미지 선택 <input type="file" name="file" id="fileInput" form="firstForm"></label>
                    </div>
                </form>

                <!-- 아이디 -->
                <div class="field">
                    <label for="myPageID"></label> <input type="text" name="myPageID" id="myPageID" value="${memberDTO.memberID}" readonly />
                </div>

                <!-- 이름 -->
                <div class="field">
                    <label for="myPageName"></label> <input type="text" name="myPageName" id="myPageName" value="${memberDTO.name}" readonly />
                </div>

                <!-- 닉네임 -->
                <div class="field">
                    <form id="changeNickname" method="post" action="changeNickname.do">
                        <label for="myPageNickname"></label> <input type="text" name="myPageNickname" id="myPageNickname" value="${memberDTO.nickname}" required /> <input type="submit" value="닉네임 변경" style="margin-left: 30px;" />
                    </form>
                </div>

                <!-- 번호 -->
                <div class="field">
                    <form id="changePh" method="post" action="#">
                        <label for="myPagePh"></label> <input type="text" name="myPagePh" id="myPagePh" value="${memberDTO.ph}" readonly />
                        <button type="button" style="margin-left: 30px;" onclick="location.href='#'">전화번호 변경</button>
                    </form>
                </div>

                <!-- 자신이 작성한 글로 이동 -->
                <div class="field">
                    <form id="myBoard" method="post" action="#">
                        <button type="button" onclick="location.href='myBoardSelectAllPage.do'">내 작성글로 가기</button>
                    </form>
                </div>

                <div class="field" style="display: flex; justify-content: space-between; margin-top: 30px">
                    <div>
                        <!-- "변경완료" 버튼을 눌렀을 때 firstform 제출 116번라인 upload.do 실행-->
                        <!-- 유저 프로필 저장만을 위해서 사용중 -->
                        <input type="submit" value="변경완료" form="firstForm"><br>
                    </div>
                    <div style="text-align: right;">
                        <button type="button" onclick="location.href='deleteAccount.do'">회원 탈퇴</button>
                    </div>
                </div>
            </div>
        </section>
    </div>

	<script>
		 <!-- 전화번호 암호화  -->
	    // DOMContentLoaded 이벤트가 발생했을 때 실행되는 함수 화면이 다 로드됨을 뜻함
	    document.addEventListener('DOMContentLoaded', function () {
	    	
	        // myPagePh 엘리먼트를 가져와서 phoneNumberElement 변수에 저장
	        var phoneNumberElement = document.getElementById('myPagePh');
	
	        // phoneNumberElement이 존재하면 실행
	        if (phoneNumberElement) {
	        	
	            // myPagePh의 값을 maskPhoneNumber 함수를 이용하여 "*"로 가려진 형태로 변경
	            phoneNumberElement.value = maskPhoneNumber('${memberDTO.ph}');
	        }
	    });
	
	    // 전화번호의 가운데 4자리를 "*"로 암호화 하는 함수
	    function maskPhoneNumber(phoneNumber) {
	    	
	        // 전화번호를 '-'로 분리하여 parts 배열에 저장
	        var parts = phoneNumber.split('-');
	
	        // parts 배열의 길이가 3이면 실행
	        if (parts.length === 3) {
	        	
	            // 가운데 부분을 '****'로 대체하고 다시 '-'로 조합하여 반환
	            parts[1] = '****';
	            return parts.join('-');
	        } else {
	        	
	            // 형식에 맞지 않는 경우 그대로 반환
	            return phoneNumber;
	        }
	    }
	    
	    <!-- MyPage들어올때 유저가 등록한 이미지를 프로필사이즈에 맞게 조정  -->
	    // 이미지 리사이징 여부를 나타내는 변수 
	    // 처음 MyPage에 들어왔을 때 딱 1번 리사이징 하기 위한 변수
	    var imageResized = false;
	
	    // 프로필 이미지를 원하는 크기로 조정하는 함수
	    function resizePreviewImage(img, maxWidth, maxHeight) {
	    	
	        // 이미지가 리사이징되었다면 함수 종료
	        if (imageResized) {
	            return;
	        }
	
	        // 캔버스를 생성하고 2D 컨텍스트를 얻어옴
	        var canvas = document.createElement('canvas');
	        var ctx = canvas.getContext('2d');
	
	        // 원본 이미지의 너비와 높이
	        var width = img.width;
	        var height = img.height;
	
	        // 너비와 높이 중에서 작은 쪽을 기준으로 1:1 비율로 만듦
	        var size = Math.min(width, height);
	        var xOffset = (width - size) / 2;
	        var yOffset = (height - size) / 2;
	
	        // 캔버스에 그림
	        canvas.width = size;
	        canvas.height = size;
	        ctx.drawImage(img, xOffset, yOffset, size, size, 0, 0, size, size);
	
	        // 조정된 이미지를 원하는 크기로 만들기 위한 새로운 캔버스 생성
	        var resizedCanvas = document.createElement('canvas');
	        var resizedCtx = resizedCanvas.getContext('2d');
	        resizedCanvas.width = maxWidth;
	        resizedCanvas.height = maxHeight;
	
	        // 파일 확장자에 따라 JPEG 또는 PNG로 변환하여 조정된 이미지로 설정
	 /*        if (img.src.toLowerCase().endsWith('.png')) {
	            resizedCtx.drawImage(canvas, 0, 0, size, size, 0, 0, maxWidth, maxHeight);
	            img.src = resizedCanvas.toDataURL('image/png');
	        } else { */
	            resizedCtx.drawImage(canvas, 0, 0, size, size, 0, 0, maxWidth, maxHeight);
	            img.src = resizedCanvas.toDataURL('image/jpeg');
	       // }
	
	        // 이미지가 리사이징되었음을 표시 207번라인으로 돌아가 함수종료
	        imageResized = true;
	    }
	    
	    <!-- 유저가 새로운프로필을 등록할때 선택한 사진을 프로필 사이즈에 맞게 조정하며 미리 보여줌  -->
	    <!-- 이후 "변경완료" 버튼을 눌러야 DB 및 mimg폴더에 저장  -->
	    // 화면이 모두 로드되면 실행되는 함수
	    $(document).ready(function () {
	    	
	        // 유저가 파일을 선택했을때 실행되는 이벤트 핸들러 등록
	        $("#fileInput").on("change", function () {
	        	
	            // 파일 입력과 이미지 미리보기 엘리먼트를 가져옴
	            var fileInput = document.getElementById('fileInput');
	            var preview = document.getElementById('preview');
	
	            // 파일 입력이 존재하고 첫 번째 파일이 존재하면 실행
	            if (fileInput.files && fileInput.files[0]) {
	            	
	                // FileReader 객체를 생성
	                var reader = new FileReader();
	
	                // 파일 읽기가 완료되면 실행되는 이벤트 핸들러 등록
	                reader.onload = function (e) {
	                	
	                    // 미리보기 엘리먼트에 이미지 소스 설정
	                    // 120번라인 id="preview" 를 가진 img태그에 파일을 읽어온 결과를 src속성에 할당
	                    preview.src = e.target.result;
	
	                    // resizeImage 함수를 사용하여 이미지 크기를 조정하고 새로운 미리보기 설정
	                    resizeImage(fileInput.files[0], 350, 350, function (resizedImage) {
	                        preview.src = resizedImage;
	                    });
	                };
	
	                // 파일을 Data URL로 읽기 시작
	                // 파일의 내용을 읽어와서 Base64 인코딩된 데이터 URL로 제공
	                reader.readAsDataURL(fileInput.files[0]);
	            }
	        });
	
	        // 이미지 크기를 조정하고 새로운 미리보기를 설정하는 함수
	        function resizeImage(file, maxWidth, maxHeight, callback) {
	        	
	            // 이미지 객체와 FileReader 객체 생성
	            var img = new Image();
	            var reader = new FileReader();
	
	            // 파일 읽기가 완료되면 실행되는 이벤트 핸들러 등록
	            reader.onload = function (e) {
	            	
	                // 이미지 소스 설정
	                img.src = e.target.result;
	
	                // 이미지 로딩이 완료되면 실행되는 이벤트 핸들러 등록
	                img.onload = function () {
	                	
	                    // 캔버스 생성
	                    var canvas = document.createElement('canvas');
	                    var ctx = canvas.getContext('2d');
	
	                    // 원본 이미지의 너비와 높이
	                    var width = img.width;
	                    var height = img.height;
	
	                    // 너비와 높이 중에서 작은 쪽을 기준으로 1:1 비율로 만듦
	                    var size = Math.min(width, height);
	                    var xOffset = (width - size) / 2;
	                    var yOffset = (height - size) / 2;
	
	                    // 캔버스에 그림
	                    canvas.width = size;
	                    canvas.height = size;
	                    ctx.drawImage(img, xOffset, yOffset, size, size, 0, 0, size, size);
	
	                    // 조정된 이미지를 원하는 크기로 만들기 위한 새로운 캔버스 생성
	                    var resizedCanvas = document.createElement('canvas');
	                    var resizedCtx = resizedCanvas.getContext('2d');
	                    resizedCanvas.width = maxWidth;
	                    resizedCanvas.height = maxHeight;
	
	                    // 파일 확장자에 따라 JPEG 또는 PNG로 변환하여 조정된 이미지로 설정
	                    if (file.type.toLowerCase().includes('jpeg') || file.type.toLowerCase().includes('jpg')) {
	                        resizedCtx.drawImage(canvas, 0, 0, size, size, 0, 0, maxWidth, maxHeight);
	                        
	                        // 조정된 이미지를 Data URL로 변환
	                        var resizedImage = resizedCanvas.toDataURL('image/jpeg');
	                        
	                        // 콜백 함수 호출
	                        // 이 코드는 콜백 함수를 호출하면서 resizedImage를 인자로 전달 
	                        // 이 콜백 함수는 전달된 이미지를 받아 특정한 작업을 수행
	                        // 이미지를 비동기적으로 조정하고 나서 그 결과를 전달할 필요가 있을 때, 
	                        // 외부에서 지정한 콜백 함수를 호출하여 조정된 이미지를 전달
	                        // 이렇게 하면 이미지가 비동기적으로 처리되어도 다음 단계에서 적절한 작업 가능
	                        callback(resizedImage);
	                    } else if (file.type.toLowerCase().includes('png')) {
	                        resizedCtx.drawImage(canvas, 0, 0, size, size, 0, 0, maxWidth, maxHeight);
	                        
	                        // 조정된 이미지를 Data URL로 변환
	                        var resizedImage = resizedCanvas.toDataURL('image/png');
	                        
	                        // 콜백 함수 호출
	                        callback(resizedImage);
	                    } else {
	                    	
	                        // 다른 형식의 파일은 그대로 Data URL을 콜백 함수에 전달
	                        callback(e.target.result);
	                    }
	                };
	            };
	
	            // 파일을 Data URL로 읽기 시작
	            reader.readAsDataURL(file);
	        }
	    });
	</script>
</body>

</html>

 

선택한 이미지를 폴더에 업로드할때 원하는 파일이름명으로 변경해서 저장하고
중복된 파일명이 있다면 덮어쓰기 기능추가

ProfileUploadAction.java

package controller.common;

import java.io.File;
import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import com.oreilly.servlet.MultipartRequest;
import com.oreilly.servlet.multipart.DefaultFileRenamePolicy;

import controller.front.Action;
import controller.front.ActionForward;
import model.member.MemberDAO;
import model.member.MemberDTO;

public class ProfileUploadAction implements Action {

    @Override
    public ActionForward execute(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        ActionForward forward = new ActionForward();
        request.setCharacterEncoding("UTF-8");

        MemberDAO memberDAO = new MemberDAO();
        MemberDTO memberDTO = new MemberDTO();

        // UploadAction클래스 위치의 경로를 찾아서 uploadDir에 대입
        // 확인된 위치 : /C:/eclipse/FinalTeamProject/workspace_infinityStone/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/chalKag/WEB-INF/classes/controller/common/
        String uploadDir = this.getClass().getResource("").getPath();
        
        // .metadata 앞까지 문자열잘라서 이미지가 저장되는 폴더인 mimg까지의 절대경로 부여
        uploadDir = uploadDir.substring(1, uploadDir.indexOf(".metadata")) + "chalKag/src/main/webapp/mimg";
      
        // 총 100M 까지 저장 가능하게 함
        int maxSize = 1024 * 1024 * 100;
        String encoding = "UTF-8";

        // memberNum을 가져와서 사용자가 저장한 프로필 이미지 명으로 사용하려고 selectOne 사용
        HttpSession session = request.getSession();
        memberDTO.setMemberID((String) session.getAttribute("member"));
        memberDTO.setSearchCondition("정보출력");
        memberDTO = memberDAO.selectOne(memberDTO);
        
        if (memberDTO != null) {
        	// 사용자가 전송한 파일 정보를 토대로 업로드 장소에 파일 업로드 수행할 수 있게 함
        	// 사용자가 저장한 파일 명을 원하는대로 바꿔서 원하는 폴더에 저장
        	
        	// MultipartRequest 객체를 생성하여 파일 업로드 처리를 위한 정보를 설정
        	// request: 현재의 HttpServletRequest 객체
        	// uploadDir: 파일을 업로드할 디렉토리 경로
        	// maxSize: 업로드할 파일의 최대 크기
        	// encoding: 인코딩 방식
        	// new CustomFileRenamePolicy(Integer.toString(memberDTO.getMemberNum())):
        	// 파일 이름 중복 시 사용할 사용자 정의 파일 리네임 정책 객체를 생성하고 전달 103번라인 참조
        	// 현재 이름을 무조건 현재로그인된 memberNum으로 변경하고 저장  
            MultipartRequest multipartRequest = new MultipartRequest(request, uploadDir, maxSize, encoding,
                    new CustomFileRenamePolicy(Integer.toString(memberDTO.getMemberNum())));
            
            // memberNum으로 재설정한 이름 newFileName에 대입
            String newFileName = multipartRequest.getFilesystemName("file");
            
            // 현재 "변경완료" 버튼을 눌러 MyPage를 빠져나갈때 선택한 프로필사진 DB 및 폴더 저장 중
            // 유저가 프로필을 재선택 하지 않고 MyPage를 빠져나갈때 newFileName이 null이므로 예외처리 
            if (newFileName != null) {
            	
            	// 유저가 프로필을 등록하지 않고 회원가입을 했을 때 아래 코드를 실행 안해도 되므로 예외처리
                if (memberDTO.getProfile() != null) {
                	// 유저가 "티모.JPG"를 저장하고 "티모.PNG"를 저장할때 rename에 의해서 
                	// "1.JPG"저장 후 "1.PNG"를 저장하는 과정에서 확장자를 제외한 "1"만 비교해서 
                	// 같은게 있다면 기존 파일 삭제 후 새로운 파일 폴더에 저장하는 과정
                	
                	// 기존 프로필 파일이 저장되어있는 절대경로 확인
                	String existingFilePath = uploadDir + File.separator + memberDTO.getProfile();

                	// 기존 프로필 파일의 기본 이름을 추출 (확장자 제외)
                	String existingFileBaseName = memberDTO.getProfile().substring(0, memberDTO.getProfile().lastIndexOf('.'));

                	// 새로 업로드된 파일의 기본 이름을 추출 (확장자 제외)
                	String newFileBaseName = newFileName.substring(0, newFileName.lastIndexOf('.'));

                	// 기존 프로필 파일이 저장되어있는 절대 경로를 나타내는 File 객체를 생성
                	File existingFile = new File(existingFilePath);

                	// 기존 프로필 파일이 존재하고, 새로 업로드된 파일의 기존 프로필 파일과 새로 업로드된 프로필 파일의 확장자를 제외한 이름이 같으면
                	if (existingFile.exists() && existingFileBaseName.equals(newFileBaseName)) {
                	    // 기존 파일을 삭제
                	    existingFile.delete();
                	}
                }
                
                //DB에 변경한 프로필 사진명 저장
                memberDTO.setProfile(newFileName);
                memberDTO.setSearchCondition("프로필변경");
                boolean flag = memberDAO.update(memberDTO);
                if (flag) {
                    System.out.println(memberDTO);
                    forward.setPath("main.do");
                    forward.setRedirect(true);
                } else {
                    forward.setPath("error/alertPage.jsp");
                    forward.setRedirect(true);
                }
                
                // 회원가입시 저장한 회원 프로필이 없으면 그냥 메인으로 이동
            } else {
                forward.setPath("main.do");
                forward.setRedirect(true);
            }
        }
        return forward;
    }
}

//rename Override를 위한 클래스
class CustomFileRenamePolicy extends DefaultFileRenamePolicy {

    private String newFileName;

    public CustomFileRenamePolicy(String newFileName) {
        this.newFileName = newFileName;
    }
    
    // 저장하는 파일명 재정의 하는 메서드
    @Override
    public File rename(File file) {
    	
    	// 파일 확장자를 extension에 대입
        String extension = extractExtension(file.getName());
        
        // newName에 newFileName(memberNum값) + ".확장자" 대입
        String newName = newFileName + extension;
        
        // 새로운 파일객체 생성 후 리턴
        File newFile = new File(file.getParent(), newName);

        return newFile;
    }
    
    // 파일 이름에서 확장자를 추출하는 메서드
    private String extractExtension(String fileName) {
    	// "."이 있는 인덱스 확인
        int dotIndex = fileName.lastIndexOf('.');
        
        // 파일 이름에 점이 존재하고, 파일 이름의 마지막 문자가 점이 아닌 경우 확인
        if (dotIndex > 0 && dotIndex < fileName.length() - 1) {
        	
        	// "."을포함한 문자열 추출
            return fileName.substring(dotIndex);
            
        } else {
            return ""; // 확장자가 없는 경우 빈 문자열 반환
        }
    }
}

 

'코리아IT핀테크과정' 카테고리의 다른 글

검색 필터링 [TEAM PROJECT]  (2) 2024.02.02
VIEW에 JSTL, EL, CustomTag 사용하기 [TEAM PROJECT]  (0) 2024.01.23
HandlerMapper, Factory pattern  (2) 2024.01.19
서블릿 Filter, Listner, XML  (0) 2024.01.18
JSTL, Custom tag  (0) 2024.01.17
Comments