본문 바로가기
Languages/HTML & CSS & JS

AJAX를 이용한 REST 방식의 파일 업로드

by 달팽이 "@... 2022. 2. 22.

 많은 레퍼런스들을 참고하여 고민하며 구현한 코드지만 저 스스로도 취업준비생인 초보 개발자이기 때문에 코드 상의 문제 혹은 더 바람직한 우수사례들이 있을 수 있습니다.  코드 작성 시 참고 정도로 활용해주시고 문제나 개선 가능한 부분 발견 시 공유해주신다면 감사하겠습니다.

 

현재 진행중인 프로젝트에서 화면 이동 없이 modal 창을 띄워 글을 작성하고 수정할 수 있는 기능이 필요했습니다

화면을 다시 받아오지 않고 비동기식으로 파일을 업로드할 수 있는 제이쿼리를 이용한 AJAX 구현 코드입니다

 

JSP

<div id="modal-question" class="modal">

    <form id="question-form" action="" method="POST" enctype="multipart/form-data">

        <input type="hidden" id="bno" name="bno">

        <input type="text" id="title" name="title" onchange="test()">

        <%--    Editor로 사용할 태그    --%>
        <textarea name="content" id="content" style="width: 100%; height: 100%;"></textarea>
        <input type="text" id="writer" name="writer" placeholder="">

        <div id="form-footer">
            <span id="upload-button">Image</span>
            <button type="submit" id="submit-button" class="btn">Submit</button>
        </div>
        <%--      이미지 업로드 input 태그(숨김)      --%>
        <input type="file" multiple="multiple" name="image" id="image" style="display: none;" accept="image/*">
    </form>
</div>

 

form-footer 태그 안에 upload-button의 id를 가진 span이 인풋을 작동시키는 버튼 역할을 합니다

form 태그의 맨 아랫줄에 이미지 업로드용 input이 있습니다 화면에 나타나지 않도록 숨김처리 하였습니다

 

 

JS

// 이미지 업로드 버튼 클릭 시 image input 태그 클릭 작동
$("#upload-button").click(function () {
    $("#image").trigger("click")
})

 

먼저 form 태그의 upload-button을 누르면 input 태그를 작동시키도록 합니다

(해당 코드는 꼭 필요한 부분은 아닙니다 input 태그를 숨기지 않고 그대로 진행하셔도 됩니다)

 

 

    // 이미지 업로드 함수. image input 변경 시 실행됨
    $("#image").on("change", function () {

        // input 태그의 파일 데이터
        const files = this.files

        // ajax submit용 form data
        const formData = new FormData()

        // input 태그 파일을 데이터 순회하며 form data에 추가
        // 첫번째 스트링 인자('image')는 서버에서 multipart file의 파라미터명으로 쓰이므로 주의
        for (let i=0; i<files.length; i++) {

            formData.append('image', files[i])
        }

        // ajax 호출
        $.ajax({
            // 요청 URL
            url: "/file/upload",

            // 파일 전송 시
            enctype: "multipart/form-data",

            // data의 스트링화(stringify) 방지
            processData: false,

            // contentType header의 default 값 설정 방지
            contentType: false,
            
            // 전송할 데이터
            data: formData,
            
            // 데이터 전송 방식
            type: "POST",
            
            // ajax 요청 성공 시 콜백함수
            success: function (imgs) {

                // 데이터를 반환받아 실행할 코드

            }
        })
    })

 

위에서 작동한 input 태그에 변경사항이 있을 시 실행되는 코드입니다 

form data를 생성해 파일 데이터를 넣어 ajax 방식으로 특정 url에 요청하는 과정입니다

ajax 요청 시 properties를 주의해주세요 (processData와 contentType 꼭 코드와 같이 설정해주세요)

 

참고하실 부분은 FormData는 console.log로 출력 시 아무것도 출력되지 않습니다 

필요하시다면 링크(https://stackoverflow.com/questions/17066875/how-to-inspect-formdata)를 참고해주세요

 

 

ImageController


@RestController
@RequestMapping("/file/")
public class ImageController {

    @Autowired
    FileService fileService;

    @PostMapping(value="/upload")
    public ResponseEntity<List<ImageFileDTO>> uploadFile(MultipartHttpServletRequest mtfRequest) throws Exception {

        // 파일(들)을 지정된 경로에 저장 및 Image DTO 리스트 반환
        List<ImageFileDTO> attachedImgs = UploadFileUtils.uploadFile(mtfRequest);

        // Image DTO 리스트와 함께 OK status response code 반환
        return new ResponseEntity<>(attachedImgs, HttpStatus.OK);
    }

}

image 관련 컨트롤러 입니다

인자로 MultipartHttpServletRequest를 전달받아 multipart file을 다룰 수 있습니다

DTO 리스트를 반환받아 http status code와 함께 클라이언트에게 전달합니다

 

ImageFileDTO

@Getter
@Setter
public class ImageFileDTO {

    private String imageName;
    private String uploadPath;
}

 

업로드된 image 파일의 이름과 업로드 경로를 담을 간단한 DTO 입니다 

 

 

UploadFileUtils

public class UploadFileUtils {

    public static List<ImageFileDTO> uploadFile(MultipartHttpServletRequest mtfRequest) {

        // multipartFile 리스트
        List<MultipartFile> mtfs = mtfRequest.getFiles("image");

        //
        List<ImageFileDTO> fileList = new ArrayList<>();

        for (MultipartFile mtf : mtfs) {

            ImageFileDTO imageFileDTO = new ImageFileDTO();

            // 파일 이름
            String originalFileName = mtf.getOriginalFilename();

            // 공백문자를 언더스코어로 교체하기
            originalFileName = originalFileName.replace(' ', '_');

            imageFileDTO.setImageName(originalFileName);

            // 업로드 경로
            String uploadPath = mtfRequest.getSession().getServletContext().getRealPath(File.separator + "WEB-INF" + File.separator + "uploadedImages" + File.separator);

            // 조회시 과부하를 막기 위한 경로 구분용 현재 날짜를 yyyyMMdd 형태로 반환받기
            String date = getTodayDate();

            // 파일 이름 중복문제를 해결하기 위한 UUID
            String uuid = UUID.randomUUID().toString();

            // 데이터베이스에 기록될 경로 형태
            // 현재날짜 + UUID + "_" + 파일
            String dbFile = date + uuid + "_" + originalFileName;

            imageFileDTO.setUploadPath(dbFile);

            // 파일 저장 경로
            String saveFile = uploadPath + dbFile;


            // 위에서 생성했던 리스트에 첨부 이미지 데이터 담기
            fileList.add(imageFileDTO);

            try {
                // 파일 경로에 저장하기
                mtf.transferTo(new File(saveFile));

            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return fileList;  // 파일 리스트 반환

    }
}

 

다른 포스팅에서 구현했던 UploadFileUtils를 약간 수정하여 multipart file들을 지정된 경로에 저장하고

파일 이름과 저장경로를 DTO에 담아 list에 넣어 반환하게끔 했습니다

 

 

AJAX 코드의 success 콜백함수 부분

// ajax 요청 성공 시 콜백함수
success: function (imgs) {

    // 데이터를 반환받아 실행할 코드

}

 

다시 ajax 코드로 돌아와 반환 받은 DTO 리스트를 가지고 콜백함수를 구현합니다 

 

 

저같은 경우는 업로드한 이미지를 tinymce WYSWYG editor에 출력하므로 아래와 같이 작성 했습니다

// ajax 요청 성공 시 콜백함수
success: function (imgs) {

    // 읽어온 이미지를 에디터 Content에 img 태그로 추가
    for (const img of imgs) {
        tinymce.activeEditor.insertContent('<img alt="photo" src="/uploadedImages/' + img.uploadPath + '" style="width: 800px; height: 500px;"/>')
    }
}

 

특정한 태그안에 업로드된 이미지를 삽입하고 싶으신 분들은 링크(https://stackoverflow.com/questions/44131193/show-image-from-ajax-response)를 참고해주세요

 

실행 결과 클라이언트측과 서버측 모두 잘 작동하고 있습니다

클라이언트측 이미지 업로드 화면

 

서버측 저장된 이미지

 

 

'Languages > HTML & CSS & JS' 카테고리의 다른 글

Tinymce editor 이미지 업로드 & 삽입하기  (0) 2022.02.20