서블릿 파일 업로드
문자가 아닌 여러가지 문자와 파일을 같이 보낼려면 어떻게 해야 할까?
multipart/form-data를 추가하면 파일과 문자를 동시에 보낼 수 있다.
logging.level.org.apache.coyote.http11=debug
디버깅해서 내용을 볼 수 있다.
part는 부분을 나눈게 꺼내어서 볼 수 있다.
Multipart-form-data의 기본 설정
max-file-size: 파일 하나의 최대 사이즈 ,기본 1MB
max-request-size :멀티 파트 사이즈 여러개 업로드가 가능한데 그 전체 합이다. 기본 10MB
멀티파트에 관련한 처리를 하지 말아라 .(기본은 true이다.)
String itemName = request.getParameter("itemName");
log.info("itemName={}", itemName);
Collection<Part> parts = request.getParts();
false이면 이 두개가 처리가 되지 않는다.
true이면 복잡한 멀티 파트 부분을 해결을 할 수 있다.
multipartresolver를 실행하게
옵션을 키게 되면 httpServletRequest에서 MultipartServletRequest로 변환이 되서 여러가지 멀티파트와 관련된 추가기능을 수행을 한다.
서블릿이 제공하는 Part에 대해 알아보자
파일을 업로드 하려면 실제 파일이 저장되는 경로를 설정해 주자
@Slf4j
@Controller
@RequestMapping("/servlet/v2")
public class ServletUploadControllerV2 {
@Value("${file.dir}")
private String fileDir;
@GetMapping("/upload")
public String newFile() {
return "upload-form";
}
@PostMapping("/upload")
public String saveFileV1(HttpServletRequest request) throws ServletException, IOException {
log.info("request={}", request);
String itemName = request.getParameter("itemName");
log.info("itemName={}", itemName);
Collection<Part> parts = request.getParts();
log.info("parts={}", parts);
for (Part part : parts) {
log.info("==== PART ====");
log.info("name={}", part.getName());
Collection<String> headerNames = part.getHeaderNames();
//Part도 header 와 내용이 따로 분리되어 있다.
for (String headerName : headerNames) {
log.info("header {}: {}", headerName, part.getHeader(headerName));
}
//편의 메서드
//content-disposition; filename
log.info("submittedFilename={}", part.getSubmittedFileName());
log.info("size={}", part.getSize()); //part body size
//바디에 있는 데이터 읽기
InputStream inputStream = part.getInputStream();
String body = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
//문자를 바이너리 셋으로 바꿀떄 캐릭터 셋을 언제나 정의를 해 주어야한다.
log.info("body={}", body);
//파일에 저장하기
if (StringUtils.hasText(part.getSubmittedFileName())) {
//실제 파일이 있는 가
String fullPath = fileDir + part.getSubmittedFileName();
//경로를 잡아줌
log.info("파일 저장 fullPath={}", fullPath);
part.write(fullPath);
}
}
return "upload-form";
}
}
파일에 실제 경로를 @Value("${파일경로}")를 통해 넣어주자
Collection part에 part에 헤더와 body 내용이 있는 데 그거를 읽어와서 로그를 찍을 수 있다.
part.getSubmittedFileName 클라이언트가 전달한 파일 이름
part.getInputStream :part의 전송 데이터를 읽음
part.write():part를 통해 전송된 데이터 저장
용량이 큰 파일을 업로드 할 때는 기타 옵션은 꺼주자
기본적인 Spring에 Upload 기능은 무엇이 있나?
기본적으로 파일 저장하는 위치를
@Value("${}")를 가져온다.
@Getmapping 처음 화면 들어가기
@Postmapping 데이터를 보내는 곳
파일이 비어 있지 않는 다면
fileDir(저장 위치)
@RequestParam MultipartFile file 에 원래 업로드 된 파일명 을 fullPath에 저장을 해준다.
새로운 file.transferTo로 저장을 해준다.
@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()) {
storeFileResult.add(storeFile(multipartFile));
}
}
return storeFileResult;
}
//파일을 저장하는 로직 스프링이 제공하는 Multipartfile을 받고 반환해준다.
//서버에 저장하는 파일명
public UploadFile storeFile(MultipartFile multipartFile) throws IOException {
if (multipartFile.isEmpty()) {
return null;
}
String originalFilename = multipartFile.getOriginalFilename();
//original +storeFIle name 이 나누어 진다.
String storeFileName = createStoreFileName(originalFilename);
multipartFile.transferTo(new File(getFullPath(storeFileName)));
return new UploadFile(originalFilename, storeFileName);
}
private String createStoreFileName(String originalFilename) {
String ext = extractExt(originalFilename);
String uuid = UUID.randomUUID().toString();
return uuid + "." + ext;
//uuid를 뽑고 "."+확장자를 더해 준다. 저장할 파일 내임
}
private String extractExt(String originalFilename) {
int pos = originalFilename.lastIndexOf(".");
return originalFilename.substring(pos + 1);
//.png를 뽑을 수 있다.
}
}
이미지를 multipart 다양하게 받아와서 저장한 다음에 반환한다.
뒤에 파일 형식 지정자를 뽑고 예를 들어 "ktprices.png"이면 .뒤에 png 형식을 뽑고 uuid를 생성해서 return 해준다.
여러개의 파일을 올리기 위해서 MultipartFile 로 만들어준다.
파일도 업로드를 여러개 하기 위해서 multiple=multiple을 꼭 해주어야 한다.
form에서 multipart로 여러가지 데이터를 받아준다.
Uploadfilename:고객이 업로드 한 파일이름
storefilename:DB에 저장되는 파일이름
따로 저장하는 이유 두개 가 같으면 이게 충돌이 일어난다.
mulitple="multiple"을 붙여줘서 여러개의 파일을 보내 줄 수 있다.
@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 {
UploadFile attachFile = fileStore.storeFile(form.getAttachFile());
List<UploadFile> storeImageFiles = fileStore.storeFiles(form.getImageFiles());
//데이터베이스에 저장
Item item = new Item();
item.setItemName(form.getItemName());
item.setAttachFile(attachFile);
item.setImageFiles(storeImageFiles);
itemRepository.save(item);
redirectAttributes.addAttribute("itemId", item.getId());
return "redirect:/items/{itemId}";
}
@GetMapping("/items/{id}")
public String items(@PathVariable Long id, Model model) {
Item item = itemRepository.findById(id);
model.addAttribute("item", item);
return "item-view";
}
@ResponseBody
@GetMapping("/images/{filename}")
public Resource downloadImage(@PathVariable String filename) throws MalformedURLException {
return new UrlResource("file:" + fileStore.getFullPath(filename));
}
@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));
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);
}
}
@ModelAttribute를 통해서 form을 전송해준다.
@ModelAttribute로 form을 가져와서
다운 받을 파일 1개와 여러가지 파일을 따로 저장한다.
item .setItemName .. setAttachFile,...setImageFiles 등등으로 itemRepository에 저장해서 넘기고 (리다이렉트 된 화면으로 정보를 넘기기 위해서 ) redirectAttributes로 바꾼다. (밑에 바뀌는 화면이다.)
@GetMapping("/images/{filename}") : img 태그로 이미지 조회를 할 때 마다 사용한다. @ResponseBody 로 이미지 바이너리를 조회한다.
file: +fileStore.getFullPath로 풀내임을 입력해서 @Responsbody로 리소스를 가져온다.(내가 파일 저장한 위치로 부터)
파일을 다운 받는 방법이다. Urlresource를 가져와서
encodedUploadFileName 인코딩된 방식을 넣는다. (이렇게 안하면 꺠지는 것도 발생)
Response.ok().header(HttpHeaders. xx ,contentDisposition) 이거를 안하면 눌렀을 때 화면을 읽기만 할 수 있다.