[6-ch21~22 Spring ] 파일 업로드 방식 ( 중복처리, 섬네일 ,, )
일단 파일 업로드 방식을 알아보기전 c드라이브 밑에 uploadBook 폴더와 임시 업로드 파일을 저장할 temp 폴더를 생성해둔다.
첨부파일을 서버에 전송하는 방식은 크게 <form> 태그를 이용해 업로드하는 방식과 Ajax를 이용하는 방식으로 나눠볼 수 있다.
📌 <form> 태그를 이용하는 방식 : 브라우저의 제한이 없어야 하는 경우 사용
- 일반적으로 페이지 이동과 동시에 첨부파일을 업로드하는 방식
- <iframe>을 이용해 화면의 이동 없이 첨부파일을 처리하는 방식
📌 Ajax를 이용하는 방식 : 첨부파일을 별도로 처리하는 방식
- <input type='file'>을 이용하고 Ajax로 처리하는 방식
- HTML5의 Drag And Drop 기능이나 jQuery 라이브러리를 이용해서 처리하는 방식
코드로보는 스프링 웹 프로젝트 책에서는 Ajax를 위주로 처리하였다.
📑 web.xml 설정
📑 1. <form>방식의 파일 업로드
<uploadForm.jsp>
<%@ 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>
</head>
<body>
<!-- 파일 업로드에서 가장 신경써야 하는 부분이다. -->
<form action="uploadFormAction" method="post" enctype="multipart/form-data">
<input type='file' name='uploadFile' multiple>
<button>Submit</button>
</form>
</body>
</html>
<UploadController.java>
package com.zerock.controller;
import java.io.File;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.multipart.MultipartFile;
import lombok.extern.log4j.Log4j2;
@Controller
@Log4j2
public class UploadController {
@GetMapping("/uploadForm")
public void uploadForm() {
log.info("upload form");
}
@PostMapping("/uploadFormAction")
public void uploadFormPost(MultipartFile[] uploadFile, Model model) {
String uploadFolder = "C:\\uploadBook";
for(MultipartFile multipartFile : uploadFile) {
log.info("==========================");
log.info("upload file name: "+ multipartFile.getOriginalFilename());
log.info("upload file size: " + multipartFile.getSize());
//java.io.File.File(String parent, String child)
// 원래 파일의 이름으로 c드라이브 upload 폴더에 저장된다.
File saveFile = new File(uploadFolder, multipartFile.getOriginalFilename());
try {
multipartFile.transferTo(saveFile);
}catch(Exception e) {
log.error(e.getMessage());
}
}
}
}
📑 2. Ajax를 이용한 파일 업로드
<uploadAjax.jsp>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%-- 이 DTD는 모든 HTML 요소와 속성들뿐만 아니라 더 이상 사용되지 않거나 아직 정식으로 포함되지 못한 요소들까지도 포함하고 있습니다.
하지만 프레임셋(frameset) 콘텐츠의 사용은 허용하지 않습니다. --%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>upload whit ajax</title>
</head>
<script src="https://code.jquery.com/jquery-3.6.4.min.js" integrity="sha256-oP6HI9z1XaZNBrJURtCoUT5SUnxFr8s3BzRl+cbzUq8=" crossorigin="anonymous"></script>
<script type="text/javascript">
$(document).ready(function(){
$("#uploadBtn").on("click", function(e){
//FormData는 가상의 <form> 태그와 같다.
//ajax를 이용하는 파일 업로드는 FormData를 이용해 필요한 파라미터를 담아 전송한다.
var formData = new FormData();
var inputFile = $("input[name='uploadFile']");
//<input>태그 그 자체를 가져온다.
console.log(inputFile[0]);
//.files로 files에 배열 객체를 참조할 수 있다.
var files = inputFile[0].files;
console.log(files);
//add file data to format
for(var i=0; i<files.length; i++){
formData.append("uploadFile", files[i]);
}
//ajax를 통해 서버의 url로 요청을 보낸다. 그때 formData 객체 자체를 전달함
// processData와 contentType은 반드시 false로 지정해야 됨.
$.ajax({
url : '/uploadAjaxAction',
processData : false,
contentType : false,
data : formData,
type : 'POST',
success : function(result){
alert("uploaded");
}
}); //$.ajax
});
});
</script>
<body>
<h1>Upload with Ajax</h1>
<div class='uploadDiv'>
<input type='file' name='uploadFile' multiple>
</div>
<button id='uploadBtn'>Upload</button>
</body>
</html>
<uploadController.java>
package com.zerock.controller;
import java.io.File;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.multipart.MultipartFile;
import lombok.extern.log4j.Log4j2;
@Controller
@Log4j2
public class UploadController {
@GetMapping("/uploadAjax")
public void uploadAjax() {
log.info("upload ajax");
}
@PostMapping("/uploadAjaxAction")
public void uploadAjaxPost(MultipartFile[] uploadFile) {
log.info("update ajax post......");
String uploadFolder = "C:\\uploadBook";
for(MultipartFile multipartFile : uploadFile) {
log.info("==========================");
log.info("upload file name: "+ multipartFile.getOriginalFilename());
log.info("upload file size: " + multipartFile.getSize());
String uploadFileName = multipartFile.getOriginalFilename();
//IE has file path
uploadFileName = uploadFileName.substring(uploadFileName.lastIndexOf("\\") +1);
log.info("only file name: " + uploadFileName );
File savaFile = new File(uploadFolder, uploadFileName);
try {
multipartFile.transferTo(savaFile);
}catch(Exception e) {
log.error(e.getMessage());
}
}
}
}
Ajax를 이용해 첨부파일을 전송하는 경우 FormData라는 객체를 이용하게 된다. FormData는 쉽게 말해 가상의 <form> 태그와 같다고 생각하면 된다. Ajax를 이용하는 파일 업로드는 FormData를 이용해 필요한 파라미터를 담아 전송하는 방식이다.
📑 파일 업로드 상세 처리
📌 1. 파일의 확장자나 크기의 사전 처리
js로 특정 크기 이상의 파일과, 특정 확장자를 가진 파일은 업로드할 수 없도록 처리한다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%-- 이 DTD는 모든 HTML 요소와 속성들뿐만 아니라 더 이상 사용되지 않거나 아직 정식으로 포함되지 못한 요소들까지도 포함하고 있습니다.
하지만 프레임셋(frameset) 콘텐츠의 사용은 허용하지 않습니다. --%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>upload whit ajax</title>
</head>
<script src="https://code.jquery.com/jquery-3.6.4.min.js" integrity="sha256-oP6HI9z1XaZNBrJURtCoUT5SUnxFr8s3BzRl+cbzUq8=" crossorigin="anonymous"></script>
<script type="text/javascript">
$(document).ready(function(){
//파일 크기와 확장자 처리
//regex = Regular Expression 정규표현식
var regex = new RegExp("(.*?)\.(exe|sh|zip|alz$)");
var maxSize = 5242880; // 5MB
function checkExtension(fileName, fileSize){
if(fileSize > maxSize){
alert("파일 사이즈 초과");
return false;
}
if(regex.test(fileName)){
alert("해당 종류의 파일은 업로드할 수 없습니다.");
return false;
}
return true;
}
$("#uploadBtn").on("click", function(e){
//FormData는 가상의 <form> 태그와 같다.
//ajax를 이용하는 파일 업로드는 FormData를 이용해 필요한 파라미터를 담아 전송한다.
var formData = new FormData();
var inputFile = $("input[name='uploadFile']");
//<input>태그 그 자체를 가져온다.
console.log(inputFile[0]);
//.files로 files에 배열 객체를 참조할 수 있다.
var files = inputFile[0].files;
console.log(files);
//add file data to format
for(var i=0; i<files.length; i++){
if(!checkExtension(files[i].name, files[i].size)){
return false;
}
formData.append("uploadFile", files[i]);
}
//ajax를 통해 서버의 url로 요청을 보낸다. 그때 formData 객체 자체를 전달함
// processData와 contentType은 반드시 false로 지정해야 됨.
$.ajax({
url : '/uploadAjaxAction',
processData : false,
contentType : false,
data : formData,
type : 'POST',
success : function(result){
alert("uploaded");
}
}); //$.ajax
});
});
</script>
<body>
<h1>Upload with Ajax</h1>
<div class='uploadDiv'>
<input type='file' name='uploadFile' multiple>
</div>
<button id='uploadBtn'>Upload</button>
</body>
</html>
📌 2. 중복된 이름의 첨부파일 처리
첨부파일을 보관하는 폴더를 생성하는 작업은 한 번에 폴더를 생성하거나 존재하는 파일을 이용하는 방식을 사용한다. java.io.File에 존재하는 mkdirs()를 이용하면 필요한 상위 폴더까지 한 번에 생성할 수 있다.
또한 중복된 파일의 이름을 다르게하기 위해서 java.util.UUID의 값을 이용해 처리하였다.
@PostMapping("/uploadAjaxAction")
public void uploadAjaxPost(MultipartFile[] uploadFile) {
log.info("update ajax post......");
String uploadFolder = "C:\\uploadBook";
//make folder ----------
//java.io.File.File(String parent, String child)
// 오늘 날짜 이름으로 파일 생성해서 c드라이브 upload 폴더에 저장된다.
File uploadPath = new File(uploadFolder, getFolder());
log.info("upload path: " + uploadPath);
if(uploadPath.exists() == false) {
uploadPath.mkdirs(); // 새로운 폴더 생성
}
for(MultipartFile multipartFile : uploadFile) {
log.info("==========================");
log.info("upload file name: "+ multipartFile.getOriginalFilename());
log.info("upload file size: " + multipartFile.getSize());
String uploadFileName = multipartFile.getOriginalFilename();
//IE has file path
uploadFileName = uploadFileName.substring(uploadFileName.lastIndexOf("\\") +1);
log.info("only file name: " + uploadFileName );
//중복 방지 UUID 적용
UUID uuid = UUID.randomUUID();
uploadFileName = uuid.toString()+"_"+uploadFileName;
//File savaFile = new File(uploadFolder, uploadFileName);
File saveFile = new File(uploadPath, uploadFileName);
try {
multipartFile.transferTo(saveFile);
}catch(Exception e) {
log.error(e.getMessage());
}
}
}
private String getFolder() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = new Date();
String str = sdf.format(date);
//File.separator 는 프로그램이 실행 중인 OS에 해당하는 구분자를 리턴
return str.replace("-", File.separator);
}
📌 3. 섬네일 처리하기
📑 업로드된 파일의 데이터 반환
이미지 파일의 경우 섬네일 생성시 다음과 같이 폴더 이름이 "s_"로 시작하게 처리했다. 이미지 파일이 아닌 경우 섬네일 생성 없이 그냥 파일만 업로드된다.
@PostMapping("/uploadAjaxAction")
public void uploadAjaxPost(MultipartFile[] uploadFile) {
log.info("update ajax post......");
String uploadFolder = "C:\\uploadBook";
//make folder ----------
//java.io.File.File(String parent, String child)
// 오늘 날짜 이름으로 파일 생성해서 c드라이브 upload 폴더에 저장된다.
File uploadPath = new File(uploadFolder, getFolder());
log.info("upload path: " + uploadPath);
if(uploadPath.exists() == false) {
uploadPath.mkdirs(); // 새로운 폴더 생성
}
for(MultipartFile multipartFile : uploadFile) {
log.info("==========================");
log.info("upload file name: "+ multipartFile.getOriginalFilename());
log.info("upload file size: " + multipartFile.getSize());
String uploadFileName = multipartFile.getOriginalFilename();
//IE has file path
uploadFileName = uploadFileName.substring(uploadFileName.lastIndexOf("\\") +1);
log.info("only file name: " + uploadFileName );
//중복 방지 UUID 적용
UUID uuid = UUID.randomUUID();
uploadFileName = uuid.toString()+"_"+uploadFileName;
//File savaFile = new File(uploadFolder, uploadFileName);
File saveFile = new File(uploadPath, uploadFileName);
try {
multipartFile.transferTo(saveFile);
//check image type file
if(checkImageType(saveFile)) {
FileOutputStream thumbnail = new FileOutputStream(new File(uploadPath, "s_" + uploadFileName));
Thumbnailator.createThumbnail(multipartFile.getInputStream(), thumbnail,100,100);
thumbnail.close();
}
}catch(Exception e) {
log.error(e.getMessage());
}
}
}
//폴더 생성 위한 현재 날짜 추출
private String getFolder() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = new Date();
String str = sdf.format(date);
//File.separator 는 프로그램이 실행 중인 OS에 해당하는 구분자를 리턴
return str.replace("-", File.separator);
}
// 섬네일 이미지 생성 위한 이미지 파일 판단
private boolean checkImageType(File file) {
try {
String contentType = Files.probeContentType(file.toPath());
//startsWith() 메서드는 어떤 문자열이 특정 문자로 시작하는지 확인하여 결과를 true 혹은 false로 반환합니다.
return contentType.startsWith("image");
}catch(IOException e){
e.printStackTrace();
}
return false;
}
📑 pom.xml
확인하려면 아래 클릭
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zerock</groupId>
<artifactId>controller</artifactId>
<name>ex05</name>
<packaging>war</packaging>
<version>1.0.0-BUILD-SNAPSHOT</version>
<properties>
<java-version>1.6</java-version>
<org.springframework-version>5.3.21</org.springframework-version>
<org.aspectj-version>1.6.10</org.aspectj-version>
<org.slf4j-version>1.6.6</org.slf4j-version>
</properties>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework-version}</version>
<exclusions>
<!-- Exclude Commons Logging in favor of SLF4j -->
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- AspectJ -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<!--Log4j2-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.18.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.18.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.18.0</version>
</dependency>
<!-- @Inject -->
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
<!-- Servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!--Lombook-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
<!-- json 추가 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.3</version>
</dependency>
<!--섬네일 이미지 생성 -->
<!-- https://mvnrepository.com/artifact/net.coobird/thumbnailator -->
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.8</version>
</dependency>
<!-- Test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-eclipse-plugin</artifactId>
<version>2.9</version>
<configuration>
<additionalProjectnatures>
<projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
</additionalProjectnatures>
<additionalBuildcommands>
<buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>
</additionalBuildcommands>
<downloadSources>true</downloadSources>
<downloadJavadocs>true</downloadJavadocs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
<compilerArgument>-Xlint:all</compilerArgument>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<configuration>
<mainClass>org.test.int1.Main</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>