프로젝트/SSR 게시판

[프로젝트] 게시판 | 6. 기존 이미지 그대로 저장하기 및 이미지 미리보기(프리뷰) 삭제하는 법

paintover23 2024. 1. 13. 10:52
728x90

 

 

Part1. 기존 저장된 이미지를 그대로 저장하기

트러블 슈팅

 

  • (문제배경) 마이페이지에서 기존 저장된 이미지를 삭제하거나 수정하지 않고 다른 정보만 수정하여 서버에 전송하면 기존 저장된 이미지가 그대로 유지되는 것이 아니라 삭제되는 문제가 발생한다.
    (문제원인) 현재 서버에서는 이미지 파일이 있으면 해당 주소를 저장하고 이미지 파일이 없으면 null을 저장하도록 코드를 구현해놨다.
let imageFile = req.file ? req.file.location : null
  • 널리 알려진대로, DB에는 이미지 파일을 저장하는 것보다 이미지를 S3 등 외부에 저장하고 그 저장한 URL을 DB에 저장하는 것이 권장된다. 이 때문에 마이페이지 GET 요청 시 이미지 파일 자체를 불러오는 것이 아니라 이미지 URL만 불러오므로 req.file은 항상 null이 되어 이러한 문제가 발생하는 것이다.

 

사고과정

  1. 유저가 기존 저장된 이미지를 그대로 유지하는 것에 대한 상태값 구분이 필요
  2. 서버는 1의 상태를 근거로 3가지 다른 방향으로 이미지를 저장할 수 있음:
    1. 이미지를 수정(또는 신규 생성한 경우) -> 해당 이미지의 URL을 DB에 저장
    2. 이미지가 삭제(또는 이미지가 없는 경우) -> null을 DB에 저장 
    3. 기존 이미지 변경이 없는 경우 -> 기존 이미지 URL을 DB에 저장

 

해결방법

1. 유저가 기존 저장된 이미지를 그대로 유지하는 것에 대한 상태값 구분이 필요

먼저 이것을 구현해보자. 이를 위해서는 DB에 저장된 이미지가 있는지 없는지 유무를 먼저 구분해줘야한다.

 

(mypage.ejs)

<div class="imgWrapper">
	<% if(result.img) { %>
	  <img src="<%= result.img %>" alt="image" class="myImg"></img>
	<% } else { %>
	  <img class="nullImg" src="#"></img>
	<% } %>
</div>
<input onchange="setThumbnail(event)" class="file" type="file" name="img1" accept="image/*" multiple />
<input class="sameImg" type="hidden" name="sameimg" value="" />
  • `result` 는 서버에서 뿌려준 데이터 이다.
  • DB에 저장된 프로필 이미지가 있는 경우는 해당 이미지를 보여주고, 없는 경우는 `nullImg`를 보여준다.
  • `sameImg` 는 기존 이미지가 그대로 유지되는 경우에 대한 상태를 서버에게 전달하기 위한 장치로 사용한다. 만약 기존 이미지가 변경되지 않은 경우에는 초기값 value="" 안에 기존 이미지 URL 주소를 넣어줄 것이다. input은 hidden 으로 설정하여 보이지 않게 한다.

 

(mypage.ejs)

<script>
	let img = document.querySelector(".myImg") 
	let nullImg = document.querySelector(".nullImg")
	let file = document.querySelector(".file")
	let sameImg = document.querySelector(".sameImg")

	 if("<%= result.img%>") {
	   let src = img.getAttribute("src")
		// 기존 저장된 이미지를 수정하지 않는 경우
	   if(src ==  "<%= result.img%>") {
		 console.log("이미지가 수정되지 않았습니다.")
		 sameImg.setAttribute("value", "<%= result.img%>")
		 console.log("서버에 보내는 이미지주소", sameImg.getAttribute("value"))
	   }
	 }
  • 같은 ejs 파일의 아래 스크립트를 열어 서버로 부터 받아온(=DB에 저장된 이미지가 있음) 이미지가 있는 경우에 대한 예외처리를 작성한다.  
  • 기존 이미지가 수정되지 않았다는 의미는 받아온 이미지의 url이 현재 url과 동일하다는 의미이므로 `src == "<%= result.img%>"` 인 경우, 아까 말한 sameImg의 value 값으로 `"<%= result.img` 을 넣어준다. 이제 서버에서는 이 value 값이 비어있으면 이미지가 수정된 것이고, value 값이 차 있으면 기존 이미지와 동일하다고 식별할 것이다.

 

2. 서버는 1의 상태를 근거로 3가지 다른 방향으로 이미지를 저장할 수 있음:


(mapage.js)

// 이미지 파일 저장
	// 1. 이미지 파일이 있으면 해당 주소를 저장, 2.없으면 null을 저장
	let imageFile = req.file ? req.file.location : null;
	
	// 3. 이미지 파일이 null 이면서 기존이미지가 수정되지 않은 경우
	// 그대로 기존 이미지 주소를 저장
	if (imageFile == null && req.body.sameimg) {
	  imageFile = req.body.sameimg;
	}
	
	await db.collection('user').updateOne(
	  { _id: new ObjectId(req.user._id) },
	  {
		$set: {
		  password: hash,
		  img: imageFile,
		},
	  }
	);
  • case1. 서버는 `req.file` 이 있는 경우 해당 file의 location을 DB에 저장한다.
  • case2. `req.file` 이 없는 경우는 null을 저장한다.
  • case3. `req.file` 이 null인데 `req.body.sameimg` 가 있는 경우는(= 이미지가 변경되지 않은 경우) 이미지 주소로  `req.body.sameimg` 을 저장한다. 

이렇게 하면 기존의 이미지를 그대로 저장할 수 있게된다!

 

 

2. Part2. 이미지 미리보기(프리뷰) 삭제하기

위와 같이 이미지 프리뷰를 삭제하는 방법을 알아보자. 같은 ejs 파일에 스크립트를 열어 프리뷰를 제거하는 함수를 만든다.

 

(mypage.ejs)

function deleteImg() {
  // 기존 저장된 이미지가 있다면
  if("<%= result.img%>") {
	img.removeAttribute("src")
  // 기존 저장된 이미지가 없다면
  } else {
	nullImg.removeAttribute("src")
  }
  file.value= null // 이미지 파일 삭제
  sameImg.setAttribute("value","")
}
  • 기본 골자는 삭제버튼을 누르면 img의 src를 제거하는 것인데 기존 저장된 이미지 있는지 유무에 따라 img 또는 nullImg로 구분하여 처리한다.
  • 유념할 점은 src 만 삭제하는 것이 아니라 `file.value= null`로 파일 자체를 완전히 삭제해 주는 것이다. (서버에서 req.file 로 읽기 때문이다)
  • 삭제하게 되면 기존 이미지와 달라지는 것이기 때문에 `sameImg.setAttribute` 의 value로 빈 값으로 만들어준다. 

 

배운점 & 복습한 점

  1. 서버가 유저한테 필요한 정보가 있을 때 input hidden을 적극적으로 사용할 수 있게 되었다.
  2. 인풋의 value를 가져오려면 name 값으로 받으면 된다. 
    `<input class="sameImg" type="hidden" name="sameimg" value="" />` 이면,
    서버에서는 `req.body.sameimg`로 접근함

 

전체 완성코드

 

(mypage.ejs)

<!-- 중략 -->
        <form
          class="form-box"
          action="/mypage/edit?_method=PUT"
          method="POST"
          enctype="multipart/form-data"
        >
          <h4 class="formbox-title">마이페이지</h4>
          <div class="deleteImg">X</div>
          <div class="imgWrapper">
            <% if(result.img) { %>
              <img src="<%= result.img %>" alt="image" class="myImg"></img>
            <% } else { %>
              <img class="nullImg" src="#"></img>
            <% } %>
          </div>
          <input onchange="setThumbnail(event)" class="file" type="file" name="img1" accept="image/*" multiple />
          <input class="sameImg" type="hidden" name="sameimg" value="" />
  
          <!-- 이미지 업로드, 프리뷰, 수정, 삭제 -->
          <script>
            let img = document.querySelector(".myImg") 
            let nullImg = document.querySelector(".nullImg")
            let file = document.querySelector(".file")
            let sameImg = document.querySelector(".sameImg")
  
             if("<%= result.img%>") {
               let src = img.getAttribute("src")
                // 기존 저장된 이미지를 수정하지 않는 경우
               if(src ==  "<%= result.img%>") {
                 console.log("이미지가 수정되지 않았습니다.")
                 sameImg.setAttribute("value", "<%= result.img%>")
                 console.log("서버에 보내는 이미지주소", sameImg.getAttribute("value"))
               }
             }
          
            // 파일 업로드 및 이미지 프리뷰
            function setThumbnail(event) {
              let reader = new FileReader()
            
              // 이미지파일 -> base64로 인코딩된 문자열로 저장
              reader.onload = function(event) {
                // 기존 저장된 이미지를 교체
                if("<%= result.img%>") {
                  img.setAttribute("src", event.target.result)
                // 신규 이미지 생성
                } else {
                  nullImg.setAttribute("src", event.target.result)
                }
              }
              // onload를 트리거 시킴
              reader.readAsDataURL(event.target.files[0])
            }
  
            // 미리보기 이미지 삭제
            document.querySelector(".deleteImg").addEventListener("click", deleteImg)
            
            function deleteImg() {
              // 기존 저장된 이미지가 있다면
              if("<%= result.img%>") {
                img.removeAttribute("src")
              // 기존 저장된 이미지가 없다면
              } else {
                nullImg.removeAttribute("src")
              }
              file.value= null // 이미지 파일 삭제
              sameImg.setAttribute("value","")
            }
          </script>
  
          <input name="username" value="<%=result.username %>" disabled />
          <input
            name="password"
            type="password"
            placeholder="수정할 비밀번호 입력"
          />
          <input name="password2" type="password" placeholder="비밀번호 재입력" />
          <div class="form-box-btn">
            <button type="submit">정보 수정하기</button>
          </div>
        </form>
      </div>
    </div>
    
  </body>
</html>

 

 

 

 

 

 

728x90
반응형