Spring
[Spring Boot] 파일 업로드와 섬네일 처리
오은이
2023. 10. 9. 22:11
주요 기능
- 이미지 파일 업로드
- 이미지 여러개 동시 업로드
- 날짜 폴더 생성
- 섬네일 처리
- 이미지 삭제 처리
나중에 구현할 기능
- 모든 형식 파일 업로드 가능
- 파일 정보를 DB에 저장
application.yml
org:
zerock:
upload:
path: "C:\\upload"
spring:
servlet:
multipart:
enabled: 'true'
max-request-size: 30MB
max-file-size: 10MB
location: C:\upload
yml 파일에서 멀티파트의 (파일)데이터 처리 관련 속성 중, 파일 업로드 가능 여부를 true, 멀티파트 요청에 대한 최대 크기 제한, 멀티파트 요청 내의 개별 파일에 대한 최대 크기 제한, 업로드 된 파일이 서버의 파일 시스템에 저장될 위치를 지정한다.
참고로 아래의 yml과 properties 파일을 서로 변환시켜주는 사이트가 아주 유용하다.
UploadTestController
@Controller
public class UploadTestController {
@GetMapping("/uploadEx")
public void uploadEx() {
}
}
UploadController
@RestController
@Log4j2
public class UploadController {
@Value("${org.zerock.upload.path}")
private String uploadPath;
@PostMapping("/uploadAjax")
public ResponseEntity<List<UploadResultDTO>> uploadFile(MultipartFile[] uploadFiles) throws IOException{
List<UploadResultDTO> resultDTOList = new ArrayList<>();
for(MultipartFile uploadFile : uploadFiles) {
//이미지 파일만 업로드 가능
if(uploadFile.getContentType().startsWith("image") == false) {
log.warn("this file is not image type");
return new ResponseEntity<>(HttpStatus.FORBIDDEN);
}
//실제 파일 이름 IE나 Edge나 전체 경로가 들어오므로
String originalName = uploadFile.getOriginalFilename();
String fileName = originalName.substring(originalName.lastIndexOf("\\") + 1);
log.info("fileName : " + fileName);
//날짜 폴더 생성
String folderPath = makeFolder();
String uuid = UUID.randomUUID().toString();
//저장할 파일 이름 중간에 "_"를 사용해 구분
String saveName = uploadPath + File.separator + folderPath +
File.separator + uuid + "_" + fileName;
Path savePath = Paths.get(saveName);
try {
uploadFile.transferTo(savePath); //원본 이미지 저장
//섬네일 생성
String thumbnailSaveName = uploadPath + File.separator + folderPath + File.separator
+ "s_" + uuid + "_" + fileName;
//섬네일 파일 이름은 s_로 시작하도록함
File thumbnailFile = new File(thumbnailSaveName);
//섬네일 생성
Thumbnailator.createThumbnail(savePath.toFile(), thumbnailFile, 100, 100);
resultDTOList.add(new UploadResultDTO(fileName, uuid, folderPath));
} catch (IOException e) {
e.printStackTrace();
}
}
return new ResponseEntity<>(resultDTOList, HttpStatus.OK);
}
private String makeFolder() {
String str = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
String folderPath = str.replace("/", File.separator);
//make folder
File uploadPathFolder = new File(uploadPath, folderPath);
if(uploadPathFolder.exists() == false) {
uploadPathFolder.mkdirs();
}
return folderPath;
}
@GetMapping("/display")
public ResponseEntity<byte[]> getFile(String fileName) {
ResponseEntity<byte[]> result = null;
try {
String srcFileName = URLDecoder.decode(fileName, "UTF-8");
log.info("fileName : " + srcFileName);
File file = new File(uploadPath + File.separator + srcFileName);
log.info("file : " + file);
HttpHeaders header = new HttpHeaders();
//MIME 타입 처리
header.add("Content-Type", Files.probeContentType(file.toPath()));
//파일 데이터 처리
result = new ResponseEntity<>(FileCopyUtils.copyToByteArray(file),
header, HttpStatus.OK);
} catch (Exception e) {
log.error(e.getMessage());
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
return result;
}
//업로드 파일 삭제
@PostMapping("/removeFile")
public ResponseEntity<Boolean> removeFiles(String fileName) {
String srcFileName = null;
try {
srcFileName = URLDecoder.decode(fileName, "UTF-8");
File file = new File(uploadPath + File.separator + srcFileName);
boolean result = file.delete();
File thumbnail = new File(file.getParent(), "s_" + file.getName());
result = thumbnail.delete();
return new ResponseEntity<>(result, HttpStatus.OK);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return new ResponseEntity<>(false, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
UploadResultDTO
@Data
@AllArgsConstructor
public class UploadResultDTO implements Serializable {
private String fileName;
private String uuid;
private String folderPath;
public String getImageURL() {
try {
return URLEncoder.encode(folderPath + "/" + uuid + "_" + fileName, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return "";
}
public String getThumbnailURL() {
try {
return URLEncoder.encode(folderPath + "/s_" + uuid + "_" + fileName, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return "";
}
}
uploadEx.html
<!DOCTYPE html>
<html leng="en" xmlns="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<input name="uploadFiles" type="file" multiple>
<button class="uploadBtn">Upload</button>
<div class="uploadResult">
</div>
<script
src="https://code.jquery.com/jquery-3.5.1.min.js"
integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="
crossorigin="anonymous"></script>
<script>
$('.uploadBtn').click(function( ) {
var formData = new FormData();
var inputFile = $("input[type='file']");
var files = inputFile[0].files;
for(var i = 0; i < files.length; i++) {
console.log(files[i]);
formData.append("uploadFiles", files[i]);
}
function showUploadedImages(arr) {
console.log(arr);
var divArea = $(".uploadResult");
var str = "";
for(var i = 0; i < arr.length; i++) {
str += "<div>";
str += "<img src = '/display?fileName=" + arr[i].thumbnailURL+"'>";
str += "<button class='removeBtn' data-name='"+arr[i].imageURL +"'>REMOVE</button>"
str += "</div>";
divArea.append(str);
}
}
//실제 업로드 부분
$.ajax({
url: '/uploadAjax',
processData: false,
contentType: false,
data: formData,
type: 'POST',
dataType: 'json',
success: function(result) {
showUploadedImages(result);
//나중에 화면 처리
},
error: function(jqXHR, textStatus, errorThrown) {
console.log(textStatus);
}
});
$(".uploadResult").on("click", ".removeBtn", function(e) {
var target = $(this);
var fileName = target.data("name");
var targetDiv = $(this).closest("div");
console.log(fileName);
$.post('/removeFile', {fileName: fileName}, function(result) {
console.log(result);
if(result == true) {
targetDiv.remove();
}
})
});
});
</script>
</body>
</html>
실행 화면 - 포트 번호 : 82


전용 날짜 폴더를 생성하고, UUID로 이름이 중복되지 않게 저장한다.
섬네일 처리가 되어 크기가 100X100으로 맞춰진 상태이며 REMOVE 버튼 클릭 시 삭제된다.