정적 파일사용자에 따라 변할 필요 없이 항상 똑같은 모습으로 보여야 할 파일들을 정적 파일(png, js 등)이라고 부른다.
multipart/form-dataenctype 속성HTML 에서 JS 없이 데이터를 전송(HTTP 요청)할때는 form 요소를 사용한다. form 요소는 자기 내부에 있는 input 요소(ex. 타입 file) 들이 들고 있는 데이터를 수합해서 action 속성에 정의된 URL로 요청을 보내는데, 이 때 이 데이터를 어떻게 인코딩할 것인지를 결정하는 방법이 enctype 속성이다.enctype 종류application/x-www-form-urlencoded (기본값)multipart/form-data <form enctype="multipart/form-data">
<input type="text" name="name">
<input type="file" name="photo">
<input type="submit">
</form>
MultipartFile 받기 & 돌려주기application.yaml 설정static-path-patternstatic-path-pattern: /static/** 로 설정하면, 클라이언트는 /static/ 경로를 통해 정적 파일에 접근할 수 있다.static-locationsstatic-locations: classpath:/static, file:media/ 로 설정하면, 정적 파일을 classpath:/static 과 file:media/ 디렉토리에서 검색한다.file:media/ : 현재 실행중인 경로의 media 라는 폴더classpath:/static : 빌드된 어플리케이션의 클래스패스의 /static 경로 (즉, resources/static)spring:
mvc:
# 어떤 URL 요청을 받았을 때 정적 파일을 돌려줄 것인가
static-path-pattern: /static/**
web:
resources:
# 정적 파일을 검색할 위치 설정
# "/static/**"로 요청된 정적 파일은 "classpath:/static"과 "file:media/"에서 검색됩니다.
static-locations: classpath:/static, file:media/
# Multipart 요청 크기 제한
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB
동작 과정file:media/ 디렉토리에 저장됨 http://localhost:8080/static/facebook.png 로 요청을 보낸다.static-path-pattern : /static/** 설정에 따라 /static/** 경로가 정적 파일 요청으로 매핑된다.static-locations 에 설정된 디렉토리에서 해당 파일을 검색한다.classPath:/static/facebook.png 에서 검색한다.file:media/facebook.png 에서 검색한다.코드Controller@PutMapping("/{userId}/avatar")
public UserDto avatar(
@PathVariable("userId")
Long userId,
@RequestParam("image")
MultipartFile imageFile
) {
return service.updateUserAvatar(userId, imageFile);
}
ServiceRequestParam 으로 전달받은 MultipartFile 은 transferTo() 메소드를 이용해 저장할 수 있다.
public UserDto updateUserAvatar(
Long id,
MultipartFile image
) {
// 1. 유저의 존재 확인
Optional<User> optionalUser = repository.findById(id);
if (optionalUser.isEmpty())
throw new ResponseStatusException(HttpStatus.NOT_FOUND);
// 2. 파일을 어디에 업로드할 것인지 결정
// media/{id}/profile.{확장자}
// 2-1. (없다면) 폴더를 만들어야 한다. (media/{id})
String profileDir = String.format("media/%d/", id);
try {
// 주어진 Path를 기준으로, 없는 모든 디렉토리를 생성하는 메서드
Files.createDirectories(Path.of(profileDir));
} catch (IOException e) {
// 폴더를 만드는데 실패하면 기록을 하고 사용자에게 전달
log.error(e.getMessage());
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR);
}
// 2-2. 실제 파일 이름을 경로와 확장자를 포함하여 만들기 (profile.png 만들기)
// 원본 이미지 가져오기
String origianlFilename = image.getOriginalFilename();
// "whale.png" -> {"whale", "png"}
// 확장자 분리하기 (. 을 기준으로 분리 <- 정규표현식)
String[] filenameSplit = originalFilename.split("\\.");
// "blue.whale.png" -> {"blue", "whale", "png"}
String extension = filenameSplit[filenameSplit.length - 1];
// 최종 파일명 만들기
String profileFilename = "profile" + extension;
String profilePath = profileDir + profileFilename;
// 3. 실제 해당 위치에 파일을 저장
try {
image.transferTo(Path.of(profilePath));
} catch (IOException e) {
log.error(e.getMessage());
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR);
}
// 4. User에 아바타 위치를 저장
// http://localhost:8080/static/{id}/profile.{확장자}
String requestPath = String.format("/static/%d/%s", id, profileFilename);
log.info(requestPath);
User target = optionalUser.get();
target.setAvatar(reqeustPath);
// 5. 응답하기
return UserDto.fromEntity(repository.save(target));
}
byte[] 로 형변환하여 추가 작업을 진행할 수도 있다.
@PutMapping(
value = "multipart",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE
)
public String multipart(
@RequestParam("name")
String name,
// 파일을 받아주는 자료형을 MutlipartFile
@RequestParam("file")
MultipartFile multipartFile
) throws IOException {
// 저장할 파일 이름
File file = new File("./media/" + multipartFile.getOriginalFilename());
// 파일에 저장하기 위한 OutputStream
try (OutputStream outputStream = new FileOutputStream(file)) {
// byte[] 데이터를 받는다.
byte[] fileBytes = multipartFile.getBytes();
// 추가작업
System.out.println(new String(fileBytes, StandardCharsets.UTF_8));
// OutputStream에 MultipartFile의 byte[]를 저장한다.
outputStream.write(fileBytes);
}
return "done";
}
etc현 개발 단계에서는 지금과 같은 static 폴더에 넣지만 나중에 프로젝트를 서비스화할 때는 서버 용량 최소화를 위해 NGINX 또는 S3 와 같은 것들을 고려해야할 수도 있다.