본문 바로가기
개발입문/SPRING 게시판 만들기

[SPRING] 첨부파일 다운로드 및 수정/삭제

by 양히◡̈ 2022. 10. 14.

글 읽기 시 첨부파일 확인하기

이제 게시글을 작성할 때 첨부파일을 DB에 저장하는 것 까지 구현했으니,

게시글을 읽었을 때도 첨부파일을 확인할 수 있도록 구현해볼 것이다.

첨부파일의 목록을 읽기 위해서는 목록을 읽는 메소드가 먼저 만들어져야 한다.

BoardService.java 를 열고 메소드 원형을 만든다.

public List<BoardAttachVO> getAttachList(Long bno);

BoardServiceImp.java 에서 방금 만든 메소드를 오버라이드한다.

@Override
public List<BoardAttachVO> getAttachList(Long bno) {
    log.info("get Attach list by bno: " + bno);
    return attachMapper.findByBno(bno);
    //게시물 번호를 전달하고, 게시물 번호와 일치하는 첨부파일을 모두 리턴함
}

findByBno() 는 BoardAttachMapper.xml 에서 구현해 놓았던 부분이고, bno의 맞는 값을 찾을 수 있게 해준다.

 

 

다음으로 첨부파일 정보를 return할 메소드를 추가할 것이다.

BoardController.java 를 열고 새로운 메소드를 추가한다.

@GetMapping(value = "/getAttachList", produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public ResponseEntity<List<BoardAttachVO>> getAttachList(Long bno) {
    log.info("getAttachList: " + bno);
    return new ResponseEntity<>(service.getAttachList(bno), HttpStatus.OK);
}

 

 

첨부파일의 목록을 글읽기 페이지에서도 화면에 표시되도록 해볼 것이다.

get.jsp 를 열고, 댓글목록 div 부분 위에 추가 작성한다.

<!-- 첨부파일 시작 -->
<br />
<div class="row">
	<div class="col-lg-12">
		<div class="panel panel-default">
			<div class="panel-heading">첨부파일</div>
			<div class="panel-body">
				<div class="uploadResult">
					<ul></ul>
				</div>
			</div>
		</div>
	</div>
</div>
<!-- 첨부파일 끝 -->

스크립트도 추가한다.

//첨부파일 목록 표시
(function() {
    var bno='<c:out value="${board.bno}"/>';
    //화면 상에 공유된 bno값을 가져와 사용 준비
    $.getJSON("/board/getAttachList",{bno:bno}, function(arr){
        console.log(arr);

        var str="";
        $(arr).each(function(i,attach){
            str+="<li data-path='";
            str+=attach.uploadPath+"' data-uuid='";
            str+=attach.uuid+"' data-filename='";
            str+=attach.fileName+"' data-type='";
            str+=attach.fileType+"'><div>";
            str+="<img src='/resources/img/attach.png' width='20' height='20'>";
            str+="<span>"+attach.fileName+"</span><br/> ";

            str+="</div></li>";
        });
        $(".uploadResult ul").html(str);
    });
})();

서버 구동 후 살펴보면, 글 읽기 페이지에서도 첨부파일 목록이 잘 표시되고 있다.

 

 

 

첨부파일 다운로드 기능 구현

글 읽기에서 첨부파일 이름을 클릭했을 때 다운로드가 가능하도록 구현할 것이다.

파일 다운로드에 관련된 메소드를 만들기 위해

uploadController.java 를 열고, 메소드를 추가로 입력한다.

//첨부파일 다운로드 메소드
//모든 파일은 내부적으로 bit값을 가짐(문서, 영상, 이미지, 소리). 비트 스트림을 재조합하여 파일로 구성함
@GetMapping(value = "/download", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
@ResponseBody
public ResponseEntity<Resource> downloadFile(@RequestHeader("User-Agent") String userAgent, String fileName) {
    //서버에 접속한 브라우저의 정보는 헤더의 User-Agent를 보면 알 수 있음

    Resource resource = new FileSystemResource("c:\\upload\\" + fileName);
    //import org.springframework.core.io.resource;
    //파일을 리소스(자원: 가공 처리를 위한 중간 단계)로 변경
    log.info("resource: " + resource);

    if (resource.exists() == false) {
        return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }

    String resourceName = resource.getFilename(); //리소스에서 파일명을 찾아서 할당
    String resourceOriginalName = resourceName.substring(resourceName.indexOf("_") + 1); //uuid를 제외한 파일명 만들기

    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, "UTF-8");
        } 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=" + downloadName); //헤더에 파일 다운로드 정보 추가
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }

    return new ResponseEntity<Resource>(resource, headers, HttpStatus.OK);
}

 

 

get.jsp 에 스크립트를 추가한다.

//첨부파일 클릭시 다운로드 처리
$(".uploadResult").on("click","li",function(e) {
    console.log("download file");
    var liObj = $(this);
    var path = encodeURIComponent(liObj.data("path")+"/"+liObj.data("uuid")+"_"+liObj.data("filename"));
    self.location = "/download?fileName="+path;
});

 

 

 

서버 구동 후 글 읽기에서 첨부파일 제목을 클릭하면 위와 같이 콘솔에 "download file"이 출력되고, 파일명을 우리가 보기 쉬운 파일명으로 변환한 다음 다운로드가 정상적으로 처리된다.

 

 

 

글 수정시 첨부파일 수정

만약 게시글이 수정된다면 첨부파일의 정보도 같이 수정되어야 한다.

예를 들어, 2개의 첨부파일 중에서 1개를 삭제한다면, 1개의 파일에 대한 정보만 등록되어야 한다.

 

먼저, 수정 창에서도 첨부파일 추가 버튼이 표시되도록 해볼 것이다.

modify.jsp 에 div를 추가한다.

<!-- 첨부파일 표시 -->
<br />
<div class="row">
	<div class="col-lg-12">
		<div class="panel panel-default">
			<div class="panel-heading">
				<div class="panel-body">
					<div class="form-group uploadDiv">
						파일첨부: <input type="file" name="uploadFile" multiple>
					</div>
					<div class="uploadResult">
						<ul></ul>
					</div>
				</div>
			</div>
		</div>
	</div>
</div>

 

수정버튼의 클릭이벤트 스크립트에서 operation이 remove거나 list일 때만 구현을 해 놓고 기존에는 수정을 else로 뺐었다.

수정도 else if문으로 처리할 것이다.

$('button').on("click", function(e){
	...
    if (operation === "remove") {
    	...
    } else if (operation === "list") {
    	...
    } else if (operation === 'modify') {
        var str="";
        $(".uploadResult ul li").each(function(i,obj){
            var jobj = $(obj);
            console.dir(jobj);
            console.log("----------------------");
            console.log(jobj.data("filename"));

            str+="<input type='hidden' name='attachList[";
            str+=i+"].fileName' value='"+jobj.data("filename")
            str+="'>";

            str+="<input type='hidden' name='attachList[";
            str+=i+"].uuid' value='"+jobj.data("uuid")
            str+="'>";

            str+="<input type='hidden' name='attachList[";
            str+=i+"].uploadPath' value='"+jobj.data("path")
            str+="'>";

            str+="<input type='hidden' name='attachList[";
            str+=i+"].fileType' value='"+jobj.data("type")
            str+="'>";
        })
        formObj.append(str);
        console.log(str);
    } 
    formObj.submit();
}

 

그리고 첨부파일 관련 함수를 추가한다.

//첨부파일 목록
(function() {
    var bno = '<c:out value="${board.bno}"/>'; //화면상에 공유된 bno값을 가져와 사용 준비
    $.getJSON("/board/getAttachList", {bno:bno}, function(arr){
        console.log(arr);

        var str="";
        $(arr).each(function(i,attach){
            var fileCallPath = encodeURIComponent(attach.uploadPath + "/" + attach.uuid + "_" + attach.fileName);

            str+="<li data-path='";
            str+=attach.uploadPath + "' data-uuid='";
            str+=attach.uuid + "' data-filename='";
            str+=attach.fileName + "' data-type='";
            str+=attach.fileType + "'><div>";
            str+="<img src='/resources/img/attach.png' width='20' height='20'>";
            str+="<span>"+attach.fileName+"</span>&nbsp;&nbsp;";
            str+="<b data-file='"+fileCallPath;
            str+="' data-type='file'>[x]</b>";

            str+="</div></li>";
        });
        $(".uploadResult ul").html(str);
    });
})();//첨부파일 목록표시 스크립트 끝

//첨부파일의 [x]버튼 눌렀을 때 처리
$(".uploadResult").on("click", "b", function(e){
    console.log("delete file");

    var delConfirm = confirm('선택한 파일을 삭제하시겠습니까?\n예를 선택하면 복구 불가합니다.');

    if(delConfirm) {
        var targetFile = $(this).data("file");
        var type = $(this).data("type");
        var targetLi = $(this).closest("li");

        $.ajax({
            url: '/deleteFile',
            data: {fileName: targetFile, type: type},
            dataType: 'text',
            type: 'POST',
            success: function(result){
                alert(result);
                targetLi.remove();
            }
        });
    } else {
        return;
    }
});

//첨부파일 등록과 표시 시작
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); //첨부파일 업로드 결과를 json으로 받으면 그 내용을 화면에 표시함
        }
    });
});

function showUploadResult(uploadResultArr){
    if(!uploadResultArr || uploadResultArr.length == 0){
        //json처리 결과가 없다면 함수 종료
        return;
    }
    var uploadUL = $(".uploadResult ul");
    var str="";

    //each 구문은 전달된 배열의 길이만큼 each 이후의 함수를 반복처리
    $(uploadResultArr).each(function(i,obj){
        var fileCallPath = encodeURIComponent(obj.uploadPath + "/" + obj.uuid + "_" + obj.fileName);
        //encodeURIComponent: uri로 전달되는 특수문자 치환 & ?
        var fileLink = fileCallPath.replace(new RegExp(/\\/g),"/");
        //전달되는 값들 중에서 역슬러시를 찾아 슬러시로 변경

        str+="<li data-path='";
        str+=obj.uploadPath+"' data-uuid='";
        str+=obj.uuid+"' data-filename='";
        str+=obj.fileName+"' data-type='";
        str+=obj.image+"'><div>";
        str+="<img src='/resources/img/attach.png'  width='20' height='20'>";
        str+="<span>"+obj.fileName+"</span> ";
        str+="<b data-file='"+fileCallPath;
        str+="' data-type='file'>[x]</b>";
        str+="</div></li>";
    });
    uploadUL.append(str);
}

 

BoardServiceImp.java 에서 modify 부분을 수정한다.

@Transactional
@Override
public boolean modify(BoardVO board) {
    log.info("modify......" + board);

    boolean modifyResult = false; //게시물 수정 성공여부
    modifyResult = mapper.update(board) == 1;

    int attachList = 0; //첨부파일 개수
    if (board.getAttachList() != null ) {
        attachList = board.getAttachList().size();
    }

    if (modifyResult && attachList > 0) {
        List<BoardAttachVO> inputList = board.getAttachList(); 
        //등록하려는 첨부파일 목록 (11,22)
        List<BoardAttachVO> dbList = attachMapper.findByBno(board.getBno()); 
        //DB에 등록되어있는 첨부파일 목록 (22,33)

        for (BoardAttachVO bav : inputList) {
            boolean flag = false;

            for (BoardAttachVO bav2 : dbList) {
                if(bav.getFileName().equals(bav2.getFileName())) {
                    flag = true; //중복이름 확인
                }
            }

            //중복되지 않았다면 DB에 등록
            if (!flag) {
                bav.setBno(board.getBno());
                attachMapper.insert(bav);
            }
        }
    }

    return modifyResult;
}

 

 

다음은 UploadController.java 에서 첨부파일 관련 메소드인 BoardAttachMapper를 명시해준다.

@Setter(onMethod_ = @Autowired)
private BoardAttachMapper attachMapper;

 

기존 deleteFile 메소드에서 try문 내부 코드를 수정한다.

// 첨부파일 업로드 취소하기
@PostMapping("/deleteFile")
@ResponseBody
public ResponseEntity<String> deleteFile(String fileName, String type) {
    log.info("deleteFile: " + fileName);
    File file;

    try {
        fileName = URLDecoder.decode(fileName, "UTF-8");
        file = new File("c:\\upload\\" + fileName); 
        //한글의 경우 페이지 전환시 변경됨. 알맞은 문자 포맷으로 해석해서 읽어들여야 함

        fileName = fileName.substring(fileName.indexOf("/") + 1, fileName.indexOf("_"));
        log.info("uuid: " + fileName); //DB에서 삭제를 선택한 파일 정보 삭제
        attachMapper.delete(fileName);
        file.delete(); //파일 삭제
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
        return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }

    return new ResponseEntity<String>("deleted", HttpStatus.OK); 
    //return null;
}

서버 구동 후 글 수정 페이지에서 일부 첨부파일 삭제시 alert 메세지와 함께 파일이 삭제된다.

 

댓글