보라코딩

스프링 파일 업로드 (ajax) 본문

코딩/Spring

스프링 파일 업로드 (ajax)

new 보라 2023. 5. 14. 22:16

 

ex05.zip
0.06MB

 

 

기본 설정

 

 

web.xml

위에 두번째줄부터 2.5인 부분 3.1으로 변경

 

 

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">

 

 

 

servlet내에 태그 추가

<!-- 파일업로드 설정 시작 -->
<multipart-config>
<location>C:\\upload</location>          <!-- 업로드 되는 파일을 저장할 공간 -->
<max-file-size>20971520</max-file-size>          <!-- 최대크기 1MB * 20 -->
<max-request-size>41943040</max-request-size>          <!-- 한번에 올릴 수 있는 최대크기 1MB * 40 -->
<file-size-threshold>20971520</file-size-threshold>          <!-- 메모리 사용 1MB * 20 -->
</multipart-config>
<!-- 파일업로드 설정 끝 -->

 

 

 

 

 

servlet-context.xml

 

<!-- 첨부파일을 처리하는 빈 설정 -->
<beans:bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver">
</beans:bean>

 

 

 

 

pom.xml

 

<!-- https://mvnrepository.com/artifact/net.coobird/thumbnailator -->
<dependency>
    <groupId>net.coobird</groupId>
    <artifactId>thumbnailator</artifactId>
    <version>0.4.8</version>
</dependency>

 

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.6</version>
</dependency>

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
    <version>2.9.6</version>
</dependency>

 

 


 

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>

 

 

 

 


ajax방법

 

 

 

Controller

 

package com.mystudy.spring2;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.net.URLDecoder;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;

import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import com.mystudy.domain.AttachFileDTO;

import lombok.extern.log4j.Log4j;
import net.coobird.thumbnailator.Thumbnailator;

@Controller
@Log4j
public class UploadController {

@GetMapping("/uploadForm")
public void uploadForm() {

log.info("upload form");
}

@PostMapping("/uploadFormAction")
public void uploadFormPost(MultipartFile[] uploadFile, Model model) {

String uploadFolder = "C:\\upload";

for (MultipartFile multipartFile : uploadFile) {

log.info("===========================");
log.info("upload file name : " + multipartFile.getOriginalFilename());
log.info("upload file size : " + multipartFile.getSize());

File saveFile = new File(uploadFolder, multipartFile.getOriginalFilename());

try {
multipartFile.transferTo(saveFile);
}catch (Exception e) {
log.error(e.getMessage());
}
}

}

 

 

 

 

 

//ajax


@GetMapping("/uploadAjax")
public void uploadAjax() {

log.info("upload ajax");
}

@PostMapping(value = "/uploadAjaxAction",
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ResponseBody
public ResponseEntity<List<AttachFileDTO>> uploadAjaxPost(MultipartFile[] uploadFile) {

List<AttachFileDTO> list = new ArrayList<>();

String uploadFolder = "C:\\upload";

String uploadFolderPath = getFolder();

// make folder -----------------------
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());

AttachFileDTO attachFileDTO = new AttachFileDTO();

String uploadFileName = multipartFile.getOriginalFilename();

//IE has file path
uploadFileName = uploadFileName.substring(uploadFileName.lastIndexOf("\\") + 1);
log.info("only file name : " + uploadFileName);
attachFileDTO.setFileName(uploadFileName);


UUID uuid = UUID.randomUUID();
uploadFileName = uuid.toString() + "_" + uploadFileName;



try {
File saveFile = new File(uploadPath, uploadFileName);
multipartFile.transferTo(saveFile);

attachFileDTO.setUuid(uuid.toString());
attachFileDTO.setUploadPath(uploadFolderPath);

//check image type file
if (checkImageType(saveFile)) {

attachFileDTO.setImage(true);

FileOutputStream thumbnail = new FileOutputStream(new File(uploadPath, "s_" + uploadFileName));

Thumbnailator.createThumbnail(multipartFile.getInputStream(), thumbnail, 100, 100);

thumbnail.close();
}

//add to List
list.add(attachFileDTO);

} catch (Exception e) {
log.error(e.getMessage());
}
}

return new ResponseEntity<List<AttachFileDTO>>(list, HttpStatus.OK);
}


@GetMapping("/display")
@ResponseBody
public ResponseEntity<byte[]> getFile(String fileName) {

log.info("fileName: " + fileName);

File file = new File("c:\\upload\\" + fileName);

log.info("file: " + file);

ResponseEntity<byte[]> result = null;

try {
HttpHeaders header = new HttpHeaders();

header.add("Content-Type", Files.probeContentType(file.toPath()));
result = new ResponseEntity<>(FileCopyUtils.copyToByteArray(file), header, HttpStatus.OK);
} catch (IOException e) {
e.printStackTrace();
}
return result;
}


@GetMapping(value = "/download", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
@ResponseBody
public ResponseEntity<Resource> downloadFile(@RequestHeader("User-Agent") String userAgent, String fileName) {

Resource resource = new FileSystemResource("c:\\upload\\" + fileName);

if (resource.exists() == false) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}

String resourceName = resource.getFilename();

// remove UUID
String resourceOriginalName = resourceName.substring(resourceName.indexOf("_") + 1);

HttpHeaders headers = new HttpHeaders();
try {

String downloadName = null;

if ( userAgent.contains("Trident")) {
log.info("IE browser");
downloadName = URLEncoder.encode(resourceOriginalName, "UTF-8").replaceAll("\\+", " ");
}else if(userAgent.contains("Edge")) {
log.info("Edge browser");
downloadName =  URLEncoder.encode(resourceOriginalName,"UTF-8");
}else {
log.info("Chrome browser");
downloadName = new String(resourceOriginalName.getBytes("UTF-8"), "ISO-8859-1");
}

log.info("downloadName: " + downloadName);

headers.add("Content-Disposition", "attachment; filename=" + downloadName);

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

return new ResponseEntity<Resource>(resource, headers, HttpStatus.OK);
}


@PostMapping("/deleteFile")
@ResponseBody
public ResponseEntity<String> deleteFile(String fileName, String type) {

log.info("deleteFile: " + fileName);

File file;

try {
file = new File("c:\\upload\\" + URLDecoder.decode(fileName, "UTF-8"));

file.delete();

if (type.equals("image")) {

String largeFileName = file.getAbsolutePath().replace("s_", "");

log.info("largeFileName: " + largeFileName);

file = new File(largeFileName);

file.delete();
}

} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}

return new ResponseEntity<String>("deleted", HttpStatus.OK);

}


private String getFolder() {

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

Date date = new Date();

String str = sdf.format(date);

return str.replace("-", File.separator);
}


private boolean checkImageType(File file) {

try {
String contentType = Files.probeContentType(file.toPath());

return contentType.startsWith("image");

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

return false;
}


}

 

 

 

 

 

uploadAjax.jsp

 

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>Upload with Ajax</h1>



<style>
.uploadResult {
width: 100%;
background-color: gray;
}

.uploadResult ul {
display: flex;
flex-flow: row;
justify-content: center;
align-items: center;
}

.uploadResult ul li {
list-style: none;
padding: 10px;
}

.uploadResult ul li img {
width: 100px;
}

.bigPictureWrapper {
  position: absolute;
  display: none;
  justify-content: center;
  align-items: center;
  top:0%;
  width:100%;
  height:100%;
  background-color: gray; 
  z-index: 100;
}

.bigPicture {
  position: relative;
  display:flex;
  justify-content: center;
  align-items: center;
}
</style>

<div class='bigPictureWrapper'>
  <div class='bigPicture'>
  </div>
</div>


<div class='uploadDiv'>
<input type='file' name='uploadFile' multiple>
</div>

<div class='uploadResult'>
<ul>

</ul>
</div>


<button id='uploadBtn'>Upload</button>

<script src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>

<script>

function showImage(fileCallPath){
  
  //alert(fileCallPath);

  $(".bigPictureWrapper").css("display","flex").show();
  
  $(".bigPicture")
  .html("<img src='/display?fileName="+ encodeURI(fileCallPath)+"'>")
  .animate({width:'100%', height: '100%'}, 1000);

}

$(".bigPictureWrapper").on("click", function(e){
  $(".bigPicture").animate({width:'0%', height: '0%'}, 1000);
/*    setTimeout(() => {
    $(this).hide();
  }, 1000); */
  
  setTimeout(function() {
  $(".bigPicture").hide();
  }, 1000);
  
});


$(".uploadResult").on("click","span", function(e){
   
  var targetFile = $(this).data("file");
  var type = $(this).data("type");
  console.log(targetFile);
  
  $.ajax({
    url: '/deleteFile',
    data: {fileName: targetFile, type:type},
    dataType:'text',
    type: 'POST',
      success: function(result){
         alert(result);
       }
  }); //$.ajax
  
});



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;
}

var cloneObj = $(".uploadDiv").clone();

$("#uploadBtn").on("click", function(e) {

var formData = new FormData();

var formData = new FormData();

var inputFile = $("input[name='uploadFile']");

var files = inputFile[0].files;

//console.log(files);

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 : '/uploadAjaxAction',
processData : false,
contentType : false,
data : formData,
type : 'POST',
dataType : 'json',
success : function(result) {

console.log(result);

showUploadedFile(result);

$(".uploadDiv").html(cloneObj.html());

}
}); //$.ajax

});

var uploadResult = $(".uploadResult ul");


 function showUploadedFile(uploadResultArr){
 
   var str = "";
   
   $(uploadResultArr).each(function(i, obj){
     
     if(!obj.image){
       
       var fileCallPath =  encodeURIComponent( obj.uploadPath+"/"+ obj.uuid +"_"+obj.fileName);
       
       var fileLink = fileCallPath.replace(new RegExp(/\\/g),"/");
       
       str += "<li><div><a href='/download?fileName="+fileCallPath+"'>"+
           "<img src='/resources/img/attach.png'>"+obj.fileName+"</a>"+
           "<span data-file=\'"+fileCallPath+"\' data-type='file'> x </span>"+
           "<div></li>"
           
     }else{
       
       var fileCallPath =  encodeURIComponent( obj.uploadPath+ "/s_"+obj.uuid +"_"+obj.fileName);
       
       var originPath = obj.uploadPath+ "\\"+obj.uuid +"_"+obj.fileName;
       
       originPath = originPath.replace(new RegExp(/\\/g),"/");
       
       str += "<li><a href=\"javascript:showImage(\'"+originPath+"\')\">"+
              "<img src='display?fileName="+fileCallPath+"'></a>"+
              "<span data-file=\'"+fileCallPath+"\' data-type='image'> x </span>"+
              "<li>";
     }
   });
   
   uploadResult.append(str);
 }

</script>


</body>
</html>

 

 


파일 업로드에서 고려해야 하는 것들

 

 

- 동일한 이름으로 파일 업로드 되었을때 기존 파일 사라지는 문제

- 이미지 파일의 경우에는 원본파일의 용량이 큰 경우 섬네일 이미지를 생성해야 하는 문제

- 이미지 파일과 일반 파일을 구분해서 다운로드 혹은 페이지에서 조회하도록 처리하는 문제

- 첨부파일 공격에 대비하기 위한 업로드 파일의 확장자 제한