일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 세션타임아웃설정
- Could not find or load main class worker.org.gradle.process.internal.worker.GradleWorkerMain
- must revalidate
- etag
- 세션만들어보기
- gradle오류
- http
- 캐시
- Not Modified
- 서블릿필터
- max age
- 프록시 캐시 서버
- no cache
- resolveArgument
- 서블릿http세션
- hikaricp
- 프록시객체
- 검증헤더
- 인증체크
- 조건부요청
- 쿠키보안문제
- 양쪽 모두 값 설정
- 쿠키생명주기
- UrlResource
- www-Authenticate
- HTTP API
- HTTP상태코드
- 300
- Expires
- supportParameter
- Today
- Total
복습을 위한
MultipartFile 본문
일반적으로 사용하는 HTML Form을 통한 파일 업로드를 이해하려면 먼저 폼을 전송하는 다음 두 가지 방식의 차이를 이해해야 한다.
HTML 폼 전송 방식
application/x-www-form-urlencoded
multipart/form-data
첫번째 방식으로는 바이너리 데이터인 파일을 전송하기 어렵다. 또한 파일을 전송할 때 이름, 나이, 첨부파일 이런 식으로 문자와 바이너리타입을 동시에 전송해야하는 상황을 처리하지못한다.
그래서 이러한 경우 HTTP에서 제공하는 multipart/form-data전송방식을 써야한다.
다른 종류의 여러 파일과 폼의 내용을 함께 전송할 수 있다. 폼의 입력 결과로 생성된 HTTP 메시지를 보면 각각의 전송 항목이 구분이 되어있다. multipart/form-data 타입을 사용하면 따로따로 각각의 파트로 나눠서 웹브라우져가 알아서 메세지를 생성해준다. 여러종류의 파일과 폼의 내용을 함께 전송가능한 것이다.
스프링은 MultipartFile 이라는 인터페이스로 멀티파트 파일을 매우 편리하게 지원한다.
@RequestParam이나 @ModelAttribute에 그대로 가져다 쓸 수 있다.
참고로 application.properties에 아래코드를 추가하면 서블릿 컨테이너는 멀티파트와 관련된 처리를 하지않는다.
spring.servlet.multipart.enabled=false
기본값은 true이다. 멀티파트데이터를 사용하고 처리할거면 따로 코드를 추가할 필요없다.
클라이언트쪽에서 파일업로드를 하기 위해서는 실제 파일이 저장되는 경로가 필요하다.
file.dir=C:/aaa/Download/ 이런식으로 application properties에 저장경로를 추가해주자
파일 저장(해당 경로로 저장)
MultipartFile file = /* 업로드된 파일 */;
File destFile = new File("저장할_경로/저장할_파일명");
file.transferTo(destFile);
원본파일이름얻어오기
MultipartFile file = /* 업로드된 파일 */;
String originalFilename = file.getOriginalFilename();
(dto)ItemForm객체로 다중객체가 들어오는지 단일 객체가 들어오는지는 스프링이 알아서 판단해준다. 우리는 그것을 받고 MutipartFile인터페이스로 입맛에 맞게 쓰면된다.
@Slf4j
@Controller
@RequiredArgsConstructor
public class ItemController {
private final ItemRepository itemRepository;
private final FileStore fileStore;
@GetMapping("/items/new")
public String newItem(@ModelAttribute ItemForm form){
return "item-form";
}
@PostMapping("/items/new")
public String saveItem(@ModelAttribute ItemForm form, RedirectAttributes redirectAttributes) throws IOException {
//단일 파일인지 다중 파일인지 스프링이 알아서 판단해준다.
//단일일 경우 로직실행
MultipartFile a = form.getAttachFile();
UploadFile attachFile = fileStore.storeFile(a);
//다중일 경우 로직실행 (알아서 여러개 다 받고 반복실행)
List<MultipartFile> imageFiles = form.getImageFiles();
List<UploadFile> storeImageFiles = fileStore.storeFiles(imageFiles);
//데이터베이스에 저장
Item item = new Item();
item.setItemName(form.getItemName());
item.setAttachFile(attachFile);
item.setImageFiles(storeImageFiles);
itemRepository.save(item);
redirectAttributes.addAttribute("itemsId", item.getId());
return "redirect:/items/{itemsId}";
}
//사용자에게 이미지 파일 보여주기
@GetMapping("/items/{id}")
public String items(@PathVariable Long id, Model model){
Item item = itemRepository.findById(id);
model.addAttribute("item",item);
return "item-view";
}
//실제 이미지 파일 url
@ResponseBody
@GetMapping("/images/{filename}")
public Resource downloadImage(@PathVariable String filename) throws MalformedURLException {
return new UrlResource("file:"+fileStore.getFullPath(filename));
//UrlResource가 파일경로와 이름을 주면 알아서 파일 찾아온다.
}
@GetMapping("/attach/{itemId}")//첨부파일다운
public ResponseEntity<Resource> downloadAttach(@PathVariable Long itemId) throws MalformedURLException {
Item item = itemRepository.findById(itemId);
String storeFileName = item.getAttachFile().getStoreFileName();
String uploadFileName = item.getAttachFile().getUploadFileName();
UrlResource resource = new UrlResource("file:" + fileStore.getFullPath(storeFileName));
//UrlResource가 파일경로 이름 주면 알아서 파일 찾아온다
log.info("uploadFileName={}",uploadFileName);
//한글 파일명 파일 깨짐 방지
String encodedUploadFileName = UriUtils.encode(uploadFileName, StandardCharsets.UTF_8);
String contentDisposition="attachment; filename=\""+encodedUploadFileName+"\"";
//헤더추가해야함 안그러면 첨부파일 다운안됨
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,contentDisposition)//필수
.body(resource);
}
// Content-Disposition 헤더생성코드. 여기서 attachment는 파일을 다운로드하도록지시하고, filename은 다운로드될 파일의 이름을 지정.
//uploadFileName은 다운로드될 파일의 실제 이름을 나타냄. 이 이름은 클라이언트(브라우저)에서 다운로드할 때 사용. \와 "는 이중 따옴표를 문자열 안에 포함시키기 위한 이스케이프 문자.
// 따라서, 예를 들어 uploadFileName이 "example.txt"라면, 최종적으로 Content-Disposition 헤더는 다음과 같이 될 것.
// Content-Disposition: attachment; filename="example.txt"
// 이렇게 설정된 헤더는 브라우저에게 해당 파일을 다운로드하라는 지시를 전달하고, 다운로드되는 파일의 이름을 "example.txt"로 표시.
}
item(Entity) itemForm(Dto) UrlResource인터페이스 엄청남을 느꼈다.
로컬 저장소에 업로드 파일 저장
@Component
public class FileStore {
@Value("${file.dir}")
private String fileDir;
public String getFullPath(String filename) {
return fileDir + filename;
}
//이미지다중업로드
public List<UploadFile> storeFiles(List<MultipartFile> multipartFiles) throws IOException {
List<UploadFile> storeFileResult = new ArrayList<>();
for (MultipartFile multipartFile : multipartFiles) {
if(!multipartFile.isEmpty()){
UploadFile uploadFile = storeFile(multipartFile);
storeFileResult.add(uploadFile);
}
}
return storeFileResult;
}
public UploadFile storeFile(MultipartFile multipartFile) throws IOException {
if(multipartFile.isEmpty()){
return null;
}
//업로드파일명 예 : image.png
String originalFilename = multipartFile.getOriginalFilename();
//서버에 저장하는 파일명
String storeFileName = createStoreFileName(originalFilename);
multipartFile.transferTo(new File(getFullPath(storeFileName)));//저장소에 저장하기
return new UploadFile(originalFilename, storeFileName);
}
private String createStoreFileName(String originalFilename) {
String ext = extractExt(originalFilename);//확장자명 png 뽑기
String uuid = UUID.randomUUID().toString(); //예 : "qqa54we-wrwrqw-678f6-86s5d65"
String storeFileName = uuid + "." + ext; //확장자명까지 붙여주기
//예 : "qqa54we-wrwrqw-678f6-86s5d65.png"
return storeFileName;
}
private String extractExt(String originalFilename) {
int pos = originalFilename.lastIndexOf(".");
String ext = originalFilename.substring(pos + 1);//확장자명
return ext;
}
}
uploadfileName필드는 유저가 올렸을 때 파일명(겹칠 수 있음), storeFileName필드는 폴더 저장 파일명(고유)
참고