1. nobody.png

2. 사진 업로드 그림 그리기
user/profile-form.mustache

{{> 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>

5. 화면 실행

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)

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
)를 발생시켜 처리합니다.
개선할 수 있는 부분:
- 파일 크기 제한:
MultipartFile
을 처리할 때 파일 크기를 제한하는 로직을 추가할 수 있습니다.
- 파일 형식 검증: 파일 확장자를 체크하여 허용된 형식(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>
태그에서 사용되며, 기본적으로 다음과 같은 세 가지 방식이 있습니다:
application/x-www-form-urlencoded
(기본 값)
multipart/form-data
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