TIL
day 47 TIL
dalooong
2023. 7. 9. 13:53
File Handling
✅ 정적 파일
사용자에게 변환 없이 전달되는 파일
- CSS
- 이미지, 영상파일
- 몇몇 HTML 등
- spring boot 기본 설정으로 정적 파일을 전달할 수 있다.
- 설정을 바꾸면 요청 경로 변경 가능
✅ form
HTML에서 JS없이 데이터를 보낼 때는? ⇒ form 사용
- 내부에 input 요소를 이용해 전달할 데이터 정의
- input type=”submit”을 이용해 form 요소 내부의 데이터 수합
- enctype 속성으로 데이터 인코딩 방식 정의 가능
✅ multipart/form-data
요청을 여러 부분으로 구분해서 전송하는 형태
- 텍스트와 파일이 혼합된 요청이라는 의미
- Form을 이용해 파일을 보낼 경우 선택해야 하는 방식
- application/x-www-form-urlencoded (기본값) : input 데이터를 모아 하나의 문자열로 표현해 전송합니다.
- mutlipart/form-data : 각각의 input 데이터를 개별적으로 인코딩해, 여러 부분 (multipart)로 나눠서 전송합니다.
이때 저희가 일반적인 문자 데이터를 보낼때는 기본값인 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>
참고) type="image" 같은 경우 이미지를 업로드 하기 위함이 아닌, 이미지를 이용해 제출 버튼을 표현하고 싶을 때 사용하는 형식입니다.
Postman을 통해 multipart/form-data 요청을 보내고 싶다면, Body 탭에서 form-data 를 선택하면 됩니다. 이때 Key 에 해당하는 값이 input 요소의 name 의 역할을 합니다.
- Postman에서 multipart/form-data를 보낼 경우
- Value에서 파일선택하면 된다.
- .html 코드
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form
action="/multipart"
method="post"
enctype="multipart/form-data">
<input type="text" name="name">
<input type="file" name="photo">
<input type="submit">
</form>
</body>
</html>
Spring에서 MultipartFile 받기
✅ @RequestParam으로 MultipartFile 인자를 받을 수 있다
- getBytes() 메소드로 byte[] 데이터로 사전 확인 가능
//저장할 파일 이름
File file = new File("./filename.png");
//파일에 저장하기 위한 OutputStream
try(OutputStream outputStream = new FileOutputStream(file));
//byte[]에 데이터를 받는다.
byte[] fileBytes = multipartFile.getBytes();
//여기에서 추가 작업
OutputStream에 MultipartFile의 byte[]를 저장한다.
outputStream.write(fileBytes);
- form.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form
action="/multipart"
method="post"
enctype="multipart/form-data">
<input type="text" name="name">
<input type="file" name="photo">
<input type="submit">
</form>
</body>
</html>
- ResponseDto
@Data
public class ResponseDto {
private String message;
}
- filecontroller
@Slf4j
@RestController
public class FileController {
@PostMapping(
value = "/multipart",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE
)
public ResponseDto multipart(
@RequestParam("name") String name,
@RequestParam("photo")MultipartFile multipartFile
) throws IOException {
Path uploadTo = Path.of("filename.png");
multipartFile.transferTo(uploadTo);
ResponseDto response = new ResponseDto();
response.setMessage("success");
return response;
}
}
postman에서 실행
인텔리제이에 설정한 filename.png 파일이 생긴 걸 확인할 수 있다.
✅ @RequestParam으로 MultipartFile 인자를 받을 수 있다
- 이때 @RequestMapping의 consumes 설정 필요
- transferTo 메소드로 파일의 형태로 저장할 수 있다. → 한 파일에서 파일 관리 할 수 있음(media)
//저장할 경로를 생성한다.
Files.createDirectories(Path.of("media"));
//저할 파일이름을 경로를 포함해 지정한다.
Path path = Path.of("media/filename.png");
//저장한다.
multipartFile.transferTo(path);
- media 폴더가 생성되고 그 안에 png 파일이 들어가있는걸 확인할 수 있다.
✅ 업로드된 데이터 돌려주기
- 한 폴더에 사용자가 업로드한 파일을 다 저장하고
- 해당 경로의 파일을 정적 파일의 형태로 전달하자
- application.yaml 설정
spring:
mvc:
static-path-pattern: /static/**
web:
# spring이 정적 파일 요청을 받을 때 그 파일을 찾는 경로들
resources:
static-locations: file:media/,classpath:/static
- 파일에 현재 시간 나오게 저장하기
@Slf4j
@RestController
public class FileController {
@PostMapping(
value = "/multipart",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE
)
public ResponseDto multipart(
@RequestParam("name") String name,
@RequestParam("photo")MultipartFile multipartFile
) throws IOException {
//저장할 경로를 생성한다.
Files.createDirectories(Path.of("media"));
//저할 파일이름을 경로를 포함해 지정한다.
// Path path = Path.of("media/filename.png");
// multipartFile.transferTo(path);
LocalDateTime now = LocalDateTime.now();
log.info(now.toString());
String filename = now.toString().replace(":", "");
Path uploadTo
= Path.of(String.format("media/%s.png", filename));
//저장한다.
multipartFile.transferTo(uploadTo);
ResponseDto response = new ResponseDto();
response.setMessage(String.format("/static/%s.png",filename));
return response;
}
- 포스트맨 실행
실행결과 → 파일이름에 현재 날짜가 적힌 걸 확인할 수 있다.
UserEntity.avatar
- userService 전체 코드 updateUserAvatar,
- updateUserAvatar
package com.example.contents;
import com.example.contents.dto.UserDto;
import com.example.contents.entity.UserEntity;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.server.ResponseStatusException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
@Slf4j
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository repository;
// createUser
public UserDto createUser(UserDto dto) {
// 1. 회원가입 ==> 프로필 이미지가 아직 필요없다.
UserEntity entity = new UserEntity();
entity.setUsername(dto.getUsername());
entity.setPhone(dto.getPhone());
entity.setBio(dto.getBio());
entity.setEmail(dto.getEmail());
entity.setAvatar(dto.getAvatar());
throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED);
}
// readUserByUsername
public UserDto readUserByUsername(String username) {
throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED);
}
// updateUser
public UserDto updateUser(Long id, UserDto dto) {
throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED);
}
// updateUserAvatar
public UserDto updateUserAvatar(Long id, MultipartFile avatarImage) {
// 사용자가 프로필 이미지를 업로드 한다.
// 1. 유저 존재 확인
Optional<UserEntity> optionalUser
= repository.findById(id);
if (optionalUser.isEmpty())
throw new ResponseStatusException(HttpStatus.NOT_FOUND);
// media/filename.png
// media/<업로드 시각>.png
// 2. 파일을 어디에 업로드 할건지
// media/{userId}/profile.{파일 확장자}
// 2-1. 폴더만 만드는 과정
String profileDir = String.format("media/%d/", id);
log.info(profileDir);
try {
Files.createDirectories(Path.of(profileDir));
} catch (IOException e) {
log.error(e.getMessage());
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR);
}
// 2-2. 확장자를 포함한 이미지 이름 만들기 (profile.{확장자})
String originalFilename = avatarImage.getOriginalFilename();
String[] fileNameSplit = originalFilename.split("\\\\.");
String extension = fileNameSplit[fileNameSplit.length - 1];
String profileFilename = "profile." + extension;
log.info(profileFilename);
// 2-3. 폴더와 파일 경로를 포함한 이름 만들기
String profilePath = profileDir + profileFilename;
log.info(profilePath);
// 3. MultipartFile 을 저장하기
try {
avatarImage.transferTo(Path.of(profilePath));
} catch (IOException e) {
log.error(e.getMessage());
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR);
}
// 4. UserEntity 업데이트 (정적 프로필 이미지를 회수할 수 있는 URL)
// <http://localhost:8080/static/1/profile.png>
log.info(String.format("/static/%d/%s", id, profileFilename));
UserEntity userEntity = optionalUser.get();
userEntity.setAvatar(String.format("/static/%d/%s", id, profileFilename));
return UserDto.fromEntity(repository.save(userEntity));
}
}
- 만약 Multipart 이미지 크기 제한이 걸려서 사진이 로당 안된다면 .yaml 에서 크기 용량을 변경해준다
spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB