복습을 위한

MultipartFile 본문

SpringMVC

MultipartFile

ho042479 2024. 1. 30. 11:38

 

 

일반적으로 사용하는 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필드는 폴더 저장 파일명(고유)

 

 

 

 

 

참고

https://www.inflearn.com/course/lecture?courseSlug=%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2&unitId=83388&category=questionDetail&tab=note

 

 

'SpringMVC' 카테고리의 다른 글

스프링 인터셉터  (0) 2024.01.31
서블릿 필터  (0) 2024.01.31
세션타임아웃설정  (0) 2024.01.31
서블릿http세션  (0) 2024.01.30
쿠키보안문제와 세션  (0) 2024.01.30