[SpringBoot] ajax로 사진 업로드와 삭제, 썸네일 미리보기 기능 구현하기
안녕하세요
오늘은 ajax를 이용하여 사진 업로드 기능을 구현하고자 합니다.
보통 게시판 등의 웹사이트를 만들면 사진을 업로드하는 기능이 흔히 들어가고는 합니다.
이 사진 업로드하는 기능을 만들면서 조금 애를 먹었어서 정보를 공유해보려고 합니다.
먼저 결과물 보여드리겠습니다ㅎㅎ
기능이 따로 특별한건 없고
(관리자페이지와 왼쪽의 네비게이션 영역은 공통레이아웃이니 신경안쓰셔도 됩니다)
유저가 사진을 추가 버튼을 누르고 사진을 추가하면 추가한 사진들의 썸네일을 보여준 뒤,
썸네일을 누르면 전송 목록 및 썸네일에서 누른 사진을 삭제,
전송하기 버튼으로 사진들을 전송하면 서버에서 사진을 받아 처리한 뒤
그 결과를 아래에 띄워주는 형태입니다.
그럼 포스팅을 시작해보도록 하겠습니다.
※참고
혹시 Thymeleaf에서 ajax 사용하는 것에 익숙하지않으시다면 아래의 글을 먼저 보고오시면 도움이 되실겁니다:)
[Thymeleaf] ajax사용법 및 예제(SpringBoot)
1. View템플릿 작성하기
먼저 html코드를 이용해 아래와 같이 사용자가 볼 화면을 만들어줍니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{cmmn/adminLayout}">
<body>
<div layout:fragment="content">
<script src="/js/storeDataSendAjax.js"></script>
<script src="/js/imagePreview.js"></script>
<div class="row border-bottom" style="margin-top: 10px; margin-bottom: 10px;"></div>
<form id="addForm">
<div style="display: inline;">
<label for="img_upload">
<img src="/img/photo_add.png" style="width:100px; height:100px; cursor: pointer;">
</label>
<input type="file" name="img_upload" id="img_upload"
onchange="previewImage(this,'View_area')"
style="display: none;" multiple>
<span id='View_area'
style='position:relative; color: black; border: 0px solid black;'>
</span>
</div>
<div style="align-content: center; width: 100%; text-align: center;">
<input type="button" class="btn" style="background: #FF6491; color: #FFF2F6;" onclick="dataSubmit();"
value="전송하기">
</div>
<div id="resultDiv">
<p th:text="${log}"></p>
</div>
</form>
</div>
</body>
</html>
|
cs |
코드 설명 드리겠습니다.
1) 서버 전송을 위해 form태그로 사진 업로드 부분을 감싸기
<form id="addForm">
2) 이미지 업로드할 부분의 템플릿 작성
<div style="display: inline;">
<label for="img_upload">
<img src="/img/photo_add.png" style="width:100px; height:100px; cursor: pointer;">
</label>
<input type="file" name="img_upload" id="img_upload"
onchange="previewImage(this,'View_area')"
style="display: none;" multiple>
<span id='View_area'
style='position:relative; color: black; border: 0px solid black;'>
</span>
</div>
중요한 부분은 input type을 file로 하고 name과 id를 모두 동일한 것으로 작성해줘야 합니다.
(둘중에 하나라도 없으면 form 전송기능이 안되더라고요)
3) 서버에서 받은 결과 로그 찍기
<div id="resultDiv">
<p th:text="${log}"></p>
</div>
그리고 마지막으로 최하단의 resultDiv에서 서버에서 받은 결과를 띄워주도록 합니다.
2. JavaScript 템플릿 작성하기
자바스크립트에서는 썸네일 띄워주기, 파일 삭제기능을 제작합니다.
먼저 전체 소스코드 부터 보여드리겠습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
|
var fileArr;
var fileInfoArr=[];
//썸네일 클릭시 삭제. function fileRemove(index) {
console.log("index: "+index);
fileInfoArr.splice(index,1);
var imgId="#img_id_"+index;
$(imgId).remove();
console.log(fileInfoArr);
}
//썸네일 미리보기. function previewImage(targetObj, View_area) {
var files=targetObj.files;
fileArr=Array.prototype.slice.call(files);
var preview = document.getElementById(View_area); //div id
var ua = window.navigator.userAgent;
//ie일때(IE8 이하에서만 작동)
if (ua.indexOf("MSIE") > -1) {
targetObj.select();
try {
var src = document.selection.createRange().text; // get file full path(IE9, IE10에서 사용 불가)
var ie_preview_error = document.getElementById("ie_preview_error_" + View_area);
if (ie_preview_error) {
preview.removeChild(ie_preview_error); //error가 있으면 delete
}
var img = document.getElementById(View_area); //이미지가 뿌려질 곳
//이미지 로딩, sizingMethod는 div에 맞춰서 사이즈를 자동조절 하는 역할
img.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+src+"', sizingMethod='scale')";
} catch (e) {
if (!document.getElementById("ie_preview_error_" + View_area)) {
var info = document.createElement("<p>");
info.id = "ie_preview_error_" + View_area;
info.innerHTML = e.name;
preview.insertBefore(info, null);
}
}
//ie가 아닐때(크롬, 사파리, FF)
} else {
var files = targetObj.files;
for ( var i = 0; i < files.length; i++) {
var file = files[i];
fileInfoArr.push(file);
var imageType = /image.*/; //이미지 파일일경우만.. 뿌려준다.
if (!file.type.match(imageType))
continue;
// var prevImg = document.getElementById("prev_" + View_area); //이전에 미리보기가 있다면 삭제
// if (prevImg) {
// preview.removeChild(prevImg);
// }
var span=document.createElement('span');
span.id="img_id_" +i;
span.style.width = '100px';
span.style.height = '100px';
preview.appendChild(span);
var img = document.createElement("img");
img.className="addImg";
img.classList.add("obj");
img.file = file;
img.style.width='inherit';
img.style.height='inherit';
img.style.cursor='pointer';
const idx=i;
img.onclick=()=>fileRemove(idx); //이미지를 클릭했을 때.
span.appendChild(img);
if (window.FileReader) { // FireFox, Chrome, Opera 확인.
var reader = new FileReader();
reader.onloadend = (function(aImg) {
return function(e) {
aImg.src = e.target.result;
};
})(img);
reader.readAsDataURL(file);
} else { // safari is not supported FileReader
//alert('not supported FileReader');
if (!document.getElementById("sfr_preview_error_"
+ View_area)) {
var info = document.createElement("p");
info.id = "sfr_preview_error_" + View_area;
info.innerHTML = "not supported FileReader";
preview.insertBefore(info, null);
}
}
}
}
}
|
cs |
각 함수 기능에 대해 설명드리겠습니다.
1) 썸네일 보여주기 기능
먼저 썸네일은 previewImage라는 함수를 만들어 파일 업로드한 input Element와 썸네일 이미지를 보여 줄 뷰 영역을 매개변수로 받아옵니다.
이후 유저가 올린 파일들을 받아와 이미지형식이 맞는지 검사하고 맞다면 그대로 썸네일을 세팅해서 뷰 영역의 chlid로 붙여줍니다.
저는 크롬에 맞춰서만 제작했는데 타 브라우저 코드는 참고로만 봐주시면 감사드리겠습니다.
2) 썸네일 클릭으로 사진 삭제하는 기능
먼저 앞의 previewImage함수에서 이미지들 onclick을 만들어
fileRemove함수를 콜백 함수로 등록해줍니다.
이 때 매개변수로 사진의 index를 넘겨주는데 그냥 i를 넘겨주면 우리가 원하는 index값이 넘어가는 것이 아닌 files.length+1로 고정이 되어버립니다.
(for문에서 다 돌고 값이 바뀌어버리기 때문에)
때문에 const 변수를 따로 선언하여 index를 넘겨주도록 만들었습니다.
3. 서버로 데이터 전송할 ajax 코드 제작
전송 로직은 이전 포스팅에서도 다루었던 ajax코드와 동일합니다.
(저는 Spring Security를 쓰고 있어서 token header 를 추가해 주었습니다)
주의하실 점은 processData와 contentType은 둘 다 false로 하셔야 됩니다!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
//form데이터 전송
function dataSubmit() {
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
var data=new FormData($("#storeAddForm")[0]);
$.ajax({
beforeSend: function(xhr){
xhr.setRequestHeader(header,token);
},
url: "url",
data: data,
processData:false,
contentType:false,
enctype:'multipart/form-data',
type:"POST",
}).done(function (fragment) {
$("#resultDiv").replaceWith(fragment);
});
}
|
cs |
4. Controller 및 서버 로직 제작
이제 Spring에서 보낸 정보들을 받아서 처리할 로직을 제작합니다.
아래는 전체 코드입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
@RequestMapping(value = "/url",method = RequestMethod.POST)
public String getData(Model model,MultipartHttpServletRequest req){
//get image file.
List<MultipartFile> multipartFileList = new ArrayList<>();
try{
MultiValueMap<String, MultipartFile> files = req.getMultiFileMap();
for (Map.Entry<String, List<MultipartFile>> entry : files.entrySet()) {
List<MultipartFile> fileList = entry.getValue();
for (MultipartFile file : fileList) {
if (file.isEmpty()) continue;
multipartFileList.add(file);
}
}
if(multipartFileList.size()>0) {
for(MultipartFile file: multipartFileList) {
file.transferTo(new File("파일 옮길 폴더경로 적기" + File.separator + file.getOriginalFilename()));
}
}
}catch (Exception e){
e.printStackTrace();
logger.info(" has no multipartFile!");
}
model.addAttribute("log","사진 "+multipartFileList.size()+"장 전송완료!");
return "html템플릿 주소 :: #resultDiv";
}
|
cs |
저는 사진들을 MultipartHttpServletRequest로 받아서 사진들을 뽑아낸 뒤,
해당 사진들을 서버 컴퓨터 내 다른 폴더로 옮기는 방식으로 제작하였습니다.
그리고 마지막으로 사진을 몇 장 받았는지를 로그로 남겨서 뷰에 다시 리턴해줍니다.
이렇게한 뒤 서버를 돌려서 사진 업로드기능을 사용해보면...
처음 사진과 같이 썸네일+클릭으로 사진 삭제+전송 기능이 완성됩니다!
이상으로 이미지 업로드 포스팅을 마치겠습니다
혹시 잘못된 점이나 잘 이해가 가지 않는 부분이 있으시다면 댓글로 알려주세요ㅎㅎ
감사합니다!
'Backend > Spring' 카테고리의 다른 글
ipTIME의 DDNS를 이용하여 운영 서버 구축하기(포트포워딩) (1) | 2020.10.30 |
---|---|
[Spring]빈 스코프(Bean Scope)의 종류 및 개념 (0) | 2020.10.27 |
[Spring]스프링 빈(Bean)을 초기화 및 종료하는 3가지 방법(Bean 생명주기 콜백) (0) | 2020.10.27 |
[Spring] 스프링의 빈(Bean) 생명주기 콜백의 개념 및 예제 (0) | 2020.10.24 |
[Spring]게시글 사진 업로드기능 구현(MultipartHttpServletRequest) (0) | 2020.07.16 |