설정
Multipart - config
<multipart-config>
<loaction>C:\\upload\\temp</location>
<max-file-size>20971520</max-file-size> // 20MB
<max-request-size>41943040</max-request-size> // 40MB
<file-size-threshold>20971520</file-size-threshold> // 20MB
</multipart-config>
XML
복사
이거는 WAS 설정일 뿐이고, 웹앱 설정은 따로 해줘야함.
스프링에
MultipartResolver라는 타입의 객체를 빈으로 등록해야한다.
@Bean
public MultipartResolver multipartResolver(){
StandardServletMultipartResolver resolver = new StandardServletMultipartResolver();
return resolver;
}
Java
복사
스프링에는 MultipartFile 타입을 제공해서 업로드되는 파일 데이터를 쉽게 처리할 수 있다.
MultipartFile
•
String getName() : 파라미터의 이름 <input> 태그의 이름
•
String getOriginalFileNAmE(): 업로드되는 파일의 이름
•
boolean isEmpty(): 파일이 존재하지 않는 경우 true
•
long getSize(): 업로드되는 파일의 크기
•
byte[] getBytes(): byte[]로 파일 데이터 변환
•
InputStream getInputStream(): 파일데이터와 연결된 InputStream을 반환
•
transferTo(File file): 파일의 저장
Client Ajax
<script>
$(document).ready(function(){
$("#uploadBtn").on("click",function(e){
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++){
formData.append("uploadFile",files[i]);
}
$.ajax({
url: '/uploadAjaxAction',
processData: false,
contentType: false,
data: formData,
type: 'POST',
success: function(result){
alert("Uploaded");
}
});
});
});
</script>
JavaScript
복사
FormData()라는 객체를 만들고
input 태그의 속성 중 하나인 files를 선택한 다음
그 파일들을 돌면서 formData에 파일 데이터를 추가시킨 후
Ajax로 보내버린다.
Server Ajax
@PostMapping("/uploadAjaxAction")
public void uploadAjaxPost(MultipartFile[] uploadFile)}
log.info("update ajax post....");
String uploadFolder = "C:\\upload";
for(Multipartfile multipartFile : uploadFile){
String uploadFileName = multipartfile.getPriginalFilename();
uploadfilename = uploadFileName.substring(uploadFileName.lastIndexOf("\\") + 1);
File saveFile = new File(uploadFolder, uploadFileName);
try{
multipartFile.transferTo(saveFile);
}catch(Exception e){
log.error(e.getMessage());
}
}
Java
복사
추가적으로 고쳐야할 점
•
동일한 이름으로 파일이 업로드 되었을 때 기존 파일이 사라지는 문제
•
이미지 파일의 경우에는 원본이 클 때, 썸네일을 생성해야하는 문제
•
이미지 파일과 일반 파일을 구분해서 다운로드 혹은 페이지에서 조회하도록 처리하는 문제
•
첨부파일 공격에 대비하기 위한 업로드 파일의 확장자 제한
파일 업로드 상세 처리
파일 확장자나 크기의 사전 처리
클라이언트 쪽에서 파일의 확장자는 차단시킨다.
서버 쪽에서도 한 번 더 체크한다. Regex
중복된 이름의 첨부파일 처리
1.
중복된 이름의 파일 처리 → UUID를 이용해서 중복이 발생할 가능성이 거의 없는 문자열을 생성해서 처리
2.
한 폴더 내에 너무 많은 파일의 생성 문제 → 년/월/일 단위의 폴더를 생성해서 파일을 저장
년/월/일 단위의 폴더를 생성
@PostMapping("/uploadAjaxAction")
public void uploadAjaxPost(MultipartFile[] uploadFile)}
log.info("update ajax post....");
String uploadFolder = "C:\\upload";
//make folder
File uploadPath = new File(uploadFolder,getFolder());
log.info("upload path: " + uploadPath);
if(uploadPath.exists() == false){
uploadPath.mkdirs();
}
//make yyyy/MM/dd folder
for(Multipartfile multipartFile : uploadFile){
String uploadFileName = multipartfile.getPriginalFilename();
uploadfilename = uploadFileName.substring(uploadFileName.lastIndexOf("\\") + 1);
File saveFile = new File(uploadFolder, uploadFileName);
try{
multipartFile.transferTo(saveFile);
}catch(Exception e){
log.error(e.getMessage());
}
}
Java
복사
getFolder() 는 오늘 날짜의 경로를 문자열로 생성. 생성된 경로는 폴더 경로로 수정된 뒤에 반환한다.
UUID 적용
@PostMapping("/uploadAjaxAction")
public void uploadAjaxPost(MultipartFile[] uploadFile)}
log.info("update ajax post....");
String uploadFolder = "C:\\upload";
//make folder
File uploadPath = new File(uploadFolder,getFolder());
log.info("upload path: " + uploadPath);
if(uploadPath.exists() == false){
uploadPath.mkdirs();
}
//make yyyy/MM/dd folder
for(Multipartfile multipartFile : uploadFile){
String uploadFileName = multipartfile.getPriginalFilename();
//이름 자르기
uploadfileName = uploadFileName.substring(uploadFileName.lastIndexOf("\\") + 1);
//UUID 생성
UUID uuid = UUID.randomUUID();
//이름에 uuid 붙이기
uploadFileName = uuid.toString() + "_" + uploadFileName;
//파일 생성
File saveFile = new File(uploadFolder, uploadFileName);
try{
//저장
multipartFile.transferTo(saveFile);
}catch(Exception e){
log.error(e.getMessage());
}
}
Java
복사
썸네일 이미지 생성
Thumbnailator 라이브러리를 이용해서 썸네일 이미지를 생성
이 때, Controller에서는
1.
업로드된 파일이 이미지 종류의 파일인지 확인
2.
이미지 파일의 경우에는 섬네일 이미지 생성 및 저장함.
이미지 파일의 판단
private boolean checkImageType(File file){
try{
String contentType = Files.probeContentType(file.toPath());
return contentType.starsWith("image");
}catch(IOException e){
e.printStackTrace();
}
return false;
}
Java
복사
이미지 파일을 판단하는 메소드
File 객체의 MimeType을 반환하는 probeContentType()함수를 이용해서 image인지 아닌지 검사함
@PostMapping(value = "/uploadAjaxAction",produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public void uploadAjaxPost(MultipartFile[] uploadFile)}
log.info("update ajax post....");
String uploadFolder = "C:\\upload";
//make folder
File uploadPath = new File(uploadFolder,getFolder());
log.info("upload path: " + uploadPath);
if(uploadPath.exists() == false){
uploadPath.mkdirs();
}
//make yyyy/MM/dd folder
for(Multipartfile multipartFile : uploadFile){
String uploadFileName = multipartfile.getPriginalFilename();
//이름 자르기
uploadfileName = uploadFileName.substring(uploadFileName.lastIndexOf("\\") + 1);
//UUID 생성
UUID uuid = UUID.randomUUID();
//이름에 uuid 붙이기
uploadFileName = uuid.toString() + "_" + uploadFileName;
try{
//파일 생성
File saveFile = new File(uploadFolder, uploadFileName);
//저장
multipartFile.transferTo(saveFile);
//이미지 인지 확인
if(checkImageType(saveFile)){
FileOutputStream thumbnail = new FileOutputStream(new File(uploadPath,"s_"+uploadFileName));
Thumnailator.createThumbnail(multipartfile.getInputStream(), thumbnail, 100, 100);
thumbnail.close()
}catch(Exception e){
log.error(e.getMessage());
}
}
Java
복사
업로드된 파일의 데이터 반환
전송되야하는 데이터
•
업로드된 파일의 이름과 원본 파일의 이름
•
파일이 저장된 경로
•
업로드된 파일이 이미지인지 아닌지에 대한 정보
→ AttachFileDTO 클래스를 만들어서 반환하도록 하자
@Data
public class AttachFileDTO{
private String fileName;
private String uploadPath;
private String uuid;
privaet boolean image;
}
Java
복사
—> 이러면 클라이언트에서 적당히 처리할 수 있다.
브라우저에서 썸네일 처리
클라이언트 쪽에서 썸네일 path를 주면 GET으로 처리한다.
@GetMapping("/display")
@ResponseBody
public ResponseEntity<byte[]> getFile(String fileName){
log.info("fileName: " + fileName);
Fie 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;
}
Java
복사
첨부 파일의 다운로드 혹은 원본 보여주기
이미지 썸네일 클릭 → 원본 보여주기(새로운 <div> 태그를 생성해서 처리하는 방식)
기타 다른 파일들 → 해당 파일의 이름으로 다운로드
첨부 파일의 다운로드
@GetMapping(value = "/download", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
@ResponseBody
public ResponseEntity<Resource> downloadFile(String fileName){
log.info("download file: " + fileName);
Resource resource = new FileSystemResource("c:\\upload\\" + fileName);
log.info("resource: " + resource);
String resourceName = resource.getFilename();
HttpHeaders headers = new HttpHeaders();
try{
headers.add("Content-Disposition", "attachment; filename=" + new String(resourceName.getBytes("UTF-8"),"ISO-8859-1"));
}catch(UnsupportedEncodingException e)}
e.printStackTrace();
}
return new ResponseEntity<Resource>(resource, headers, HttpStatus.OK);
}
Java
복사
서버에서 파일 이름에 UUID가 붙은 부분을 제거하고 다운로드
@GetMapping(value = "/download", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
@ResponseBody
public ResponseEntity<Resource> downloadFile(@RequestHeader("User-Agent") String userAgent,
String fileName){
log.info("download file: " + fileName);
//Resource 객체 생성
Resource resource = new FileSystemResource("c:\\upload\\" + fileName);
if(resource.exists() == false){
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
log.info("resource: " + resource);
String resourceName = resource.getFilename();
//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.getBytes("UTF-8"),"ISO-8859-1");
}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=" + new String(resourceName.getBytes("UTF-8"),"ISO-8859-1"));
}catch(UnsupportedEncodingException e)}
e.printStackTrace();
}
return new ResponseEntity<Resource>(resource, headers, HttpStatus.OK);
}
Java
복사
첨부파일 삭제
고려해야할 점
•
이미지 파일의 경우에는 썸네일가지 같이 삭제됨
•
파일을 삭제한 후에는 브라우저에서도 섬네일이나 파일 아이콘이 삭제되도록 처리하는 점
•
비정상적으로 브라우저의 종료 시 업로드된 파일의 처리
일반 파일과 이미지 파일의 삭제
@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("largerFileName: " + largerFileName);
file = new File(largeFileName);
file.delete();
}catch(UnsupportedEncodingException e){
e.printStackTrace();
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
return new ResponseEntity<String>("deleted",HttpStatus.OK);
}
Java
복사
프로젝트의 첨부파일 - 등록
첨부 + 게시물의 테이블 설계
AttachedFileDTO 참고해서 짜보기