사진 업로드 (save) - DB 공유

coding S's avatar
Apr 26, 2024
사진 업로드 (save) - DB 공유

[ save-form.mustache 수정 ]

{{> layout/header}} <div class="container" style="margin-top: 5%; margin-bottom: 5%"> <div class="row"> <!-- 이미지와 상품 정보를 함께 업로드하는 섹션 --> <div class="col-lg-15 mb-4 mb-lg-0"> <form id="productForm" action="/product/save" method="post" enctype="multipart/form-data"> <div class="row"> <div class="form-group mt-3 col-3" style="margin-left: 10%;"> <img id="previewImg" src="/upload/default.png" width="300" height="300" style="border-radius: 5%;"> <div class="mt-3"> <input type="file" class="form-control" id="imgFile" name="imgFile" accept="image/*"> </div> </div> <div class="col-lg-6"> <div class="mb-3 mt-3"> 상 품 명 : <input id="name" name="name" type="text" class="form-control" placeholder="상품명을 입력하세요"> <div class="alert alert-danger" id="nameCheck">상품명을 입력해주세요</div> </div> <div class="mb-3 mt-3"> 상품가격 : <input id="price" name="price" type="text" class="form-control" placeholder="상품가격을 입력하세요"> </div> <div class="mb-3 mt-3"> 상품수량 : <input id="qty" name="qty" type="text" class="form-control" placeholder="상품수량을 입력하세요"> </div> <div class="d-flex justify-content-center"> <button type="submit" class="btn btn-success mt-2">상품등록완료</button> </div> </div> </div> </form> </div> <!-- 이미지와 상품 정보를 함께 업로드하는 섹션 --> </div> </div> <script> document.addEventListener("DOMContentLoaded", function() { // imgFile input 필드에 event listener 추가 document.getElementById('imgFile').addEventListener('change', function(event) { var output = document.querySelector('img'); // 미리보기를 할 이미지 태그 선택 if (event.target.files && event.target.files[0]) { // FileReader 객체를 이용해 파일을 읽음 var reader = new FileReader(); reader.onload = function(e) { output.src = e.target.result; // 읽은 파일의 내용을 img 태그의 src 속성에 할당 output.style.display = 'block'; // 이미지 태그를 화면에 표시 }; reader.readAsDataURL(event.target.files[0]); // 파일 읽기 시작 } }); }); </script> <script src="/js/name-check.js"></script> {{> layout/footer}}
💡
multipart/form-data 로 필요한 데이터 한 번에 받기 해당 스크립트는 미리 보기 띄워주는 스크립트
 

[ 이미지 미리보기 ]

notion image
이미지 미리보기를 하기 위해선 자바 스크립트 코드가 필요하다 파일 입력 필드에 change 이벤트 리스너를 추가하고, 사용자가 파일을 선택하면 해당 파일을 읽어서 이미지 태그의 소스로 설정해야 함! 이벤트 리스너를 발동시키는데, DOMContentLoaded 라는 이벤트임. 스크립트가 문서의 어디에 위치하든지 간에 안전하게 DOM 요소에 접근하고 싶다면 DOMContentLoaded 이벤트를 사용 (*<html>, <head>, <title>, <body>, <h1>, <p> 등의 각 태그는 DOM의 요소)
 

[ script 설명 ]

페이지가 다 불러와지면: 사용자가 웹페이지에 접속하고 모든 요소가 준비가 되면, 이 스크립트가 실행 사진 선택하기: 사용자가 '파일 선택' 입력란에서 사진을 선택하면, 스크립트가 그 사진을 처리하기 시작 사진 읽기: 선택된 사진을 읽기 위해 FileReader라는 도구를 사용. 이 도구는 선택한 사진을 웹페이지에서 바로 볼 수 있는 형태로 변환. 사진 보여주기: FileReader가 사진을 읽으면, 그 결과물을 웹페이지에 있는 <img> 태그의 src 속성에 넣어준다. 이렇게 하면 선택한 사진을 웹페이지에서 바로 볼 수 있게 된다. 즉, 사용자가 파일을 선택하면, 그 파일이 바로 웹페이지에 보여지도록 하는 기능

코드 설명

document.addEventListener("DOMContentLoaded", function() { 이 줄은 웹 페이지가 모두 로드되고 준비되었을 때, 즉 HTML 문서의 구조가 완성되었을 때 실행될 함수를 등록. "DOMContentLoaded" 이벤트는 외부 리소스(이미지, 스타일시트 등)의 로드 여부와 상관없이, HTML 문서의 로드가 완료되면 발생 document.getElementById('imgFile').addEventListener('change', function(event) { 여기서는 'imgFile'이라는 ID를 가진 HTML 요소(이 경우 <input type="file">)에 'change' 이벤트 리스너를 추가. 사용자가 파일을 선택하면 이 이벤트가 발생. var output = document.querySelector('img'); // 미리보기를 할 이미지 태그 선택 이 코드는 <img> 태그를 찾아서 output이라는 변수에 저장. 이 <img> 태그는 나중에 사용자가 선택한 이미지 파일의 미리보기로 사용된다. if (event.target.files && event.target.files[0]) { 사용자가 파일을 하나 이상 선택했는지 확인. event.target.files는 사용자가 선택한 파일들의 목록. event.target.files[0]는 그 목록의 첫 번째 파일을 가리킨다. var reader = new FileReader(); FileReader 객체를 생성. 이 객체를 사용하여 사용자가 선택한 파일을 읽을 수 있다. reader.onload = function(e) { 파일이 성공적으로 읽혀졌을 때 발생하는 onload 이벤트에 대한 처리 함수를 정의한다. e는 이벤트 객체로, 읽혀진 파일의 결과를 포함한다. output.src = e.target.result; // 읽은 파일의 내용을 img 태그의 src 속성에 할당 읽혀진 파일의 내용(이미지 데이터)을 output 변수에 저장된 <img> 태그의 src 속성에 할당한다. 이렇게 하면 웹 페이지에 이미지가 표시된다. output.style.display = 'block'; // 이미지 태그를 화면에 표시 <img> 태그의 스타일을 변경하여 화면에 보이도록 한다. 기본적으로 <img> 태그는 보이지만, 특정 상황에서는 숨겨져 있을 수 있으니 display 속성을 'block'으로 설정하여 확실히 보이게 함. reader.readAsDataURL(event.target.files[0]); // 파일 읽기 시작 FileReader 객체의 readAsDataURL 메소드를 사용하여 사용자가 선택한 첫 번째 파일을 읽기 시작. 이 메소드는 파일의 내용을 데이터 URL로 변환. 파일이 성공적으로 읽히면 앞서 정의한 onload 함수가 실행된다.
 
 

[ Product 테이블 ]

notion image
굳이 이미지를 위한 테이블을 만들 필요는 없을듯함. product 받을 때 필드로 이미지를 받으면 됨
 

[ ProductRequest ]

@Data public static class SaveDTO { private String name; private Integer price; private Integer qty; private MultipartFile imgFile; }
💡
일단 클라이언트에게 MultipartFile 로 받음. 이미지는 byte니까 …
 

[ ProductController ]

// 상품 등록 @PostMapping("/product/save") public String save(ProductRequest.SaveDTO requestDTO) { System.out.println(requestDTO); MultipartFile imgFile = requestDTO.getImgFile(); String imgFileName = UUID.randomUUID() + "_" + imgFile.getOriginalFilename(); // Path imgPath = Paths.get("./src/main/resources/static/upload/" + imgFileName); Path imgPath = Paths.get("./upload/" + imgFileName); try { //upload 디렉토리가 존재하지 않는다면, 서버가 시작될 때 해당 디렉토리를 자동으로 생성하는 코드 //static에 안 넣으려고 설정해줬나봄 Files.createDirectories(imgPath.getParent()); Files.write(imgPath, imgFile.getBytes()); productService.save(requestDTO, imgFileName); } catch (IOException e) { throw new RuntimeException(e); } return "redirect:/product"; }
[ 이미지 변환 순서 ] 이미지 파일을 바이트 배열로 읽어옴 -> 바이트 배열을 Base64로 인코딩하여 문자열로 변환 -> 변환된 문자열을 HTTP를 통해 전송 -> Base64로 인코딩된 문자열 데이터를 받음 -> 문자열을 Base64 디코딩하여 원래의 바이트 배열로 변환 -> 바이트 배열을 사용하여 이미지 파일을 복원하거나 필요한 작업을 수행 그걸 MultipartFile imgFile = requestDTO.getImgFile(); 이 객체가 알아서 해준다! 그리고 reqeuestDTO와 imgFileName을 DB에 저장해줌 * imgFileName은 UUID(롤링)해서 'DB'에 이미지 '주소'를 저장한 다음, 불러올 때 이 이미지 '주소'를 불러오는 것. 이미지 자체는 서버에 저장 됨.
💡
Files.write() 메서드를 사용하여 바이너리 데이터(이 경우 imgFile.getBytes())를 파일 시스템의 특정 경로(imgPath)에 쓰는 작업을 수행
 

[ ProductService ]

//상품 등록 @Transactional public void save(ProductRequest.SaveDTO requestDTO, String imgFileName) { productRepo.save(requestDTO, imgFileName); }
 

[ ProductRepository ]

//상품 등록 public void save(ProductRequest.SaveDTO requestDTO, String imgFileName) { String q = """ insert into product_tb(name, price, qty, img_file_name, created_at) values (?, ?, ?, ?, now()) """; Query query = em.createNativeQuery(q); query.setParameter(1, requestDTO.getName()); query.setParameter(2, requestDTO.getPrice()); query.setParameter(3, requestDTO.getQty()); query.setParameter(4, imgFileName); query.executeUpdate(); }
 

[ 이미지가 보여야할 곳에 머스태치 뿌리기 ]

1. detail.mustache

{{> layout/header}} <div class="container" style="margin-top: 5%; margin-bottom: 5%"> <!-- enctype="multipart/form-data"--> <div class="row"> <!-- 이미지 --> <div class="col-lg-4 mb-4 mb-lg-0"> <img src="/upload/{{product.imgFileName}}" width="300" height="300" style="border-radius: 5%"> </div> <!-- 상품 정보 입력 --> <div class="col-lg-6 px-xl-10"> <div class="mb-3 mt-3"> 상 품 명 : <input name="name" type="text" class="form-control" value="{{product.name}}" disabled> </div> <div class="mb-3 mt-3"> 상품가격 : <input name="price" type="text" class="form-control" value="{{product.price}}" disabled> </div> <div class="mb-3 mt-3"> 상품수량 : <input name="qty" type="text" class="form-control" value="{{product.qty}}" disabled> </div> <div class="d-flex justify-content-center"> <a href="/product/{{id}}/update-form"> <button type="submit" class="btn btn-primary mt-3">수정</button> </a> <form action="/product/{{id}}/delete" method="post"> <button type="submit" class="btn btn-danger mt-3">삭제</button> </form> </div> </div> </div> </div> <script src="/js/detail.js"></script> {{> layout/footer}}
@GetMapping("/product/{id}") public String detail(@PathVariable Integer id, HttpServletRequest request) { ProductResponse.DetailDTO product = productService.findByIdDetail(id); request.setAttribute("product", product); System.out.println(product); return "/product/detail"; }
💡
해당 코드로 id를 찾으면, product에 img가 함께 저장되어 있으므로 찾아올 수 있음! index.mustache 에서도 동일하게 찾아서 넣으면 됨
 

[ 미리 보기 화면 ]

notion image
 

[ 결과 화면 ]

notion image
 
Share article

codingb