inblog logo
|
Uni
    Springboot

    Blov-v3(리팩토링), 이미지 업로드하기

    이미지 업로드
    홍윤's avatar
    홍윤
    Sep 13, 2024
    Blov-v3(리팩토링), 이미지 업로드하기
    Contents
    1. nobody.png2. 사진 업로드 그림 그리기3. UserController 코드 수정4. header.mustache 수정5. 화면 실행6. 파일 처리 프로세스

    1. nobody.png

    notion image

    2. 사진 업로드 그림 그리기

    user/profile-form.mustache
    notion image
    {{> layout/header}} <div class="container p-5"> <!-- 요청을 하면 localhost:8080/login POST로 요청됨 username=사용자입력값&password=사용자값 --> <div class="card"> <div class="card-header"><b>프로필 사진을 등록해주세요</b></div> <div class="card-body d-flex justify-content-center"> <img src="/nobody.png" width="200px" height="200px"> </div> <div class="card-body"> <form> <div class="mb-3"> <input type="file" class="form-control" name="profile"> </div> <button type="submit" class="btn btn-primary form-control">사진변경</button> </form> </div> </div> </div> {{> layout/footer}}
     

    3. UserController 코드 수정

    @GetMapping("/api/user/profile-form") public String profileForm(){ return "user/profile-form"; }
     

    4. header.mustache 수정

    <li class="nav-item"> <a class="nav-link" href="/api/user/profile-form">프로필</a> </li>
    notion image
     

    5. 화면 실행

    notion image
     

    6. 파일 처리 프로세스

     

    6.1 WebConfig.java 수정하기

    // 웹서버 폴더 추가 @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { WebMvcConfigurer.super.addResourceHandlers(registry); // 1. 절대경로 file:///c:/upload/ // 2. 상대경로 file:./upload/ registry .addResourceHandler("/images/**") .addResourceLocations("file:" + "./images/") .setCachePeriod(60 * 60) // 초 단위 => 한시간 .resourceChain(true) .addResolver(new PathResourceResolver()); }
    • 코드설명
    💡
    이 코드는 Spring Boot에서 정적 리소스(이미지, CSS, JavaScript 등)를 특정 경로로 서빙하기 위한 설정입니다. 특히, 서버의 파일 시스템에 저장된 파일(예: 이미지)을 클라이언트가 요청할 때, 해당 파일을 제공할 수 있도록 경로를 설정하는 역할을 합니다.

    1. addResourceHandlers(ResourceHandlerRegistry registry)

    • 설명: Spring에서 정적 리소스를 처리하기 위한 경로를 설정하는 메서드입니다. 여기서는 /images/**로 시작하는 URL 요청을 처리하는 설정을 추가하고 있습니다.

    2. WebMvcConfigurer.super.addResourceHandlers(registry)

    • 설명: WebMvcConfigurer의 기본 동작을 유지하면서, 추가적인 리소스 경로 설정을 하기 위해 호출되는 메서드입니다. 생략해도 무방하지만, 기본 설정을 변경하고 싶지 않을 때 호출할 수 있습니다.

    3. registry.addResourceHandler("/images/**")

    • 설명: /images/**로 시작하는 URL 요청에 대한 매핑 경로를 정의합니다.
      • 예를 들어, 클라이언트가 localhost:8080/images/example.png를 요청하면, 이 설정에 따라 서버는 해당 파일을 찾기 위한 로직을 실행합니다.

    4. addResourceLocations("file:" + "./images/")

    • 설명: 실제 파일이 저장된 서버의 경로를 지정합니다.
      • file: 접두사는 파일 시스템 경로를 의미하며, **./images/*는 프로젝트의 상대 경로에서 images 디렉토리를 지정합니다.
      • 결과적으로, 서버는 ./images/ 디렉토리에서 파일을 찾게 됩니다.

    5. setCachePeriod(60 * 60)

    • 설명: 리소스를 캐시할 시간을 설정합니다.
      • 60 * 60은 초 단위로 1시간(3600초)을 의미합니다.
      • 클라이언트는 한 번 다운로드한 리소스를 1시간 동안 캐시할 수 있으며, 같은 리소스를 요청할 때 서버에 다시 요청하지 않고 캐시된 데이터를 사용하게 됩니다.

    6. resourceChain(true)

    • 설명: 리소스 처리 체인을 활성화합니다.
      • 이 설정은 정적 리소스를 처리할 때 성능을 최적화하거나 추가적인 리소스 처리를 적용할 수 있게 합니다.

    7. addResolver(new PathResourceResolver())

    • 설명: 리소스 경로를 해석하는 **PathResourceResolver*를 추가합니다.
      • *PathResourceResolver*는 요청된 리소스 경로를 기반으로 실제 파일을 찾는 역할을 합니다.
      • 서버가 /images/**로 들어오는 요청을 처리할 때, 이 경로에 해당하는 파일을 지정된 ./images/ 폴더에서 찾도록 도와줍니다.

    6.2 images 폴더 만들기 (사진도 추가해두기 nobody.png)

    notion image
     
     

    6.3 파일 저장 프로세스 만들기

     
    User
    package org.example.springv3.user; import jakarta.persistence.*; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import org.hibernate.annotations.CreationTimestamp; import java.sql.Timestamp; @Builder @Setter @Getter @Table(name = "user_tb") @NoArgsConstructor @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Column(unique = true, nullable = false) private String username; // 아이디 @Column(nullable = false) private String password; @Column(nullable = false) private String email; private String profile; @CreationTimestamp private Timestamp createdAt; @Builder public User(Integer id, String username, String password, String email, String profile, Timestamp createdAt) { this.id = id; this.username = username; this.password = password; this.email = email; this.profile = profile; this.createdAt = createdAt; } }
     
    MyFile
    package org.example.springv3.core.util; import org.example.springv3.core.error.ex.Exception500; import org.springframework.web.multipart.MultipartFile; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.UUID; public class MyFile { public static String 파일저장(MultipartFile file){ UUID uuid = UUID.randomUUID(); // uuid String fileName = uuid+"_"+file.getOriginalFilename(); // 롤링 Path imageFilePath = Paths.get("./images/"+fileName); try { Files.write(imageFilePath, file.getBytes()); } catch (Exception e) { throw new Exception500("파일 저장 오류"); } return fileName; } }
    • 코드설명
    💡
    이 코드는 파일을 서버에 저장하는 기능을 담당하는 유틸리티 클래스입니다. 주로 사용자가 업로드한 파일(예: 이미지 파일)을 서버의 특정 경로에 저장하고, 해당 파일의 이름을 반환하는 역할을 합니다. 이 코드에서는 UUID를 이용해 파일 이름을 고유하게 만든 후, 파일을 저장하는 로직을 구현했습니다.

    각 부분에 대한 설명:

    1. UUID uuid = UUID.randomUUID();

    • 설명: UUID(Universally Unique Identifier)를 생성합니다. UUID는 고유한 식별자로, 파일 이름의 충돌을 방지하기 위해 사용됩니다.
    • 목적: 파일 이름이 중복되지 않도록 고유한 식별자를 생성하는데, 이 식별자는 파일 이름의 앞에 붙게 됩니다.

    2. String fileName = uuid + "_" + file.getOriginalFilename();

    • 설명: 파일의 최종 이름을 구성합니다. 원본 파일 이름에 고유한 UUID를 붙여 파일 이름을 중복되지 않게 만듭니다.
      • 예를 들어, 사용자가 example.png라는 파일을 업로드하면, 이 코드는 UUID_example.png와 같은 형식의 이름을 생성합니다.

    3. Path imageFilePath = Paths.get("./images/" + fileName);

    • 설명: 파일을 저장할 경로를 설정합니다.
      • *Paths.get()*은 파일 시스템의 경로를 나타내며, 여기에 파일 이름을 포함시킵니다.
      • ./images/ 경로는 현재 작업 디렉토리 내의 images 폴더를 의미합니다. 즉, 파일이 images 폴더에 저장됩니다.

    4. Files.write(imageFilePath, file.getBytes());

    • 설명: 실제로 파일을 저장하는 부분입니다.
      • file.getBytes(): 업로드된 파일의 바이트 데이터를 가져옵니다.
      • Files.write(): 지정한 경로(imageFilePath)에 파일 데이터를 씁니다. 즉, 이 메서드가 호출되면 실제로 서버 디스크에 파일이 저장됩니다.

    5. return fileName;

    • 설명: 저장된 파일의 이름을 반환합니다. 고유한 UUID와 원본 파일 이름이 결합된 새로운 파일 이름을 반환하여, 서버나 클라이언트가 이 이름을 통해 파일을 다시 찾을 수 있도록 합니다.

    6. catch (Exception e)

    • 설명: 파일 저장 과정에서 오류가 발생했을 경우 예외를 처리합니다. 예를 들어 디스크에 쓰는 도중 오류가 발생할 경우, 사용자 정의 예외(Exception500)를 발생시켜 처리합니다.

    개선할 수 있는 부분:

    1. 파일 크기 제한: MultipartFile을 처리할 때 파일 크기를 제한하는 로직을 추가할 수 있습니다.
    1. 파일 형식 검증: 파일 확장자를 체크하여 허용된 형식(ex. 이미지 파일만 허용)만 저장되도록 할 수 있습니다.
     
    UserService
    @Transactional public void 프로필업로드(MultipartFile profile, User sessionUser){ String imageFileName = MyFile.파일저장(profile); // DB에 저장 User userPS = userRepository.findById(sessionUser.getId()) .orElseThrow(() -> new Exception404("유저를 찾을 수 없어요")); userPS.setProfile(imageFileName); } // 더티체킹 update됨
     
    UserController
    @PostMapping("/api/user/profile") public String profile(@RequestParam("profile") MultipartFile profile){ User sessionUser = (User) session.getAttribute("sessionUser"); userService.프로필업로드(profile, sessionUser); return "redirect:/api/user/profile-form"; } @GetMapping("/api/user/profile-form") public String profileForm(HttpServletRequest request) { User sessionUser = (User) session.getAttribute("sessionUser"); String profile = userService.프로필사진가져오기(sessionUser); request.setAttribute("profile", profile); return "user/profile-form"; }
     
    profile-form 그림 파일 수정 (머스테치)
    {{> layout/header}} <div class="container p-5"> <!-- 요청을 하면 localhost:8080/login POST로 요청됨 username=사용자입력값&password=사용자값 --> <div class="card"> <div class="card-header"><b>프로필 사진을 등록해주세요</b></div> <div class="card-body d-flex justify-content-center"> <img src="/images/{{profile}}" width="200px" height="200px"> </div> <div class="card-body"> <form action="/api/user/profile" method="post" enctype="multipart/form-data"> <div class="mb-3"> <input type="file" class="form-control" name="profile"> </div> <button type="submit" class="btn btn-primary form-control">사진변경</button> </form> </div> </div> </div> {{> layout/footer}}
    • 코드설명
    💡
    HTML에서 파일 업로드를 처리할 때는 반드시 <form> 태그에서 enctype="multipart/form-data" 속성을 사용해야 합니다. 이 속성은 폼 데이터가 인코딩되는 방식을 지정하며, 파일을 포함한 데이터는 일반적인 폼 인코딩 방식으로 처리할 수 없기 때문에 이 속성이 필수입니다.

    enctype="multipart/form-data"란?

    • *enctype*은 "encryption type"의 줄임말로, 폼 데이터를 서버로 전송할 때 인코딩되는 방식을 지정합니다. HTML의 <form> 태그에서 사용되며, 기본적으로 다음과 같은 세 가지 방식이 있습니다:
    1. application/x-www-form-urlencoded (기본 값)
    1. multipart/form-data
    1. text/plain
    이 중 파일 업로드를 처리하려면 반드시 **multipart/form-data**를 사용해야 합니다.

    multipart/form-data의 필요성

    1. 기본 인코딩 방식: application/x-www-form-urlencoded

    기본 인코딩 방식은 텍스트 데이터를 URL 인코딩하여 서버로 전송하는 방식입니다. 예를 들어, 일반 텍스트 필드나 체크박스, 라디오 버튼 등의 데이터를 처리할 때는 이 방식이 사용됩니다. 그러나 이 방식은 파일과 같은 바이너리 데이터를 처리할 수 없습니다.
    • 텍스트 필드만 처리할 경우:
      • 예: name=John&age=30

    2. multipart/form-data를 사용해야 하는 이유

    파일은 텍스트가 아닌 바이너리 데이터이므로, 이를 URL 인코딩 방식으로는 처리할 수 없습니다. 파일을 업로드할 때는 폼 데이터를 여러 부분(part)로 나누어 각각의 필드를 따로 인코딩하는 방식인 **multipart/form-data**를 사용해야 합니다. 이 방식에서는 각 폼 필드(파일, 텍스트 등)가 각각 독립된 파트로 나뉘고, 파일 데이터는 바이너리 형태로 전송됩니다.
    • 파일과 텍스트 데이터를 처리할 경우:
      • 각각의 데이터가 경계를 나누어 전송됩니다.
      • 파일은 바이너리로 전송되고, 텍스트는 각 부분에 인코딩되어 전송됩니다.

    multipart/form-data가 어떻게 동작하는지:

    • 텍스트 필드는 일반 텍스트로 전송되지만, 파일은 바이너리로 전송됩니다.
    • 서버에서 각 데이터 파트를 구분할 수 있도록 경계를 설정하고, 각각의 필드를 독립된 데이터 블록으로 처리합니다.
    Share article
    Contents
    1. nobody.png2. 사진 업로드 그림 그리기3. UserController 코드 수정4. header.mustache 수정5. 화면 실행6. 파일 처리 프로세스

    Uni

    RSS·Powered by Inblog