Back-End/Spring Legacy

[6-ch21~22 Spring ] 파일 업로드 방식 ( 중복처리, 섬네일 ,, )

s워니얌 2023. 5. 10. 16:50

 

일단 파일 업로드 방식을 알아보기전 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>
반응형