본문 바로가기
[ JAVA ]/JAVA Spring

[ Spring ] Ajax 파일업로드

by 환이s 2023. 4. 20.


FormData 객체

 

파일 업로드 Ajax 방식의 핵심은 FormData라는 브라우저에서 지원하는 클래스입니다.

 

FormData <form> 과 같은 효과를 가져다주는 key/value 가 저장되는 객체이며, <form> 태그처럼 데이터를 처리할 수 있게 해 줍니다.

 

이를 XMLHttpRequest(XHR)라는 Ajax 요청을 생성하는 JavaScript API에 실어서 서버에 보내면 마치 <form>이 전송된 것과 같은 효과를 가집니다.


UUID(Universally Unique IDentifier)

 

UUID는 네트워크 상에서 고유성을 보장하는 ID를 만들기 위한 표준 규약입니다. UUID는 다음과 같이 32개의 16진수로 구성되며 5개의 그룹으로 표시되고 각 그룹은 붙임표(-)로 구분합니다.

 

8(비트)-4(비트)-4(비트)-4(비트)-12(비트)

예) 550e8400-e29b-41d4-a716-446655440000

 

결론적으로 UUID는 파일 업로드 시 파일의 이름이 중복되지 않도록 하기 위해 쓰입니다.

간단한 예제 코드를 통해서 알아봅시다.


UploadFileUtils.java

 

먼저 업로드 파일에 필요한 기능을 별도로 Util 페이지 생성해서 구현해 줍니다.

 

package com.example.spring.util;

import java.awt.image.BufferedImage;
import java.io.File;
import java.text.DecimalFormat;
import java.util.Calendar;
import java.util.UUID;

import javax.imageio.ImageIO;

import org.imgscalr.Scalr;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.FileCopyUtils;

public class UploadFileUtils {
		
	//로깅
	private static final Logger logger = LoggerFactory.getLogger(UploadFileUtils.class);
	
	
	public static String uploadFile(String uploadPath, String originalName, byte[] fileDate)  throws Exception{
		
		//uuid 발급 
		UUID uid = UUID.randomUUID();
		String savedName = uid.toString() + "_" +originalName;
		
		//업로드 할 디렉토리 생성
		String savedPath = calcPath(uploadPath);
		File target = new File(uploadPath+savedPath,savedName);
		
		//임시 디렉토리에 업로드된 파일을 지정된 디렉토리로 복사
		FileCopyUtils.copy(fileDate, target);
		
		//파일의 확장자 검사(ex : a.jpg/ aaa.bbb.ccc.jpg)
		String formatName = originalName.substring(originalName.lastIndexOf(".")+1);
		String uploadedFileName = null;
		
		//이미지 파일인 경우 : 썸네일(작은 이미지)을 생성
		if(MediaUtils.getMediaType(formatName) != null) {
			//썸네일 생성
			uploadedFileName = makeThumbnail(uploadPath, savedPath, savedName);
		}else { // 이미지 파일이 아닌경우
			uploadedFileName = noimg(uploadPath, savedPath, savedName);
		}
		return uploadedFileName;
	}

	//이미지 파일이 아닌 파일처리 메서드
	public static String noimg(String uploadPath, String path, String fileName) throws Exception {
		//File.separator  는 이름 구분자로서 예를 들어 윈도우의 경우 upload\\test.txt
		//리눅스는 upload/test.txt라고 쓰는데, '\' , '/' 를 따로 신경 안써도 separator가 
		//OS에 따라 자동으로 처리한다.
		String iconName = uploadPath + path + File.separator + fileName;
		
		return iconName.substring(uploadPath.length()).replace(File.separatorChar, '/');
		
	}


	public static String makeThumbnail(String uploadPath, String path, String fileName)  throws Exception{
		// 원본 이미지를 읽기 위한 버퍼
		BufferedImage sourceImg = ImageIO.read(new File(uploadPath+path,fileName ));
		//100픽셀 단위의 썸네일 생성
		BufferedImage destImg = Scalr.resize(sourceImg, Scalr.Method.AUTOMATIC, Scalr.Mode.FIT_TO_HEIGHT, 100);
		
		//썸네일의 이름
		String thumbnailName = uploadPath + path + File.separator + "s_" + fileName;
		File newFile = new File(thumbnailName);
		String formatName = fileName.substring(fileName.lastIndexOf(".")+1);
		
		//썸네일 생성
		ImageIO.write(destImg, formatName.toUpperCase(), newFile);
		
		//썸네일 이름 리턴
		return thumbnailName.substring(uploadPath.length()).replace(File.separatorChar, '/');
	}

	
	//날짜 처리 (2023년 폴더 / 00월 폴더 / 00일 폴더 생성)
	public static String calcPath(String uploadPath) {
		Calendar cal = Calendar.getInstance();
		String yearPath = File.separator + cal.get(Calendar.YEAR);
		String monthPath = yearPath + File.separator + new DecimalFormat("00").format(cal.get(Calendar.MONTH) + 1);
		
		String datePath = monthPath + File.separator + new DecimalFormat("00").format(Calendar.DATE);
		
		makeDir(uploadPath, yearPath , monthPath, datePath);
		logger.info(datePath);
		return datePath;
	}
	//디렉토리 생성 2023-04-17
	private static void makeDir(String uploadPath, String... paths) {
			// String ... 은 가변 사이즈 매개변수(배열의 요소가 몇개든 상관없이 처리)
			// 디렉토리가 존재하면 skip
		if(new File(paths[paths.length -1]).exists()) { // 존재하면
			return;
		}
		for(String path : paths) {
			File dirPath = new File(uploadPath + path);
			if(!dirPath.exists()) {
				dirPath.mkdir(); // 디렉토리 생성
			}
		}
	}
}

uploadAjax.jsp

 

출력 화면으로 간단한 View단 폼 만들어서 Controller에 요청을 보냅니다.

 

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<%@ include file="../include/header.jsp" %>
<style type="text/css">
.fileDrop{
	width: 100%;
	height: 200px;
	border: 1px dotted blue;
}
</style>
<script type="text/javascript">
$(function() {
	/* 드래그 할 때 기본 효과를 막음 */
	$(".fileDrop").on("dragenter dragover" , function(event) {
		event.preventDefault();
	});
	$(".fileDrop").on("drop", function(event) {
		/* drop이 될 때 기본 효과를 막음 */
		event.preventDefault();
		/* 첨부파일 배열(여러개 동시 드래그 시 한개만 처리하도록 조치)  */
		var files = event.originalEvent.dataTransfer.files;
		var file=files[0]; // 첫번째 첨부파일
		var formData = new FormData(); /* Ajava 방식의 파일 업로드의 핵심 객체  */
		formData.append("file",file); /* 폼에 file 변수 추가 */
		//서버에 파일 업로드(백그라운드에서 실행)
		$.ajax({
			type:"post",
			url:"${path}/upload/uploadAjax",
			data : formData,
			dataType: "text",
			processData: false, // 파일 전송시 자동으로 쿼리 스트링 형식으로 전송되지 않도록 막는 처리
			contentType: false, /* multipart/ form-data 로 처리되는 것과 같음*/
			seccess: function(data, status,req) {
				console.log("data: "+ data ); // 업로드된 파일 이름
				console.log("status: "+ status ); // 성공, 실패 여부
				console.log("req: "+ req.status);// 요청 코드값
			}
		});
	});
});



</script>
</head>
<body>
<%@ include file="../include/menu.jsp" %>
<h2>Ajax File Upload</h2>
<!-- 파일을 업로드할 영역 -->
<div class="fileDrop"></div>


<!-- 업로드된 파일 목록을 출력 영역 -->
<div class="uploadedList"></div>

</body>
</html>

AjaxUploadController.java

 

업로드 디렉터리 설정을 해주고, 객체를 Json 형식으로 데이터를 리턴하려고 할 때, @ResponseBody를 사용해 줍니다.(서버 -> 클라이언트)

 

package com.example.spring.controller.upload;

import javax.annotation.Resource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import com.example.spring.util.UploadFileUtils;

@Controller
public class AjaxUploadController {
	// 로깅
	private static final Logger logger = LoggerFactory.getLogger(AjaxUploadController.class);
	
	//업로드 디렉토리
	@Resource(name ="uploadPath")
	String uploadPath;
	
	@RequestMapping(value = "upload/uploadAjax", method = RequestMethod.GET)
		public String uploadAjax() {
			return "upload/uploadAjax";
		}
	
	@ResponseBody // 객체를 json 형식으로 데이터 리턴(서버 -> 클라이언트)
	@RequestMapping(value = "/upload/uploadAjax" , method = RequestMethod.POST, produces = "text/plain;charset=utf-8") //한글이 깨지지 않도록 처리
	 // ResponseEntity : 업로드한 파일 정보와 Http 상태 코드를 함께 리턴
		public ResponseEntity<String> uploadAjax(MultipartFile file) throws Exception{
		return new ResponseEntity<String>(UploadFileUtils.uploadFile(uploadPath, file.getOriginalFilename(), file.getBytes()), HttpStatus.OK);
		
		
	}
	
	}

출력

 

< View >

 

업로드 파일을 영역 안에다가 드래그 해서 업로드시켜줍니다.

 

< upload File >

 


마치며

 

오늘은 Ajax 파일 업로드에 대해서 알아봤습니다.

다음 포스팅에서 뵙겠습니다.

728x90