메타코딩 SNS프로젝트

26. 프로필 페이지 - Image를 서버에 업로드하기(UUID활용)

정현3 2022. 6. 20. 18:10

Image의 모델링이 완료되었으니, 다음으로는 서버에 Image를 전송하여 저장하도록 만들어주어야 한다.

우선 ImageController로 가서 업로드할 컨트롤러를 만들어주자.

package com.cos.photogramstart.web;

import com.cos.photogramstart.config.PrincipalDetails;
import com.cos.photogramstart.service.ImageService;
import com.cos.photogramstart.web.dto.Image.ImageUploadDto;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

@RequiredArgsConstructor
@Controller
public class ImageController {

    private final ImageService imageService;

    @GetMapping({"/", "/image/story"})
    public String story() { //기본페이지
        return "image/story";
    }

    @GetMapping("/image/popular")
    public String popular() { //인기페이지
        return "image/popular";
    }

    @GetMapping("/image/upload")
    public String upload() { //사진 등록을 위한 페이지
        return "image/upload";
    }

    @PostMapping("/image")
    public String imageUpload(ImageUploadDto imageUploadDto,
                              @AuthenticationPrincipal PrincipalDetails principalDetails) {

        //서비스 호출
        imageService.사진업로드(imageUploadDto, principalDetails);

        return "redirect:/user/"+principalDetails.getUser().getId();

    }
}

-> 이 '메서드'는 어떤 '매개변수'를 받아야 할까?

-> 당연히 Image에서 만든 데이터를 받아주어야 하는데, Image오브젝트와는 다른 정보를 받을것이다.

바로 PostImageUrl인데, 이 컨트롤러에서 우리는 url(경로)가 아닌 'Image 파일'을 받을것이다. 

때문에 Dto를 따로 만들어주어야 한다.

Dto/image 경로에 ImageUploadDto 클래스를 만들어주자.

Image를 업로드할때, 두개의 input태그인 '사진파일'과 '사진설명'에 관한 데이터를 받아주자.

<!--사진업로드 Form-->
<form class="upload-form" action="/image" method="post" enctype="multipart/form-data">
    <input  type="file" name="file"  onchange="imageChoose(this)"/>
    <div class="upload-img">
        <img src="/images/person.jpeg" alt="" id="imageUploadPreview" />
    </div>
    
    <!--사진설명 + 업로드버튼-->
    <div class="upload-form-detail">
           <input type="text" placeholder="사진설명" name="caption" />
        <button class="cta blue">업로드</button>
    </div>
    <!--사진설명end-->
package com.cos.photogramstart.web.dto.Image;

import lombok.Data;
import org.springframework.web.multipart.MultipartFile;

@Data
public class ImageUploadDto {

    private MultipartFile file;
    private String caption;

}

사진파일의 타입은 MultipartFile로 받아주면된다

MultipartFile로 받아주면 여러가지 타입의 파일을 모두 받아줄 수 있기 때문이다.

가령 jpg, jpeg, png, gif처럼 타입이 나누어져 있는데, 이걸 모두 받아줄 수 있다. 

Dto를 만들어주었으니 Controller로 돌아가서 이 Dto를 '매개변수'로 받아주자.

 @PostMapping("/image")
    public String imageUpload(ImageUploadDto imageUploadDto,
                              @AuthenticationPrincipal PrincipalDetails principalDetails) {

        //서비스 호출
        imageService.사진업로드(imageUploadDto, principalDetails);

        return "redirect:/user/"+principalDetails.getUser().getId();

    }
}

그리고 또 받아주어야 하는 정보는 누가 올렸는지에 대한 '세션정보'도 있다

return 해줄 페이지는 Image를 올린 user를 찾아가야 한다.

Controller는 얼추 완성되었으니 , ImageUpload 로직을 담당할 Service를 만들어주자

package com.cos.photogramstart.service;

import com.cos.photogramstart.config.PrincipalDetails;
import com.cos.photogramstart.domain.Image.ImageRepository;
import com.cos.photogramstart.web.dto.Image.ImageUploadDto;
import lombok.RequiredArgsConstructor;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.UUID;

@RequiredArgsConstructor
@Service
public class ImageService {

    private final ImageRepository imageRepository;

    @Value("${file.path}")
    private String uploadFolder = "c:/workspace/springbootwork/upload/";

    public void 사진업로드(ImageUploadDto imageUploadDto, PrincipalDetails principalDetails) {

        UUID uuid = UUID.randomUUID(); //범용 고유 식별자

        String imageFileName = uuid+"_"+ imageUploadDto.getFile().getOriginalFilename(); //1.jpg

        System.out.println("이미지 파일 이름 : " + imageFileName);

        Path imageFilePath = Paths.get(uploadFolder+imageFileName);

        //통신, I/O -> 예외가 발생할 수 있다.
        try {
            Files.write(imageFilePath, imageUploadDto.getFile().getBytes());
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}

-> 이제 Controller로 돌아가서 Service를 DI해주고 메서드를 호출해보자

 @PostMapping("/image")
    public String imageUpload(ImageUploadDto imageUploadDto,
                              @AuthenticationPrincipal PrincipalDetails principalDetails) {

        //서비스 호출
        imageService.사진업로드(imageUploadDto, principalDetails);

        return "redirect:/user/"+principalDetails.getUser().getId();

    }
}

-> Controller는 완성되었으니, ImageUpload 로직을 완성하러 가보자

 

-> 혼선을 방지하기 위해 우리가 사용할 기술은 UUID(범용 고유 식별자)이다

UUID(Universally Unique IDentifier)는 고유성(Unique)가 보장하는 id를 만들 수 있게 해주는 기술이다

128비트의 숫자로써, 총 32자리의 16진수의 숫자를 8-4-4-4-12 자리로 하이픈이 추가되서 구분한다. 적용시켜보자

package com.cos.photogramstart.service;

import com.cos.photogramstart.config.PrincipalDetails;
import com.cos.photogramstart.domain.Image.ImageRepository;
import com.cos.photogramstart.web.dto.Image.ImageUploadDto;
import lombok.RequiredArgsConstructor;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.UUID;

@RequiredArgsConstructor
@Service
public class ImageService {

    private final ImageRepository imageRepository;

    //6. 이미지가 저장되는 경로 호출하기
    @Value("${file.path}")
    private String uploadFolder = "c:/workspace/springbootwork/upload/";

    public void 사진업로드(ImageUploadDto imageUploadDto, PrincipalDetails principalDetails) {

        //2. UUID 객체를 생성한다
        UUID uuid = UUID.randomUUID(); //범용 고유 식별자

        //1. 업로드 되는 원본 파일명을 imageFileName이라고 지정한다.
        //3. UUID를 더한 값으로 지정한다
        String imageFileName = uuid+"_"+ imageUploadDto.getFile().getOriginalFilename(); //1.jpg

        //4. UUID가 적용된 파일명 확인하기
        System.out.println("이미지 파일 이름 : " + imageFileName);

        //5. Image 저장 경로 확인하기
        Path imageFilePath = Paths.get(uploadFolder+imageFileName); //이미지경로

        //7. 파일을 업로드하기
        //통신, I/O -> 예외가 발생할 수 있다.
        try {
            Files.write(imageFilePath, imageUploadDto.getFile().getBytes());
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}
server:
  port: 8080
  servlet:
    context-path: /
    encoding:
      charset: utf-8
      enabled: true
    
spring:
  mvc:
    view:
      prefix: /WEB-INF/views/
      suffix: .jsp
      
  datasource:
    driver-class-name: org.mariadb.jdbc.Driver
    url: jdbc:mariadb://localhost:3306/photogrammmmm?serverTimezone=Asia/Seoul
    username: root
    password: 12341234
    
  jpa:
    open-in-view: true
    hibernate:
      ddl-auto: update
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
    show-sql: true
      
  servlet:
    multipart:
      enabled: true
      max-file-size: 2MB

  security:
    user:
      name: test
      password: 1234   

file:
  #  path: C:/workspace/springbootwork/upload/
  path: /Users/jeonghyunlee/Desktop/EaszUp-Springboot-Photogram-Start/upload/

이렇게 완성된 path를 file에 업로드해주자

'파일업로드'를 try-catch로 묶는 이유는 통신이나 I/O가 진행될때, '예외'가 발생할 수 있기 때문이다. 

가령 , 1/jpg파일을 불러오는 요청을 할때, 1.jpa파일이 없을수도 있기 때문이다. 

우선 Service는 여기까지 만들고, upload form태그에 action을 걸어주고 테스트해보자.

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
   
   <%@ include file="../layout/header.jsp" %>

    <!--사진 업로드페이지 중앙배치-->
        <main class="uploadContainer">
           <!--사진업로드 박스-->
            <section class="upload">
               
               <!--사진업로드 로고-->
                <div class="upload-top">
                    <a href="home.html" class="">
                        <img src="/images/logo.jpg" alt="">
                    </a>
                    <p>사진 업로드</p>
                </div>
                <!--사진업로드 로고 end-->
                
                <!--사진업로드 Form-->
                <form class="upload-form" action="/image" method="post" enctype="multipart/form-data">
                    <input  type="file" name="file"  onchange="imageChoose(this)"/>
                    <div class="upload-img">
                        <img src="/images/person.jpeg" alt="" id="imageUploadPreview" />
                    </div>
                    
                    <!--사진설명 + 업로드버튼-->
                    <div class="upload-form-detail">
                           <input type="text" placeholder="사진설명" name="caption" />
                        <button class="cta blue">업로드</button>
                    </div>
                    <!--사진설명end-->
                    
                </form>
                <!--사진업로드 Form-->
            </section>
            <!--사진업로드 박스 end-->
        </main>
        <br/><br/>
   
   <script src="/js/upload.js" ></script>
    <%@ include file="../layout/footer.jsp" %>