서버측
1. 업로드된 파일이 이미지 파일인지 확인
1.1 이미지 파일일 경우 썸네일 이미지를 생성하고 저장한다.
→ 별도의 객체를 생성하여 처리한다.
브라우저측
사용자가 업로드한 첨부파일이 이미지일 경우, 썸네일을 제작하기 위해 라이브러리를 사용한다.
<!-- https://mvnrepository.com/artifact/net.coobird/thumbnailator -->
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.17</version>
</dependency>
UploadController
private boolean checkImageType(File file) {
try {
String contentType = Files.probeContentType(file.toPath());
return contentType.startsWith("image");
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
@PostMapping("/ajaxUpload")
public void uploadAjax(MultipartFile[] uploadFile){
log.info("post /ajaxUpload");
String uploadPath = "/Users/jueon/Desktop/study/spring/upload/temp";
// 폴더 객체 생성
File uploadFolder = new File(uploadPath, getFolder());
// 년/월/일 폴더 생성
if (!uploadFolder.exists()) uploadFolder.mkdirs();
for (MultipartFile file : uploadFile) {
String fileName = file.getOriginalFilename();
UUID uuid = UUID.randomUUID();
fileName = uuid.toString() + "_" + fileName;
try {
File saveFile = new File(uploadFolder, fileName);
file.transferTo(saveFile);
// 이미지 파일 여부 확인
if (checkImageType(saveFile)) {
// FileOutputStream() : 파라미터로 파일을 받고 해당 파일의 outputStream을 반환한다. (output = write )
FileOutputStream thumbnail = new FileOutputStream(new File(uploadFolder, "s_" + fileName));
// createThumbnail(inputStream, outputStream, width, height)
// InputStream과 File객체를 이용하여 파일을 생성한다
Thumbnailator.createThumbnail(file.getInputStream(), thumbnail, 100, 100);
thumbnail.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
파일 업로드 작업에 대해 브라우저에게 피드백을 전송한다.
자바측에서는 별도의 객체를 생성하여 이를 처리하고 결과를 JSON 형태로 브라우저에게 전송한다.
JSON 형태로 변환을 위한 라이브러리를 추가한다.
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.4</version>
</dependency>
첨부파일의 정보를 저장하는 클래스를 생성한다.
package com.ze.domain;
import lombok.Data;
@Data
public class AttachFileDTO {
private String fileName; // 원본 파일의 이름
private String uploadFolder; // 업로드된 경로
private String uuid; // uuid 값
private boolean image; // 이미지 파일 여부
}
@PostMapping(value= "/ajaxUpload", produces = "application/json") // JSON 반환을 위한 옵션 추가
@ResponseBody // 어노테이션 추가
public ResponseEntity<List<AttachFileDTO>> uploadAjax(MultipartFile[] uploadFile){
log.info("post /ajaxUpload");
List<AttachFileDTO> list = new ArrayList<>();
String uploadPath = "/Users/jueon/Desktop/study/spring/upload/temp";
File uploadFolder = new File(uploadPath, getFolder());
if (!uploadFolder.exists()) uploadFolder.mkdirs();
for (MultipartFile file : uploadFile) {
AttachFileDTO attachFileDTO = new AttachFileDTO();
// 원본 파일명
String fileName = file.getOriginalFilename();
attachFileDTO.setFileName(fileName); // 원본 파일명을 DTO에 저장한다.
UUID uuid = UUID.randomUUID();
attachFileDTO.setUuid(uuid.toString()); // uuid 저장
attachFileDTO.setUploadFolder(uploadFolder.getPath()); // uploadFolder 경로 저장
fileName = uuid.toString() + "_" + fileName;
try {
File saveFile = new File(uploadFolder, fileName);
file.transferTo(saveFile);
// 이미지 파일 여부 확인
if (checkImageType(saveFile)) {
attachFileDTO.setImage(true); // 이미지 파일일 경우
FileOutputStream thumbnail = new FileOutputStream(new File(uploadFolder, "s_" + fileName));
Thumbnailator.createThumbnail(file.getInputStream(), thumbnail, 100, 100);
thumbnail.close();
}
list.add(attachFileDTO);
} catch (IOException e) {
e.printStackTrace();
}
}
return new ResponseEntity<>(list, HttpStatus.OK);
}
위와 같이 수정 후, 브라우저에서 AJAX 처리 후 결과를 확인해보면 아래와 같은 결과가 나온다.
<input>
태그 초기화 $.ajax({
url : '/ajaxUpload',
processData : false,
contentType : false,
method: 'post',
data: formData,
dataType : 'json',
success : function(result) {
console.log(result);
$("#uploadInputEL").val(""); // 빈 값을 넣어서 초기화 해준다
}
}) // $.ajax
서버로부터 JSON 형태로 받은 업로드 결과를 이용하여 화면에 파일 아이콘 등을 출력하도록 한다.
이미지 파일이 아닌 경우 파일 아이콘을 출력하고, 이미지 파일인 경우 해당 이미지 파일에 대한 썸네일을 출력한다.
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<script src="https://code.jquery.com/jquery-3.6.1.min.js"
integrity="sha256-o88AwQnZB+VDvE9tvIXrMQaPlFFSUTR+nldQm1LuPXQ=" crossorigin="anonymous"></script>
</head>
<body>
<div class="uploadDiv">
<input type="file" name="uploadFiles" multiple id="uploadInputEL">
</div>
<button id="uploadBtn" >Upload</button>
<div class="uploadResult">
<ul id="file-list">
</ul>
</div>
<script>
$(function () {
//...
$("#uploadBtn").on("click", function (e){
let formData = new FormData()
let inputFile = $("input[name='uploadFiles']");
let files = inputFile[0].files;
for (let i = 0; i < files.length; i++) {
if(!checkExtentsion(files[i].name, files[i].size)) return false;
formData.append("uploadFile", files[i]);
}
$.ajax({
url : '/ajaxUpload',
processData : false,
contentType : false,
method: 'post',
data: formData,
dataType : 'json', // 반환 받는 결과 데이터의 타입
success : function(result) {
$("#uploadInputEL").val("");
let liEl = "";
$.each(result, (idx,file) => {
console.log(file)
if (!file.image) { // 일반 파일일 경우
liEl +=
"<li><img src='/resources/images/file-icon.png'/>" + file.fileName+"</li>"
} else {
liEl += "<li>" + file.fileName + "</li>"
}
})
$("#file-list").html(liEl); // ul태그 아래에 li 태그들을 붙여준다
}
}) // $.ajax
}) // .on("click")
}) // $(function(){})
</script>
</body>
</html>
$.each(반복가능한 객체, 콜백함수)
썸네일은 서버를 통해 특정 URI를 호출하면 보여줄 수 있도록 처리한다. (GET 방식)
특정한 URI 뒤에 파일 이름을 추가하면 이미지 파일 데이터를 가져와서 이미지태그를 작성하여 화면에 출력한다.
브라우저 → 서버로 전송되는 데이터는 '파일의 경로 + s_ + uuid'이며, 전송 시 경로나 파일 이름에 한글 또는 공백 등의 문자가 들어있으면 문제가 발생한다. 이를 방지하기 위해서 JS의 함수를 이용한다.
특정 파일 이름을 받아서 이미지 데이터를 전송하는 코드를 작성한다.
@GetMapping("/display")
@ResponseBody
public ResponseEntity<byte[]> getFile(String filename) {
log.info("filename : " + filename);
File file = new File("/Users/jueon/Desktop/study/spring/upload/temp/" + 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;
}
getFile()
은 파라미터로 파일 경로가 포함된 파일의 이름을 받고, 바이트 배열을 반환한다.
바이트 배열로 이미지 파일 데이터 전송 시, 브라우저로 전달하는 MIME 타입이 파일의 종류에 따라 달라지는 점을 주의한다.
MimeType 이란?
MIME 타입이란 클라이언트에게 전송된 문서의 다양성을 알려주기 위한 메커니즘: 웹에서 파일의 확장자는 별 의미가 없습니다. 그러므로, 각 문서와 함께 올바른 MIME 타입을 전송하도록, 서버가 정확히 설정하는 것이 중요합니다. 브라우저들은 리소스를 내려받았을 때 해야 할 기본 동작이 무엇인지를 결정하기 위해 대게 MIME 타입을 사용합니다.
Files 객체의 probeContentType()
메소드
마임타입을 확인하지 못하면 null을 반환한다. 실제 파일의 내용이 아니라 파일의 확장자를 이용하여 마임타입을 판단한다. 확장자가 없는 파일은 null을 반환. 실제 파일이 존재하지 않아도 확장자를 이용하여 마임 타입을 반환한다.
probeContentType()
메소드를 이용하여 적절한 마임 타입 데이터를 HTTP 헤더 메세지에 포함시킨다.
예를 들어 jpg 파일의 경우 헤더의 Content-Type은 image/jpg가 되고 png의 경우 image/png가 됨
테스트해 보려면 이런식으로..url창에 쓰면 댐
http://localhost:8080/display?filename=2022/10/17/spring.png