# Spring - 파일업로드 연습1 (일반적인 방식)
# Spring - 파일업로드 연습2 (파일명 중복제거)
# Spring - 파일업로드 연습3 (업로드 결과를 iframe에 출력)
# Spring - 파일업로드 연습4 AJAX방식(파일 저장 디렉토리, 썸네일 이미지 생성)
# AJAX(Asynchronous Javascript And XML)란?
이전의 포스팅에서는 ajax방식으로 파일을 날짜별 디렉토리에 자동으로 저장하고, 이미지 파일의 경우에는 썸네일 이미지 생성하는 것까지 구현해보았다. 이번에는 업로드한 파일의 이름을 출력하고, 이미지 파일이면 썸네일로 사진을 미리볼 수 있게 구현해보자. 그리고 마지막으로 업로드한 파일을 다운로드하거나 삭제도 할 수 있도록 구현해보자.
View - uploadAjax.jsp(업로드 페이지)
1. ajax 파일업로드 요청 처리, 이미지파일은 썸네일 출력, 일반파일은 파일명 출력
소스코드
$(".fileDrop").on("drop", function(event) {
event.preventDefault();
var files = event.originalEvent.dataTransfer.files;
var file = files[0];
console.log(file);
var formData = new FormData();
formData.append("file", file);
$.ajax({
type: "post",
url: "${path}/upload/uploadAjax",
data: formData,
dataType: "text",
processData: false,
contentType: false,
// 업로드 성공하면
success: function(data) {
var str = "";
// 이미지 파일이면 썸네일 이미지 출력
if(checkImageType(data)){
str = "<div><a href='${path}/upload/displayFile?fileName="+getImageLink(data)+"'>";
str += "<img src='${path}/upload/displayFile?fileName="+data+"'></a>";
// 일반파일이면 다운로드링크
} else {
str = "<div><a href='${path}/upload/displayFile?fileName="+data+"'>"+getOriginalName(data)+"</a>";
}
// 삭제 버튼
str += "<span data-src="+data+">[삭제]</span></div>";
$(".uploadedList").append(str);
}
});
});
ajax 파일 업로드 요청(
/uploadAjax
)이 성공적으로 처리가 되면checkImageType()
함수를 호출하여 이미지 파일 여부를 판별한다. 업로드된 파일이 이미지이면 ajax 이미지 출력요청(/displayFile
)을 처리한다. 썸네일 이미지는 업로드 파일 목록 영역$(".uploadedList").append(str)
)에 출력하고, 원본 이미지를 링크한<a href>
태그로 감싸서 썸네일 이미지를 클릭하면 원본사이즈의 이미지를 볼 수 있게 구현했다.
업로드된 파일이 일반 파일일 경우에는 원본파일의 이름만 목록에 출력해주고, 클릭시 다운로드가 되도록 원본 파일을 링크한<a href>
태그로 감쌌다.
구현화면
목록에 썸네일 이미지, 파일명 출력, 크롬관리자도구에서 업로드된 파일확인 콘솔확인
원본이미지 보기 화면
일반 파일일 경우 다운로드
2. ajax 파일삭제 요청 처리
소스코드
$(".uploadedList").on("click", "span", function(event){
alert("이미지 삭제")
var that = $(this); // 여기서 this는 클릭한 span태그
$.ajax({
url: "${path}/upload/deleteFile",
type: "post",
// data: "fileName="+$(this).attr("date-src") = {fileName:$(this).attr("data-src")}
// 태그.attr("속성")
data: {fileName:$(this).attr("data-src")}, // json방식
dataType: "text",
success: function(result){
if( result == "deleted" ){
// 클릭한 span태그가 속한 div를 제거
that.parent("div").remove();
}
}
});
});
업로드한 파일을 목록에서 삭제하기 위해
<span>
태그를 클릭 이벤트로 설정하고,<span>
태그를 클릭을 했는지 인지할 수 있도록 alert창이 뜨게 처리도 하였다. ajax 삭제요청이 처리되고 나면 클릭한span
의 부모태그인div
를 삭제하게된다.$(this)
를 사용하면 보다 간편하게 코드를 작성할 수가 있다.$(this)
를 알기 전까지는 목록의 레코드마다 id속성을 배열 만들어서로 처리했지만$(this)
를 사용하면 배열로 처리하지 않고 클릭 이벤트만으로 구분을 할 수가 있기 때문에 보다 간결한 코드를 작성할 수 있었다.
구현화면
파일 삭제 처리 전 업로드 파일 목록
파일 삭제 처리 전 저장디렉토리
이미지 파일 삭제
파일 업로드 목록에서 파일 삭제처리 확인
저장디렉토리에서 파일이 삭제처리 확인
3. 파일관련 스크립트
// 원본파일이름을 목록에 출력하기 위해
function getOriginalName(fileName) {
// 이미지 파일이면
if(checkImageType(fileName)) {
return; // 함수종료
}
// uuid를 제외한 원래 파일 이름을 리턴
var idx = fileName.indexOf("_")+1;
return fileName.substr(idx);
}
업로드된 파일이 이미지가 아닐 경우 업로드파일 목록에 파일명을 출력해주기 위한 함수이다.
파일명의 중복 저장을 방지하기 위해 생성한 UUID를 제거하고 나머지 원본이름만 리턴해준다.
// 이미지파일 링크 - 클릭하면 원본 이미지를 출력해주기 위해
function getImageLink(fileName) {
// 이미지파일이 아니면
if(!checkImageType(fileName)) {
return; // 함수 종료
}
// 이미지 파일이면(썸네일이 아닌 원본이미지를 가져오기 위해)
// 썸네일 이미지 파일명 - 파일경로+파일명 /2017/03/09/s_43fc37cc-021b-4eec-8322-bc5c8162863d_spring001.png
var front = fileName.substr(0, 12); // 년원일 경로 추출
var end = fileName.substr(14); // 년원일 경로와 s_를 제거한 원본 파일명을 추출
console.log(front); // /2017/03/09/
console.log(end); // 43fc37cc-021b-4eec-8322-bc5c8162863d_spring001.png
// 원본 파일명 - /2017/03/09/43fc37cc-021b-4eec-8322-bc5c8162863d_spring001.png
return front+end; // 디렉토리를 포함한 원본파일명을 리턴
}
썸네일 이미지를 클릭하면 원본 이미지가 출력될 수 있도록 원본이미지 파일명을 추출하기 위한 함수이다.
변수front
는 년월일 디렉토리를 추출하고, 변수end
는 년월일과 썸네일 이미지임을 알려주는 “s_”를 제거한 원본 이미지의 파일명만을 추출한다.
년월일 디렉토리와 원본파일명을 합친 문자열을 리턴한다.
// 이미지파일 형식을 체크하기 위해
function checkImageType(fileName) {
// i : ignore case(대소문자 무관)
var pattern = /jpg|gif|png|jpeg/i; // 정규표현식
return fileName.match(pattern); // 규칙이 맞으면 true
}
파일이 업로드되면 목록영역에 이미지파일은 썸네일을 일반파일이면 원본파일명을 출력하기위해서 이미지 파일의 여부를 판별하기 위한 함수이다.
정규표현식을 사용하여 jpg, gif, png, jpeg과 일치하는 문자열이 포함되어 있으면 true를 리턴하고 아니면 false를 리턴한다.
이 함수의 문제점은 업로드된 파일명이 파일의 확장자가 아닌 파일명에 jpg, gif, png, jpeg가 들어가면 사진파일로 인식한다는 문제가 발생하는데 이는 추후에 수정해보자.
4. HTML body영역
<body>
<h2>AJAX File Upload</h2>
<!-- 파일을 업로드할 영역 -->
<div class="fileDrop"></div>
<!-- 업로드된 파일 목록 -->
<div class="uploadedList"></div>
<body>
Controller - UploadController(업로드 관련 컨트롤러)
@Controller
public class UploadController {
private static final Logger logger = LoggerFactory.getLogger(UploadController.class);
// xml에 설정된 리소스 참조
// bean의 id가 uploadPath인 태그를 참조
@Resource(name="uploadPath")
String uploadPath;
// 업로드 흐름 : 업로드 버튼클릭 => 임시디렉토리에 업로드=> 지정된 디렉토리에 저장 => 파일정보가 file에 저장
/****************************** # 일반적인 방식의 업로드 처리 *********************************/
// 1. 일반적인 업로드 매핑
@RequestMapping(value="/upload/uploadForm", method=RequestMethod.GET)
public void uplodaForm(){
// upload/uploadForm.jsp(업로드 페이지)로 포워딩
}
// 2. 일반적인 업로드 처리 매핑
@RequestMapping(value="/upload/uploadForm", method=RequestMethod.POST)
public ModelAndView uplodaForm(MultipartFile file, ModelAndView mav) throws Exception{
// 파일의 원본이름 저장
String savedName = file.getOriginalFilename();
// 파일의 정보 로그 출력
logger.info("파일이름 :"+file.getOriginalFilename());
logger.info("파일크기 : "+file.getSize());
logger.info("컨텐트 타입 : "+file.getContentType());
// 랜덤생성+파일이름 저장하기 위해 파일명 랜덤생성 메서드호출
savedName = uploadFile(savedName, file.getBytes());
mav.setViewName("upload/uploadResult");
mav.addObject("savedName", savedName);
return mav; // uploadResult.jsp(결과화면)로 포워딩
}
// 3. 파일명 랜덤생성 메서드
private String uploadFile(String originalName, byte[] fileData) throws Exception{
// UUID 발급
UUID uuid = UUID.randomUUID();
// 파일명 = UUID + 원본이름
String savedName = uuid.toString() + "_" + originalName;
// 파일 경로, 파일명을 받아 파일 객체 생성
File target = new File(uploadPath, savedName);
// 임시디렉토리에 저장된 업로드된 파일을 지정된 디렉토리로 복사
// FileCopyUtils.copy(바이트배열, 파일객체)
FileCopyUtils.copy(fileData, target);
return savedName;
}
/****************************** # 일반적인 방식의 업로드 처리 *********************************/
/****************************** # ajax 방식의 업로드 처리 *********************************/
// 4. Ajax업로드 페이지 매핑
@RequestMapping(value="/upload/uploadAjax", method=RequestMethod.GET)
public void uploadAjax(){
// uploadAjax.jsp로 포워딩
}
// 5. Ajax업로드 처리 매핑
// 파일의 한글처리 : produces="text/plain;charset=utf-8"
@ResponseBody // view가 아닌 data리턴
@RequestMapping(value="/upload/uploadAjax", method=RequestMethod.POST, produces="text/plain;charset=utf-8")
public ResponseEntity<String> uploadAjax(MultipartFile file) throws Exception {
logger.info("originalName : "+file.getOriginalFilename());
logger.info("size : "+file.getSize());
logger.info("contentType : "+file.getContentType());
return new ResponseEntity<String>(UploadFileUtils.uploadFile(uploadPath, file.getOriginalFilename(), file.getBytes()), HttpStatus.OK);
}
// 6. 이미지 표시 매핑
@ResponseBody // view가 아닌 data리턴
@RequestMapping("/upload/displayFile")
public ResponseEntity<byte[]> displayFile(String fileName) throws Exception {
// 서버의 파일을 다운로드하기 위한 스트림
InputStream in = null; //java.io
ResponseEntity<byte[]> entity = null;
try {
// 확장자를 추출하여 formatName에 저장
String formatName = fileName.substring(fileName.lastIndexOf(".") + 1);
// 추출한 확장자를 MediaUtils클래스에서 이미지파일여부를 검사하고 리턴받아 mType에 저장
MediaType mType = MediaUtils.getMediaType(formatName);
// 헤더 구성 객체(외부에서 데이터를 주고받을 때에는 header와 body를 구성해야하기 때문에)
HttpHeaders headers = new HttpHeaders();
// InputStream 생성
in = new FileInputStream(uploadPath + fileName);
// 이미지 파일이면
if (mType != null) {
headers.setContentType(mType);
// 이미지가 아니면
} else {
fileName = fileName.substring(fileName.indexOf("_") + 1);
// 다운로드용 컨텐트 타입
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
//
// 바이트배열을 스트링으로 : new String(fileName.getBytes("utf-8"),"iso-8859-1") * iso-8859-1 서유럽언어, 큰 따옴표 내부에 " \" 내용 \" "
// 파일의 한글 깨짐 방지
headers.add("Content-Disposition", "attachment; filename=\""+new String(fileName.getBytes("utf-8"), "iso-8859-1")+"\"");
//headers.add("Content-Disposition", "attachment; filename='"+fileName+"'");
}
// 바이트배열, 헤더, HTTP상태코드
entity = new ResponseEntity<byte[]>(IOUtils.toByteArray(in), headers, HttpStatus.OK);
} catch (Exception e) {
e.printStackTrace();
// HTTP상태 코드()
entity = new ResponseEntity<byte[]>(HttpStatus.BAD_REQUEST);
} finally {
in.close(); //스트림 닫기
}
return entity;
}
// 7. 파일 삭제 매핑
@ResponseBody // view가 아닌 데이터 리턴
@RequestMapping(value = "/upload/deleteFile", method = RequestMethod.POST)
public ResponseEntity<String> deleteFile(String fileName) {
// 파일의 확장자 추출
String formatName = fileName.substring(fileName.lastIndexOf(".") + 1);
// 이미지 파일 여부 검사
MediaType mType = MediaUtils.getMediaType(formatName);
// 이미지의 경우(썸네일 + 원본파일 삭제), 이미지가 아니면 원본파일만 삭제
// 이미지 파일이면
if (mType != null) {
// 썸네일 이미지 파일 추출
String front = fileName.substring(0, 12);
String end = fileName.substring(14);
// 썸네일 이미지 삭제
new File(uploadPath + (front + end).replace('/', File.separatorChar)).delete();
}
// 원본 파일 삭제
new File(uploadPath + fileName.replace('/', File.separatorChar)).delete();
// 데이터와 http 상태 코드 전송
return new ResponseEntity<String>("deleted", HttpStatus.OK);
}
/****************************** # ajax 방식의 업로드 처리 *********************************/
}