웹 서비스에서 파일 업로드 기능은 매우 자주 사용된다. 이번 글에서는 Spring Boot에서 파일 업로드 시 처리해야 할 주요 작업들과 실제 파일 저장 로직, 그리고 유효성 검증 및 예외 처리에 대해 정리해보려고 한다.
업로드된 파일을 서버에 저장할 때 원래 파일명을 그대로 저장하면, 같은 이름의 파일이 업로드될 경우 기존 파일이 덮어 써지는 문제가 발생한다. 이를 방지하기 위해 UUID를 활용하여 고유한 파일명을 생성해 저장해주어야 한다.
String changedName = UUID.randomUUID().toString() + "." + extension;
파일 확장자는 파일명에서 마지막 .
이후의 문자열로 얻을 수 있다.
String extension = originalName.substring(originalName.lastIndexOf(".") + 1);
lastIndexOf(".")
를 사용하면 파일명 중 마지막 점(.
)의 위치를 기준으로 확장자를 정확히 추출할 수 있다.
파일은 디스크에 저장할 때 폴더 + 파일명으로 경로가 완성되어야 한다.
이 경로를 코드에 하드코딩하기보다는 application.yml
에 저장하고 @Value
로 주입받는 게 유지보수에 유리하다.
application.yml
file:
upload-dir: C:/uploads/myfile/files
코드에서 주입받기
@Value("${file.upload-dir}")
private String uploadUrl;
하드코딩(
String path = "C:/..."
)보다 설정 파일을 사용하는 것이 환경 변경에 유리하고, 코드 수정 없이 배포 가능하다.
업로드 디렉토리가 존재하지 않으면 직접 만들어주어야 한다.
File directory = new File(uploadUrl);
if (!directory.exists()) {
directory.mkdirs();
}
업로드된 파일을 서버 디스크에 저장하려면 transferTo()
메서드를 꼭 호출해줘야 한다. 이걸 하지 않으면 DB에 경로만 저장되고 실제 파일은 존재하지 않는 상황이 생긴다.
File saveFile = new File(uploadUrl + File.separator + changedName);
file.transferTo(saveFile); // 서버 디스크에 저장
디렉토리가 존재하지 않는다면 mkdirs()
로 먼저 만들어줘야 한다.
@Transactional
public Object createPostWithFile(PostRequestDTO postRequestDTO, List<MultipartFile> files) {
Post post = new Post(postRequestDTO.getTitle(), postRequestDTO.getContent());
if (!files.isEmpty()) {
File directory = new File(uploadUrl);
if (!directory.exists()) {
directory.mkdirs();
}
for (MultipartFile file : files) {
validateFile(file);
String originalName = file.getOriginalFilename();
String extension = originalName.substring(originalName.lastIndexOf(".") + 1);
String changedName = UUID.randomUUID().toString() + "." + extension;
String path = uploadUrl + File.separator + changedName;
try {
file.transferTo(new File(path));
} catch (IOException e) {
throw new RuntimeException("파일 저장 실패", e);
}
Attachment newAttachment = Attachment.builder()
.name(originalName)
.changedName(changedName)
.path(path)
.size(file.getSize())
.extension(extension)
.build();
post.addAttachment(newAttachment);
}
}
postRepository.save(post);
return "저장 완료";
}
업로드 가능한 파일에 대해 몇 가지 기준을 두고 필터링해야 한다.
아래 3가지는 기본적으로 검사해주는 게 좋다.
.jpg
, .png
, .pdf
등 허용된 확장자만 업로드 가능private void validateFile(MultipartFile file) {
if (file.getOriginalFilename() == null) {
throw new IllegalArgumentException("유효하지 않은 파일명입니다.");
}
if (file.getSize() > MAX_FILE_SIZE) {
throw new IllegalArgumentException("파일 크기는 100MB를 초과할 수 없습니다.");
}
String extension = file.getOriginalFilename()
.substring(file.getOriginalFilename().lastIndexOf(".") + 1)
.toLowerCase();
if (!ALLOWED_EXTENSIONS.contains(extension)) {
throw new IllegalArgumentException("허용되지 않은 파일 확장자입니다. 허용 확장자: " + ALLOWED_EXTENSIONS);
}
}
잘못된 요청에 대해서는 IllegalArgumentException
등 구체적인 예외를 던져, 클라이언트가 문제를 쉽게 파악할 수 있도록 한다.
IllegalArgumentException
:인자가 잘못되었을 때 발생하는 예외
정리한 파일 업로드 로직은 다음과 같은 요구사항을 충족한다: