73일차 (1) - 파일 업로드 (회원가입시 프로필 이미지 처리)

Yohan·2024년 6월 8일
0

코딩기록

목록 보기
112/156
post-custom-banner

동기방식 파일업로드 처리 기본

UploadController

  • 파일 업로드의 경로와 jsp파일 처리, 파일명을 중복 없는 파일명으로 변경하여 파일 업로드 처리
  • 파일이 여러개면 List로 처리해서 반복문을 통해 해결할 수 있다.
@Controller
@Slf4j
public class UploadController {

    // 업로드 루트 경로
    private String rootPath = "C:/Users/smr78/OneDrive/바탕 화면/yocong/spring-prj/upload";

    @GetMapping("/upload/form")
    public String uploadForm() {
        return "upload/upload-form";
    }

    // MultipartFile : 파일 업로드시 필수
    // 파일이 여러개면 List<MultipartFile> 로 처리
    // @RequestParam("thumbnail") : js에서 name부분을 spring에서 file로 받음
    @PostMapping("/upload/file")
    public String uploadFile(
            @RequestParam("thumbnail") MultipartFile file
    ) {
        // 서버에서 전송된 파일의 정보 읽어옴
        log.info("file-name: {}", file.getOriginalFilename());
        log.info("file-size: {}MB", file.getSize() / 1024.0 / 1024.0);
        log.info("file-type: {}", file.getContentType());

        // 첨부파일 서버에 저장하기
        // 1. 루트 디렉토리를 생성
        File root = new File(rootPath);
        if (!root.exists()) root.mkdirs();

        // 2. 원본 파일명을 중복이 없는 랜덤 파일명으로 변경하여 파일 업로드
        FileUtil.uploadFile(rootPath, file);

        return "redirect:/upload/form";
    }


}
  • 서버에서 전송된 파일의 정보를 잘 읽어오는 것을 볼 수 있음

FileUtil

  • 파일명을 중복없는 새로운 파일명으로 바꿔줌
public class FileUtil {

    /**
     * 사용자가 클라이언트에서 파일을 전송했을 때
     * 중복이 없는 새로운 파일명을 생성하여 해당 파일명으로
     * 날짜별 폴더로 업로드하는 메서드
     *
     * @param file - 사용자가 업로드한 파일의 정보객체
     * @param rootPath - 서버에 업로드할 루트 디렉토리 경로
     *                   ex) D:/spring-prj/upload
     * @return - 업로드가 완료되었을 경우 업로드 된 파일의 위치 경로
     *                   ex)  /2024/05/29/djfalsjdflaksjlfdsaj_고양이.jpg
     */
    public static String uploadFile(String rootPath, MultipartFile file) {

        // 원본 파일명을 중복이 없는 랜덤 파일명으로 변경
        String newFileName = UUID.randomUUID() + "_" + file.getOriginalFilename();

        // 파일 업로드 수행
        try {
            file.transferTo(new File(rootPath, newFileName));
        } catch (IOException e) {
            e.printStackTrace();
        }

        return "";
    }
}
  • 중복없는 파일명이 생성된 것을 볼 수 있다.

upload-form.jsp

  • thumbnail이라는 name을 Controller에서 file로 불러오는 것임
<%@ 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">
    <title>Web Study</title>
</head>
<body>

<h1>파일 업로드 예제</h1>
  <form action="/upload/file" method="post" enctype="multipart/form-data">
    <%--   form태그의 enctype="multipart/form-data" 속성이 있어야 한다.  --%>
    <input type="file" name="thumbnail" id="img-input" accept="image/*">
    <button type="submit">전송</button>
  </form>

</body>
</html>

FileUtil - 날짜별로 관리하기 위해 날짜 폴더 생성

public class FileUtil {

    public static String uploadFile(String rootPath, MultipartFile file) {

        // 원본파일명을 중복이 없는 랜덤 파일명으로 변경
        String newFileName = UUID.randomUUID() + "_" + file.getOriginalFilename();

        // 이 첨부파일을 날짜별로 관리하기 위해 날짜 폴더를 생성
        String newUploadPath = makeDateFormatDirectory(rootPath);

        // 파일 업로드 수행
        try {
            file.transferTo(new File(newUploadPath, newFileName));
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 파일 전체 경로

        // fullPath:  D:/spring_prj/upload/2024/06/05/djlfsjdjsf_dog.png
        String fullPath = newUploadPath + "/" + newFileName;

        // url-path: /local/2024/06/05/djlfsjdjsf_dog.png
        String urlPath = "/local"+fullPath.substring(rootPath.length());

        // 업로드가 완료되면 데이터베이스에 파일의 경로 위치를 저장
        // ex) /local/2024/06/05/dkfjsldjfkslfjlds_dog.jpg
        return urlPath;
    }

    private static String makeDateFormatDirectory(String rootPath) {


        // 오늘 날짜 정보를 추출
        LocalDate now = LocalDate.now();
        int year = now.getYear();
        int month = now.getMonthValue();
        int day = now.getDayOfMonth();

        // int -> String으로 처리해줌
        List<String> dateList = List.of(year + "", len2(month), len2(day));

        // rootPath - D:/spring_prj/upload
        String newDirectoryPath = rootPath;

        // newDirectoryPath - D:/spring_prj/upload/2024/06/05
        for (String s : dateList) {
            newDirectoryPath += "/" + s;
            File f = new File(newDirectoryPath);
            if (!f.exists()) f.mkdir();
        }

        return newDirectoryPath;
    }

    // 한자리 수면 앞에 0이 붙게됨
    private static String len2(int n) {
        return new DecimalFormat("00").format(n);
    }
}

upload-form.jsp

  • 파일업로드의 input을 꾸미는 것보다 기존 input 태그를 숨기고 새로운 태그를 만들어서 꾸미고 input에 접근할 수 있게 만드는 것이 꾸밀 때 편하다.
<%@ 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" />
    <title>Web Study</title>

    <style>
      #img-input {
        display: none;
      }

      .upload-box {
        width: 150px;
        height: 150px;
        border: 3px dashed orange;
        display: flex;
        justify-content: center;
        align-items: center;
        color: red;
        font-weight: 700;
        cursor: pointer;
      }
    </style>
  </head>
  <body>
    <h1>파일 업로드 예제</h1>

    <div class="upload-box">여기를 눌러 파일을 올려주세요</div>

    <form action="/upload/file" method="post" enctype="multipart/form-data">
      <%-- form태그의 enctype="multipart/form-data" 속성이 있어야 한다. --%>
      <input type="file" name="thumbnail" id="img-input" accept="image/*" />
      <button type="submit">전송</button>
    </form>

    <script>
      document.querySelector('.upload-box').onclick = e => {
        document.getElementById('img-input').click();
      };
    </script>
  </body>
</html>

클라이언트 부분에서의 회원가입시 이미지 처리

sign-up.jsp

  • MultipartFile을 사용하면 enctype="multipart/form-data" 코드 필수
  • 회원가입시 프로필 이미지를 추가할 수 있도록함
<div class="card-body">
              <form
                action="/members/sign-up"
                name="signup"
                id="signUpForm"
                method="post"
                enctype="multipart/form-data"
              >

              <div class="profile">
                <div class="thumbnail-box">
                  <img src="/assets/img/image-add.png" alt="프로필 썸네일">
                </div>
  
                <label>프로필 이미지 추가</label>
  
                <input
                        type="file"
                        id="profile-img"
                        accept="image/*"
                        style="display: none;"
                        name="profileImage"
                >
              </div>
              
        <!-- 프로필 사진 관련 스크립트 -->
        <script>
          // 프로필 사진 동그라미 썸네일 부분
          const $profile = document.querySelector('.profile');
          // 실제 프로필사진이 첨부될 input
          const $fileInput = document.getElementById('profile-img');
    
          $profile.addEventListener('click', e => {
            $fileInput.click();
          });
    
          // 프로필 사진 선택시 썸네일 보여주기
          $fileInput.addEventListener('change', e => {
            console.log('file changed!');
            // 사용자가 첨부한 파일 데이터 읽기
            const fileData = $fileInput.files[0];
            // console.log(fileData);
    
            // 첨부파일 이미지의 로우데이터(바이트)를 읽는 객체 생성
            const reader = new FileReader();
    
            // 파일의 데이터를 읽어서 img태그에 src속성에 넣기 위해
            // 파일을 URL형태로 변경
            reader.readAsDataURL(fileData);
    
            // 첨부파일이 등록되는 순간 img태그에 이미지를 세팅
            reader.onloadend = e => {
              const $img = document.querySelector('.thumbnail-box img');
              $img.src = reader.result;
            };
    
          });
    
        </script>


서버에서의 회원가입시 이미지 정보 업로드

  • 화면에서 사진이 업로드 된다면 서버에서도 업로드가 되어야 함

MemberController

  • 업로드 경로와 회원가입시 서버에 프로필을 업로드하는 코드 추가
  • SignUpDto에도 private MultipartFile profileImage; 추가

루트 경로 숨기기 (보안)

  • 업로드 경로는 밖으로 드러나지 않고 숨기는 것이 좋다.

application.properties

  • application.properties에 업로드 경로를 써놓고

MemberController

  • Controller에서 @Value로 값을 받아서 경로를 숨겨서 사용한다.

DB에서 회원가입시 이미지 정보 업로드

  • 여러 프로필하고싶으면 새로운 테이블 추가 (1:M)
  • 지금은 1대1로 진행하니까 MEMBER테이블에 프로필이미지 컬럼 추가하는 방식으로 진행

Entity

  • DB와 1대1 매칭되는 객체에서도 프로필 이미지 업데이트

xml 업데이트

  • DB를 업데이트했으므로 xml에서도 save시 프로필 이미지가 업데이트 되도록함

업로드가 완료되면 데이터베이스에 파일의 경로 위치를 저장하기 위해 서버에 업로드 후 업로드 경로를 반환하도록 함

FileUtil - 파일 업로드 수행 후 파일 경로 반환 추가

MemberController

MemberService

  • join시 프로필이미지를 받아옴

LocalResourceConfig

  • 회원가입시 저장된 프로필 경로를 웹에 쳐보면 사진이 나오는 것을 볼 수 있다.
// 로컬 서버에 저장된 이미지를 웹브라우저에서 불러올 수 있도록
// 로컬 서버 파일경로를 웹 서버 URL로 변경하는 설정
@Configuration
public class LocalResourceConfig implements WebMvcConfigurer {

    @Value("${file.upload.root-path}")
    private String rootPath;

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {

        /*
            ResourceLocations : 로컬에 있는 경로
            ResourceHandler : 해당 로컬 경로를 web url로 변환

            ex )

            D:/xxx/dog.jpg

            local접근 -   file:D:/xxx/dog.jpg
            web접근 - http://localhost:8383/local/dog.jpg
         */
        registry
                .addResourceHandler("/local/**")
                .addResourceLocations("file:" + rootPath);
    }
}
  • 회원가입 시 프로필 이미지를 등록한 회원은 profile_img에 /local ~ 형식으로 파일의 경로를 저장하는 것을 볼 수 있다.
  • 또한 LocalResourceConfig의 설정에 의해 profile_img에 저장된 경로를 주소창에 치면 사진이 뜨는 것을 볼 수 있다.

profile
백엔드 개발자
post-custom-banner

0개의 댓글