국비 92 - 프로필 사진 등록(스프링)

냐아암·2023년 8월 24일
0

국비

목록 보기
107/114

🔑 Servlet만을 사용했을 때와 차이점

  • 파일 최대 크기를 js에서 지정해줌
  • 메모리 -> 하드로 이동해주는 코드 작성
<form action="profile" method="POST" name="myPageFrm" id="profileFrm" enctype="multipart/form-data">

                    <div class="profile-image-area">

                    <c:if test="${empty loginMember.profileImage}" >
                        <%-- 프로필 이미지가 없으면 기본 이미지 --%>
                        <img src="/resources/images/user.png" id="profileImage">
                    </c:if>

                    <c:if test="${!empty loginMember.profileImage}" >
                        <%-- 프로필 이미지가 있으면 있는 이미지 --%>
                        <img src="${loginMember.profileImage}" id="profileImage">
                    </c:if>


                    </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>${loginMember.memberEmail}</span>
                    </div>
                    
                    <div class="myPage-row">
                        <label>가입일</label>
                        <span>${loginMember.enrollDate}</span>
                    </div>
                    
                </form>
// 프로필 이미지 추가/변경/삭제
const profileImage = document.getElementById("profileImage"); // img 태그
const deleteImage = document.getElementById("deleteImage"); // x버튼
const imageInput = document.getElementById("imageInput"); // input태그

let initCheck; // 초기 프로필 이미지 상태를 저장하는 변수
               // false == 기본 이미지, true == 이전 업로드 이미지

let deleteCheck = -1;
// 프로필 이미지가 새로 업로드되거나 삭제되었음을 나타내는 변수
// -1 == 초기값 , 0 == 프로필 삭제(x버튼), 1 == 새 이미지 업로드


let originalImage; // 초기 프로필 이미지의 파일 경로 지정

if(imageInput != null){ // 화면에 이미지 input이 있다면

    // 프로필 이미지가 출력되는 img 태그의 src 속성 저장
    originalImage = profileImage.getAttribute("src");

    // 회원 프로필 화면 진입 시
    // 현재 회원의 프로필 이미지 상태를 확인
    if(originalImage == "/resources/images/user.png"){ // 기본 이미지인 경우
        initCheck = false;
    } else {
        initCheck = true;
    }
    

    // change 이벤트 : 값이 변했을 때
    // - input type = "file", "checkbox", "radio"에서 많이 사용
    // - text/number 형식 사용 가능
    // -> 이때 input 값 입력 후 포커스를 잃었을 때
    //    이전 값과 다르면 change 이벤트 발생

    imageInput.addEventListener("change", e => {

        // 2MB 최대 크기 제한
        const maxSize = 1 * 1024 * 1024 * 2; // 파일의 최대 크기 지정(바이트 단위)

        console.log(e.target); // input
        console.log(e.target.value); // 업로드된 파일 경로
        console.log(e.target.files); // 업로드된 파일의 정보가 담긴 배열
        
        const file = e.target.files[0]; // 업로드한 파일의 정보가 담긴 객체

        // 파일을 한 번 선택한 후 취소했을 때
        if(file == undefined){
            console.log("파일 선택이 취소됨");
            deleteCheck = -1; // 취소 == 파일 없음 == 초기 상태

            // 취소 시 기존 프로필 이미지로 변경
            profileImage.setAttribute("src", originalImage);
            return;
        }

        if(file.size > maxSize){ // 선택된 파일의 크기가 최대 크기를 초과한 경우
            alert("2MB 이하의 이미지를 선택해주세요.");
            imageInput.value="";
            // input type = "file" 태그에 대입할 수 있는 value는 ""(빈칸)뿐이다!

            deleteCheck = -1; // 취소 == 파일 없음 == 초기 상태

            // 기존 프로필 이미지로 변경
            profileImage.setAttribute("src", originalImage);

            return;
        }

        // JS에서 파일을 읽는 객체
        // - 파일을 읽고 클라이언트 컴퓨터에 파일을 저장할 수 있다.

        const reader = new FileReader();

        reader.readAsDataURL(file);
        // 매개변수에 작성된 파일을 읽어서 저장한 후
        // 파일을 나타내는 URL을 result 속성으로 얻어올 수 있게 함

        // 다 읽었을 때
        reader.onload = e => {
            // console.log(e.target);
            // console.log(e.target.result); // 읽은 파일의 URL

            const url = e.target.result;

            // 프로필 이미지(img)태그에 src 속성으로 추가
            profileImage.setAttribute("src", url);

            deleteCheck = 1;
        }
    });

    // x 버튼 클릭 시 
    deleteImage.addEventListener("click", () => {

        // 프로필 이미지를 기본 이미지로 변경
        profileImage.setAttribute("src", "/resources/images/user.png");
        imageInput.value=""; // input type = "file"의 value 삭제

        deleteCheck = 0;
    });

    // #profileFrm이 제출되었을 때
    document.getElementById("profileFrm"),addEventListener("submit", e => {

        // let initCheck; // 초기 프로필 이미지 상태를 저장하는 변수
        // false == 기본 이미지, true == 이전 업로드 이미지

        // let deleteCheck = -1;
        // 프로필 이미지가 새로 업로드되거나 삭제되었음을 나타내는 변수
        // -1 == 초기값 , 0 == 프로필 삭제(x버튼), 1 == 새 이미지 업로드

        let flag = true;
        // 프로필 이미지가 없다 -> 있다
        if(!initCheck && deleteCheck == 1) flag = false;

        // 프로필 이미지가 있다 -> 삭제
        if(deleteCheck == 0 && initCheck) flag = false;

        // 이전 프로필 이미지가 있다 -> 새 이미지
        if(deleteCheck == 1 && initCheck) flag = false;

        if(flag){ // flag가 true라면 제출하면 안 됨
            e.preventDefault(); // form 기본 이벤트 제거
            alert("이미지 변경 후 클릭하세요");
        }

        /* if(profileImage.getAttribute("src") == originalImage){
            alert("test")
            e.preventDefault();
        } */

        
    });


}

🔑 pom.xml에 의존성 주입

<!-- 파일 업로드 관련 라이브러리 -->
		<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
		<dependency>
			<groupId>commons-fileupload</groupId>
			<artifactId>commons-fileupload</artifactId>
			<version>1.5</version>
		</dependency>

🔑 root-context.xml에 bean 등록

<!-- 파일 업로드를 위한 MutipartResolver 구현체 CommonsMultipartResolver bean 등록 -> 
		CommonsMultipartResolver를 bean으로 등록하면 multipart/form-data 형식으로 요청 시 input 
		type="file" 태그를 자동적으로 인식하여 MultipartFile 객체로 반환하고 파일 외의 데이터(정수, 문자열 등의 텍스트 
		데이터)는 기존처럼 사용 가능(MultipartRequest 필요 없음) -->
	<bean id="multipartResolver"
		class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
		<property name="maxUploadSize" value="104857600" />
		<property name="maxUploadSizePerFile" value="104857600" />
		<property name="maxInMemorySize" value="104857600" />
	</bean>
	<!-- 104857600 byte == 100MB maxUploadSize : 한 요청당 업로드가 허용되는 최대 용량을 바이트 
		단위로 설정. -1 은 제한이 없다는 뜻으로 이 프로퍼티를 지정하지 않을때 기본값. maxUploadSizePerFile : 한 파일당 
		업로드가 허용되는 최대 용량을 바이트 단위로 설정. -1 은 제한이 없다는 뜻으로 이 프로퍼티를 지정하지 않을때 기본값. maxInMemorySize 
		: 디스크에 저장하지 않고 메모리에 유지하도록 허용하는 바이트 단위의 최대 용량을 설정. 사이즈가 이보다 클 경우 이 사이즈 이상의 
		데이터는 파일에 저장됩니다. 기본값은 10240 바이트. -->
        
        

📍 MultipartFile

  • transferTo() : 파일을 지정된 경로에 저장(메모리 -> HDD/SSD)
  • getOriginalFileName() : 파일 원본명
  • getSize() : 파일 크기

🔑 예외 던져주기 !!

Controller

// 프로필 이미지 수정
	@PostMapping("/profile")									// 업로드한 파일
	public String updateProfile(@RequestParam("profileImage") MultipartFile profileImage
								, @SessionAttribute("loginMember") Member loginMember
								, RedirectAttributes ra // 리다이렉트 시 메세지 전달
								, HttpSession session // 세션 객체
								) throws IllegalStateException, IOException {
		
		// 웹 접근 경로
		String webPath = "/resources/images/member/";
		
		// 실제로 이미지 파일이 저장되어야 하는 서버 컴퓨터 경로
		String filePath = session.getServletContext().getRealPath(webPath);
		
		// 프로필 이미지 수정 서비스 호출
		int result = service.updateProfile(profileImage, webPath, filePath, loginMember);
		
		String message = null;
		
		if(result > 0) {
			message = "프로필 이미지가 변경되었습니다.";
		} else {
			message = "프로필 변경 실패";
		}
		
		ra.addFlashAttribute("message" , message);
		
		return "redirect:profile";
	}
    

Service

/** 프로필 이미지 수정 서비스
	 * @param profileImage
	 * @param webPath
	 * @param filePath
	 * @param loginMember
	 * @return result
	 */
	int updateProfile(MultipartFile profileImage, String webPath, String filePath, Member loginMember) 
			throws IllegalStateException, IOException;

ServiceImpl

// 파일명 변경 메소드
	public static String fileRename(String originFileName) {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
		String date = sdf.format(new java.util.Date(System.currentTimeMillis()));

		int ranNum = (int) (Math.random() * 100000); // 5자리 랜덤 숫자 생성

		String str = "_" + String.format("%05d", ranNum);

		String ext = originFileName.substring(originFileName.lastIndexOf("."));

		return date + str + ext;
	}

DAO

/** 프로필 이미지 수정
	 * @param loginMember
	 * @return result
	 */
	public int updateProfileImage(Member loginMember) {
		return sqlSession.update("myPageMapper.updateProfileImage", loginMember);
	}

sql

<!-- 프로필 이미지 수정 -->
	<update id="updateProfileImage">
		UPDATE MEMBER SET PROFILE_IMG = #{profileImage} WHERE MEMBER_NO = #{memberNo}
	</update>
    
profile
개발 일지

0개의 댓글