Part 28. 게시물의 수정과 첨부파일
- 게시물을 수정할 때 첨부파일과 관련된 작업은 사실상 게시물 등록 작업과 상당히 유사하다.
- 첨부파일이라는 개념 자체가 수정이 아닌 기존 파일을 삭제하고, 새로운 파일을 추가하기 때문이다.
- 게시물 수정에서 첨부파일은 수정이라는 개념보다는 삭제 후 다시 추가한다는 개념으로 접근해야 한다.
- 게시물의 수정에는 기존의 게시물 테이블을 수정하는 작업과 변경(새롭게 추가된)된 첨부파일을 등록하는 작업으로 이루어진다.
28.1 화면에서 첨부파일 수정
- 게시물의 수정은 views 폴더 내에 /board/modify.jsp에서 이루어진다.
- 게시물의 수정은 게시물의 조회화면과 유사하지만 1) 원본 이미지 확대나 다운로드 기능이 필요하지 않다는 점, 2) 게시물 조회와 달리 삭제 버튼이 있어야 하는 점이 다르다.
28.1.1 첨부파일 데이터 보여주기
- modify.jsp 파일에서 페이지가 로딩되면 첨부파일을 가져오는 작업을 먼저 처리한다.
- 첨부파일을 보여주는 부분의 < div > 를 추가하고, get.jsp에서 사용한 < style > 태그의 내용을 그대로 사용한다.
< modify.jsp >
<div class='bigPictureWrapper'>
<div class='bigPicture'>
</div>
</div>
<style>
.uploadResult {
width:100%;
background-color: gray;
}
.uploadResult ul{
display:flex;
flex-flow: row;
justify-content: center;
align-items: center;
}
.uploadResult ul li {
list-style: none;
padding: 10px;
align-content: center;
text-align: center;
}
.uploadResult ul li img{
width: 100px;
}
.uploadResult ul li span {
color:white;
}
.bigPictureWrapper {
position: absolute;
display: none;
justify-content: center;
align-items: center;
top:0%;
width:100%;
height:100%;
background-color: gray;
z-index: 100;
background:rgba(255,255,255,0.5);
}
.bigPicture {
position: relative;
display:flex;
justify-content: center;
align-items: center;
}
.bigPicture img {
width:600px;
}
</style>
<div class="row">
<div class="col-lg-12">
<div class="panel panel-default">
<div class="panel-heading">Files</div>
<div class="panel-body">
<div class='uploadResult'>
<ul>
</ul>
</div>
</div>
</div>
</div>
</div>
- jQuery의 $(document).ready()를 이용해 첨부파일을 보여주는 작업을 처리한다.
< modify.jsp >
<script>
$(document).ready(function() {
(function(){
var bno = '<c:out value="${board.bno}"/>';
$.getJSON("/board/getAttachList", {bno: bno}, function(arr){
console.log(arr);
var str = "";
$(arr).each(function(i, attach){
if(attach.fileType){
var fileCallPath = encodeURIComponent( attach.uploadPath+ "/s_"+attach.uuid +"_"+attach.fileName);
str += "<li data-path='"+attach.uploadPath+"' data-uuid='"+attach.uuid+"' "
str +=" data-filename='"+attach.fileName+"' data-type='"+attach.fileType+"' ><div>";
str += "<img src='/display?fileName="+fileCallPath+"'>";
str += "</div>";
str +"</li>";
}else{
str += "<li data-path='"+attach.uploadPath+"' data-uuid='"+attach.uuid+"' "
str += "data-filename='"+attach.fileName+"' data-type='"+attach.fileType+"' ><div>";
str += "<span> "+ attach.fileName+"</span><br/>";
str += "<img src='/resources/img/attach.png'></a>";
str += "</div>";
str +"</li>";
}
});
$(".uploadResult ul").html(str);
});
})();
});
</script>
- 게시물의 조회화면에서 수정/삭제 화면으로 이동하면 첨부된 파일들을 볼 수 있다.
- 첨부파일을 수정하기 위해서는 게시물을 등록할 때 사용했던 버튼과 파일을 교체하기 위한 < input type='file' > 이 필요하다.
<div class="panel-body">
<div class="form-group uploadDiv">
<input type="file" name='uploadFile' multiple="multiple">
</div>
<div class='uploadResult'>
<ul>
</ul>
</div>
</div>
- 화면에서는 첨부파일이 보여지는 영역에 파일을 추가할 수 있는 < input > 태그가 추가된다.
- 이미 등록되어 있는 첨부파일을 수정하려면 우선 기존의 특정한 파일을 삭제할 수 있도록 화면을 변경한다.
- Ajax로 첨부파일의 데이터를 가져온 부분을 아래와 같이 수정한다.
< modify.jsp >
$(arr).each(function(i, attach){
//image type
if(attach.fileType){
var fileCallPath = encodeURIComponent( attach.uploadPath+ "/s_"+attach.uuid +"_"+attach.fileName);
str += "<li data-path='"+attach.uploadPath+"' data-uuid='"+attach.uuid+"' "
str +=" data-filename='"+attach.fileName+"' data-type='"+attach.fileType+"' ><div>";
str += "<span> "+ attach.fileName+"</span>";
str += "<button type='button' data-file=\'"+fileCallPath+"\' data-type='image' "
str += "class='btn btn-warning btn-circle'><i class='fa fa-times'></i></button><br>";
str += "<img src='/display?fileName="+fileCallPath+"'>";
str += "</div>";
str +"</li>";
}else{
str += "<li data-path='"+attach.uploadPath+"' data-uuid='"+attach.uuid+"' "
str += "data-filename='"+attach.fileName+"' data-type='"+attach.fileType+"' ><div>";
str += "<span> "+ attach.fileName+"</span><br/>";
str += "<button type='button' data-file=\'"+fileCallPath+"\' data-type='file' "
str += " class='btn btn-warning btn-circle'><i class='fa fa-times'></i></button><br>";
str += "<img src='/resources/img/attach.png'></a>";
str += "</div>";
str +"</li>";
}
});
- 화면에서는 교체하는 파일을 첨부하는 < input > 태그와 첨부파일의 이름과 삭제가 가능한 버튼이 보이게 된다.
28.1.2 첨부파일의 삭제 이벤트
- 첨부파일 처리에서 가장 신경 쓰이는 부분은 사용자가 이미 있던 첨부파일 중에 일부를 삭제한 상태에서 게시물을 수정하지 않고 빠져나가는 상황이다.
- 만일 사용자가 특정 첨부파일을 삭제했을 때 Ajax를 통해 업로드된 파일을 삭제하게 되면 나중에 게시물을 수정하지 않고 빠져나갔을 때 파일은 삭제된 상태가 되는 문제가 생긴다.
- 이를 방지하려면 사용자가 특정 첨부파일을 삭제했을 때 화면에서만 삭제하고, 최종적으로 게시물을 수정했을 때 이를 반영하는 방식을 이용해야 한다.
- 우선은 간단히 'x' 버튼을 클릭하면 사용자의 확인을 거쳐 화면상에 사라지도록 한다.
< modify.jsp >
$(".uploadResult").on("click", "button", function(e){
console.log("delete file");
if(confirm("Remove this file? ")){
var targetLi = $(this).closest("li");
targetLi.remove();
}
});
- 실제 파일의 삭제는 게시물의 수정 작업 시 이루어져야 하기 때문에 만일 사용자가 특정 첨부파일을 삭제했다면 삭제하는 파일에 대한 정보를 보관할 필요가 있다.
- 다행히도 < li > 태그 내에 필요한 모든 정보가 들어 있으므로, 이를 이용해 < input type='hidden' > 태그를 생성해 둔다.
- 실제 파일 삭제는 게시물의 수정 버튼을 누르고 처리되는 과정에서 이루어지도록 한다.
- 데이터베이스 정보와 비교해 수정된 게시물에 포함된 항목들 중에 기존에는 존재했으나 수정하면서 빠지는 항목이 있다면 이는 사용자가 해당 파일을 삭제하기 원하는 것이다.
- 만일 사용자가 화면에서 특정 첨부파일을 삭제했더라도 게시물의 수정을 하지 않았다면 화면상에서만 파일이 안 보이는 것뿐이므로 나중에 다시 조회하면 원래의 첨부파일들을 확인할 수 있다.
28.1.3 첨부파일 추가
- 첨부파일 추가는 기존의 게시물 등록 시의 처리와 동일하다.
- 서버에 파일을 업로드하고, 이를 화면에 섬네일이나 파일의 아이콘으로 보이게 처리한다.
< modify.jsp >
var regex = new RegExp("(.*?)\.(exe|sh|zip|alz)$");
var maxSize = 5242880; //5MB
function checkExtension(fileName, fileSize){
if(fileSize >= maxSize){
alert("파일 사이즈 초과");
return false;
}
if(regex.test(fileName)){
alert("해당 종류의 파일은 업로드할 수 없습니다.");
return false;
}
return true;
}
$("input[type='file']").change(function(e){
var formData = new FormData();
var inputFile = $("input[name='uploadFile']");
var files = inputFile[0].files;
for(var i = 0; i < files.length; i++){
if(!checkExtension(files[i].name, files[i].size) ){
return false;
}
formData.append("uploadFile", files[i]);
}
$.ajax({
url: '/uploadAjaxAction',
processData: false,
contentType: false,data:
formData,type: 'POST',
dataType:'json',
success: function(result){
console.log(result);
showUploadResult(result); //업로드 결과 처리 함수
}
}); //$.ajax
});
function showUploadResult(uploadResultArr){
if(!uploadResultArr || uploadResultArr.length == 0){ return; }
var uploadUL = $(".uploadResult ul");
var str ="";
$(uploadResultArr).each(function(i, obj){
if(obj.image){
var fileCallPath = encodeURIComponent( obj.uploadPath+ "/s_"+obj.uuid +"_"+obj.fileName);
str += "<li data-path='"+obj.uploadPath+"'";
str +=" data-uuid='"+obj.uuid+"' data-filename='"+obj.fileName+"' data-type='"+obj.image+"'"
str +" ><div>";
str += "<span> "+ obj.fileName+"</span>";
str += "<button type='button' data-file=\'"+fileCallPath+"\' "
str += "data-type='image' class='btn btn-warning btn-circle'><i class='fa fa-times'></i></button><br>";
str += "<img src='/display?fileName="+fileCallPath+"'>";
str += "</div>";
str +"</li>";
}else{
var fileCallPath = encodeURIComponent( obj.uploadPath+"/"+ obj.uuid +"_"+obj.fileName);
var fileLink = fileCallPath.replace(new RegExp(/\\/g),"/");
str += "<li "
str += "data-path='"+obj.uploadPath+"' data-uuid='"+obj.uuid+"' data-filename='"+obj.fileName+"' data-type='"+obj.image+"' ><div>";
str += "<span> "+ obj.fileName+"</span>";
str += "<button type='button' data-file=\'"+fileCallPath+"\' data-type='file' "
str += "class='btn btn-warning btn-circle'><i class='fa fa-times'></i></button><br>";
str += "<img src='/resources/img/attach.png'></a>";
str += "</div>";
str +"</li>";
}
});
uploadUL.append(str);
}
28.1.4 게시물 수정 이벤트 처리
- 실제 기시물의 첨부파일을 수정하는 모든 작업은 서버에서 처리되도록 하므로, 게시물을 수정할 때는 게시물 등록 작업과 같이 모든 첨부파일 정보를 같이 전송해야 한다.
- 기존의 소스 코드에서 수정 버튼을 클릭했을 때 아래와 같은 내용의 수정이 필요하다.
< modify.jsp >
var formObj = $("form");
$('button').on("click", function(e){
e.preventDefault();
var operation = $(this).data("oper");
console.log(operation);
if(operation === 'remove'){
formObj.attr("action", "/board/remove");
}else if(operation === 'list'){
// move to list
formObj.attr("action", "/board/list").attr("method", "get");
var pageNumTag = $("input[name='pageNum']").clone();
var amountTag = $("input[name='amount']").clone();
var keywordTag = $("input[name='keyword']").clone();
var typeTag = $("input[name='type']").clone();
formObj.empty();
formObj.append(pageNumTag);
formObj.append(amountTag);
formObj.append(keywordTag);
formObj.append(typeTag);
}else if(operation === 'modify'){
console.log("submit clicked");
var str = "";
$(".uploadResult ul li").each(function(i, obj){
var jobj = $(obj);
console.dir(jobj);
str += "<input type='hidden' name='attachList["+i+"].fileName' value='"+jobj.data("filename")+"'>";
str += "<input type='hidden' name='attachList["+i+"].uuid' value='"+jobj.data("uuid")+"'>";
str += "<input type='hidden' name='attachList["+i+"].uploadPath' value='"+jobj.data("path")+"'>";
str += "<input type='hidden' name='attachList["+i+"].fileType' value='"+ jobj.data("type")+"'>";
});
formObj.append(str).submit();
}
formObj.submit();
});
28.2 서버 측 게시물 수정과 첨부파일
- 게시물을 수정할 때 첨부파일의 처리는 생각보다 복잡하다.
- 가장 큰 이유는 기존의 첨부파일 중에 어떤 파일을 수정했고, 어떤 파일이 삭제되었는지 알아야 하기 때문이다.
- 예제에서 이에 대한 처리는 우선 간단한 방법으로 게시물의 모든 첨부파일 목록을 삭제하고, 다시 첨부파일 목록을 추가하는 형태로 처리를 하는 것이다.
- 이 경우 데이터베이스 상에는 문제가 없는데, 실제로 파일이 업로드된 폴더에는 삭제된 파일이 남아 있는 문제가 생긴다.
- 이에 대한 처리는 주기적으로 파일과 데이터베이스를 비교하는 드으이 방법을 활용해 처리할 수 있다.
28.2.1 BoardService(Impl)의 수정
- BoardService에서 게시물의 수정은 우선 기존의 첨부파일 관련 데이터를 삭제한 후에 다시 첨부파일 데이터를 추가하는 방식으로 동작한다.
< BoardServiceImpl 클래스 >
@Transactional
@Override
public boolean modify(BoardVO board) {
log.info("modify......." + board);
attachMapper.deleteAll(board.getBno());
boolean modifyResult = mapper.update(board) == 1;
if (modifyResult && board.getAttachList() != null && board.getAttachList().size() > 0) {
board.getAttachList().forEach(attach -> {
attach.setBno(board.getBno());
attachMapper.insert(attach);
});
}
return modifyResult;
}
- 첨부파일은 수정이라기 보다는 삭제 후에 다시 추가한다는 개념이므로 게시물의 수정전과 후의 데이터베이스에 정상적으로 변경이 되는지 확인하는 것이 필요하다.