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 파일 업로드에 대해서 알아봤습니다.
다음 포스팅에서 뵙겠습니다.
'[ JAVA ] > JAVA Spring' 카테고리의 다른 글
[ Spring ] 게시판 02 - 회원 목록 페이지 나누기 기능 구현 (0) | 2023.04.26 |
---|---|
[ Spring ] 게시판 01 - 목록/글쓰기 구현 (0) | 2023.04.24 |
[ Spring ] 코드 난독화 (1) | 2023.04.19 |
[ Spring ] Interceptor (0) | 2023.04.17 |
[ Spring ] 상품 수정/삭제 기능 구현 (0) | 2023.04.14 |