Spring-프로필 이미지 변경

맑은 눈의 코드 👀·2023년 8월 27일
0

06_framework 이론

목록 보기
18/23
post-thumbnail
post-custom-banner

🏝️프로필이미지 변경

FileReader객체
change이벤트 : 값이 변했을 때

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

👙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"  %>

<!DOCTYPE html>
<html lang="ko">
<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" href="/resources/css/myPage/myPage-style.css">
</head>
<body>
    <main>
       <jsp:include page="/WEB-INF/views/common/header.jsp"/>

        
        <!-- 마이페이지 - 내 정보 -->
        <section class="myPage-content">
            
			<!-- 사이드메뉴 include -->
			<!-- jsp 액션 태그 -->
			<jsp:include page="/WEB-INF/views/myPage/sideMenu.jsp"/>


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

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

                <%-- 
                    - 파일 제출 시 무조건 POST 방식
                    - enctype 속성 추가 

                    - enctypy : form 태그 데이터가 서로로 제출 될 때 인코딩 되는 방법을 지전
                        (POST 방식일 때만 사용 가능)
                    - application/x-www-form-urlencoded : 모든 문자를 서버로 전송 하기 전에 인코딩
                        (form태그 기본 값)
                    - multipart/form-data : 모든 문자를 인코딩 하지 안음
                        (원본 데이터가 유지 되어 이미지, 파일들을 서버로 전송할 수 있음)
                --%>


                <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>
            </section>
        </section>
    </main>

	<jsp:include page="/WEB-INF/views/common/footer.jsp"/>
    
    <script src="/resources/js/myPage/myPage.js"></script>

</body>
</html>

🩴myPage.js

// 프로필 이미지 추가/ 변경/ 삭제 
const profileImage = document.getElementById("profileImage");
const deleteImage = document.getElementById("deleteImage");
const imageInput= document.getElementById("imageInput");

let initCheck; // 초기 프로필 이미지 상태를 저장하는 변수
                // false == 기본 이미지
                // ture  == 이전 이미지
let deleteCheck = -1;
// 프로필이미지가 새로 업로드 되었거나 삭제됨을 나타내는 변수
// -1 = 초기값, 0 == 프로필 삭제 (X버튼), 1 == 새 이미지 업로드 

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

if(imageInput != null){//화면에 imageInput이 있을 경우

    // 프로필 이미지가 출력되는 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을 reasult 속성으로 얻어올 수 있게 함
    
        reader.onload = e => {
            //console.log(e.target);
            //console.log(e.target.result); //읽은 파일의 URL
        
            const url = e.target.result;
            
            //프로필 이미지(img) 태그에 포함된 url 주소 
            profileImage.setAttribute("src", url);
           
            deleteCheck = 1; //새 이미지 업로드 
        
        }
    });

    // X버튼 클릭시
    deleteImage.addEventListener("click", () => {
        
        //프로필 이미지 변경
        profileImage.setAttribute("src",originalImage);
        
        imageInput.value=""; //input type="file"의 value 삭제

        deleteCheck = 0;
    });


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

        //let initCheck; 
        // 초기 프로필 이미지 상태를 저장하는 변수
        // false == 기본 이미지
        // ture  == 이전 이미지
        // let deleteCheck = -1;
        // 프로필이미지가 새로 업로드 되었거나 삭제됨을 나타내는 변수
        // -1 = 초기값, 0 == 프로필 삭제 (X버튼), 1 == 새 이미지 업로드 
        let flag = true;

        // 프로필 이미지가 없다 -> 있다
        if( !initCheck && deleteCheck == 1 ){
            flag = false;
        }
        // 이전 프로필 이미지가 있다 -> 삭제
        if( initCheck && deleteCheck == 0 ){
            flag = false;
        }
        // 이전 프로필 이미지가 있다 -> 새 이미지
        if( initCheck && deleteCheck == 1 ){
            flag = false;
        }
        if(flag){// flag == true -> 제출하면 안되는 경우 
            e.preventDefault(); // form 기본 이벤트 제거
            alert("이미지 변경 후 클릭하세요");
        }
    });
}

👙XML에 파일 업로드에 필요한 라이브러리 추가

🩴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

프로젝트 전반적으로 사용할 DB연결 관련 내용 (JDBC, MyBatis, DBCP),

AOP, 트렌젝션 처리, 파일 업로드 등을 작성

<!-- 
   파일 업로드를 위한 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 바이트.
    -->   

👙 myPage.controller

🩴MyPageController.java

MultipartFile : input type="file"로 제출된 파일을 저장하는 객체

[제공하는 메소드]
- transferTo() : 파일을 지정된 경로에 저장(메모리 -> HDD/SSD)
- getOriginalFileName() : 파일 원본명
- getSize() : 파일 크기
	// 프로필 이미지 수정 
	@PostMapping("/profile")
	public String uadateProfile(
			@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);
		// C:\workspace\6_Framework\boardProject\src\main\webapp\resources\image
		
		// 프로필 이미지 수정 서비스 호출
		int result = service.updateProfile(profileImage, webPath, filePath, loginMember);
		
		String message = null;
		if(result > 0) message ="프로필 이미지가 변경 되었습니다.";
	
		else message ="프로필 변경 실패.";
		
		ra.addFlashAttribute("message", message);
		
		return "redirect:profile";
	}

👙 myPage.model.service

🩴MyPageService.java인터페이스

예외 처리! throws IllegalStateException, IOException

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

🩴MyPageServiceImpl.java

.transferTo() :apthem

// 프로필 이미지 수정 서비스 
	@Override
	public int updateProfile(MultipartFile profileImage, String webPath, String filePath, Member loginMember) throws IllegalStateException, IOException {
	//프로필 이미지 변경 실패 대비
	String temp= loginMember.getProfileImage(); // 이전 이미지 저장

	String rename = null; //변경할 이름 저장 변수 

	if( profileImage.getSize() > 0 ) { //업로드 된 이미지가 있을 경우 

		// 1) 파일 이름 변경 
		rename = fileRename(profileImage.getOriginalFilename());

		// 2) 바뀐 이름으로 loginMember에 세팅
		loginMember.setProfileImage(webPath + rename);
		// /resoureces/images/member/20230824114510_1234.jpg

	}else { // 없는 경우(x버튼)
		loginMember.setProfileImage(null);
		// 세션 이미지를 null로 변경해서 삭제

	}

	// 프로필 이미지 수정 DAO메소드 호출
	int result = dao.updateProfileImage(loginMember);
	
	if(result > 0){ //성공
		
		// 새 이미지가 업로드 된 경우 
		if(rename != null) {
			profileImage.transferTo(new File(filePath + rename));
		}
		
	}else { // 실패
		// 이전 이미지로 프로필 다시 세팅
		loginMember.setProfileImage(temp);
	}
	return result;
}

// 파일명 변경 메소드
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;
}
![](https://velog.velcdn.com/images/ee_ji0/post/16700e8d-79c9-481d-95ce-7549ec59db04/image.png)




## 👙 MyPage.model.dao
### 🩴MyPageDAO.java
```java
/** 프로필 이미지 수정
	 * @param loginMember
	 * @return result
	 */
	public int updateProfileImage(Member loginMember) {
		return sqlSession.update("myPageMapper.updateProfileImage", loginMember);
	}

👙 myPage-mapper.xml

🩴myPae-mapper.xml

내가 실수 한 부분! paramType은 전달 받은 값에 대한 자료형을 알려 주는 거임

<!-- 프로필 이미지 수정 <-->
	<update id="updateProfileImage">
		UPDATE MEMBER SET
		PROFILE_IMG = #{profileImage}
 		WHERE MEMBER_NO = #{memberNo}
	</update>
profile
나를 죽이지 못하는 오류는 내 코드를 더 강하게 만들지ㅋ
post-custom-banner

1개의 댓글

comment-user-thumbnail
2023년 12월 6일

안녕하세요, 혹시 profile_image 컬럼의 데이터형을 뭘로 했는지 알 수 있을까요?

답글 달기