[이미지 업로드] FormData 그리고, MultipartFile

hyozkim·2020년 3월 16일
5

하,, 오늘은 정말,, 이거 하나 제대로 알기 위해 하루를 날려먹었다.
오늘 내로 안하고 또 내일로 미루면 아무것도 못할거 같아 정말 끝까지 매달렸다.

Front-End

<form id="formElem" enctype="multipart/form-data">
	<input type="file" class="hidden_input" id="reviewImageFileOpenInput" accept="image/*" multiple>
</form>
/***
 * 이미지 등록
 */
const imageTag = document.getElementById("reviewImageFileOpenInput");
imageTag.addEventListener('change', function() {
    document.querySelector(".lst_thumb")
        .querySelector("li").style.display = "block";

    loadImg(this); // 이미지 파일을 읽어 img src 에 넣는 함수
});

function loadImg(value) {
    if(value.files && value.files[0]) {
        var reader = new FileReader();

        reader.onload = function (e) {
            document.querySelector(".lst_thumb")
                .querySelector("img").setAttribute('src', e.target.result);
        }

        reader.readAsDataURL(value.files[0]);
    }
}

첫번째로 애먹었던 것은 업로드한 이미지를 바로 보게하는 부분이다.
이미지를 저장하고자 하는 경로업로드한 이미지 이름을 붙여 src에 세팅하려고 했으나 생각해보니 매우 무식한 방법이었다. 이미지를 저장하고자 하는 경로에 이미지가 없으면 이미지를 보이지 않기 때문이다..

FileReader()를 사용해서 업로드하는 것을 구글링해서 찾았고, 이미지 정보를 읽어들여온 정보를 src에 넣어주면 윗처럼 무식하지 않은 방법으로 방금 올린 이미지를 확인할 수 있었다.

/***
 * [Ajax] 서버에 전달
 * FormData를 사용하여 이미지 전달(fileName: imageFile)
 */
var formData = new FormData();
    formData.append('imageFile', document.getElementById('formElem')[0].files[0]);
    fetch('api/reservations/'+reservationInfoId+'/comments?comment='+comment+'&productId='+productId+'&score='+score, {
        method: 'post',
        headers: {
            // 'Content-Type': 'multipart/form-data'
        },
        body: formData
    }).then(function(response) {
        return response.json();
    }).then(function(data) {
        location.href = "mainpage.html";
    });

두번째 Front-End부분에서 헤멧던건 당연 FormData이다.

formData를 K,V 으로 세팅하는건 알겠는데, body에서 저렇게 담아보내면 왜! Controller에서 못받는 건지 너무 답답했다.

1) Content-Type을 'application/json' 안해서 그런가?
이건 Controller에서 parameter 부분인 comment, productId를 찍어보면 값이 나오기 때문에
'multipart/form-data'으로 해도 된다고 생각했다.

the request was rejected because no multipart boundary was found

그런데 에러가 발생했다. (구글링을 해보니 content-type을 명시하지 말라는 내용이 있었음.)

boundary를 찾지 못한다는 소리인데 Content-Type을 지정해두게 되면 Multipart boundary를 열심히 찾는구나~ 라고 이해하고 주석처리하니까 파일 전달이 잘되었다. 결국 'application/json','multipart/form-data' 둘다 안해줘야 작동 됬다. (참고한 링크) 이 부분은 사실 포스트맨으로 테스트할 때는 잘되었는데, 웹에서 직접 해보니 에러가 발생해서 의아했다. (포스트맨에서는 Content-Type:'multipart/form-data'으로 지정함.. 자동으로 처리 해주나봄) 아무튼 이부분은 MultipartFile HTTP 통신에 대해 더 깊게 알아봐야 할 것 같다.

2) formData가 뭔가 잘못된것이 아닌가?

var imageData = document.getElementById('formElem')[0].files[0]

... HTML에 form을 만들고 'formElem'이란 이름을 가진 폼 안에 type="file"인 input을 가져온다는 뜻이다. files[0] 이미지 하나만 가져오면 되니까 [0]인건 알겠는데 저기 앞에 getElementById('formElem')[0] 이건 왜 인덱스 0 처리를 해줘야 되는지,,, 하 -.- 타자치면서도 아직 부들부들 거린다.

imageData.name 으로 찍어보면 값이 나오는 건 확인해서, 이제 문제는 백엔드에 있다라고 생각했다..


Back-End

@PostMapping(path="reservations/{reservationInfoId}/comments", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ApiResult<Map<String,String>> comments(@PathVariable(required = true) Long reservationInfoId,
                                              @RequestParam(name = "comment", required = true) String comment,
                                              @RequestParam(name = "productId", required = true) Long productId,
                                              @RequestParam(name = "score", required = true) int score,
                                              @RequestParam(value = "imageFile",required = false) MultipartFile file) {
        commentService.saveComment(reservationInfoId, productId, comment, score, file);
        // return data
        Map<String,String> map = new HashMap<>();
        return OK(map);
    }

(일단 테스트한다고 Empty Map 내보냄..)

@RequestParam(value = "imageFile",required = false) MultipartFile file) {

일단 @RequestParam 으로 MultipartFile을 가져오는지도 몰랐다..
무튼 구글링해서 다른 parameter인 reservationInfoId, comment, productId, score 다 가져오므로 RequestParam에 뭔가 문제가 있다고 생각했다.
구글링하면서 된다는 소스 다 비교해봐도 틀린점을 못찾았다. 그래서 @RequestBody, @RequestPart 다해봤는데, 왠걸...

MultipartResolver Bean등록을 안했다..

부랴부랴 pom.xml에 필요한 dependency 등록.

<!-- common fileupload -->
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>${commons.fileupload.version}</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>${commons.io.version}</version>
        </dependency>

Resolver Bean 생성.

@Bean
    public MultipartResolver multipartResolver() {
        CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
        multipartResolver.setMaxUploadSize(2000000000);
        return multipartResolver;
    }


됐다..!
차례대로

file.getName(), file.getOriginalFileName(), file.getContentType()

이미지를 보면 FormData.append 할때 key값('imageFile')이 getName() 결과로 나온다.

이로써 파일 업로드 공부 끝.
효율적으로 개발하는 나만의 방법을 만들어야 멘탈 관리도 잘되고 시간도 절약할 수 있을 것 같다.. 그리고 부스트코스 예약서비스 개발 진짜 끝..

profile
차근차근 develog

1개의 댓글

comment-user-thumbnail
2022년 4월 26일

감사합니다!!

답글 달기