이번에는 게시글 작성 시에 첨부파일을 등록하도록 하는 코드를 구현해볼 것이다.
구현하는데 사용한 방식은 다음과 같다.
2. 각 파일들에 대한 랜덤한 UUID를 부여
3. 부여한 파일명을 로컬 드라이브에 저장
4. 랜덤파일명 및 기존파일명을 테이블에 별도로 저장
5. 이후 게시글 상세보기를 할 경우 해당 게시글 번호에 맞는 파일명들을 DB에서 불러와서 호출
<div class="form-group">
<label for="files">첨부파일</label>
<input type="file" id="files" name="files" multiple="multiple">
</div>
type을 file로 설정하면 첨부파일을 추가할 수 있도록 하고, multiple 속성을 부여하면 여러 개의 파일을 첨부할 수 있게 된다.
이를 전송할 때 주의해야할 점은 다음과 같다.
<form enctype="multipart/form-data">
2. javascript에서 첨부파일이 있는 경우와 없는 경우를 나누어야 했음. 왜냐하면, 하나의 코드로 합쳐두면 Controller 상에서 multifile이 null인 상태가 되므로 에러가 발생하기 때문이었다.
합쳐져 있는 경우 다음과 같은 에러가 발생했다.
[nio-8080-exec-5] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved
[org.springframework.web.multipart.support.MissingServletRequestPartException:
Required part 'uploadFiles' is not present.]
이러한 이슈들을 해결한 후의 자바스크립트는 다음과 같다.
$("#btn-save").click(function() {
var header = $("meta[name='_csrf_header']").attr("content");
var token = $("meta[name='_csrf']").attr("content");
var inputFiles = $("input[name='files']");
var files = inputFiles[0].files;
var attached = new FormData();
for (let i = 0; i < files.length; i++) {
attached.append("uploadFiles", files[i]);
}
var tempData =
{
"title" : $("#title").val(),
"content" : $("#content").val()
};
attached.append("boardDetail", JSON.stringify(tempData));
if (files.length > 0) {
$.ajax({
type : "post",
url : "/board/write",
data : attached,
beforeSend : function(xhr) {xhr.setRequestHeader(header, token);},
processData : false,
contentType : false,
enctype : "multipart/form-data",
success : function() {
console.log("success");
window.location.href = "/board";
}, fail : function() {
console.log("failed");
}
});
} else {
$.ajax({
type : "post",
url : "/board/write2",
data : JSON.stringify(tempData),
beforeSend : function(xhr) {xhr.setRequestHeader(header, token);},
contentType : "application/json; charset=UTF-8",
success : function() {
console.log("success");
window.location.href = "/board";
},
fail : function() {
console.log("failed");
}
});
}
})
@Transactional
@ResponseBody
@PostMapping(value = "/board/write", consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE})
public String boardWrite(
HttpServletRequest hsRequest,
@RequestPart("uploadFiles") MultipartFile[] files,
@RequestParam("boardDetail") String map) throws ParseException {
BoardDTO boardDTO = new BoardDTO();
JSONParser jsonParser = new JSONParser();
JSONObject obj = (JSONObject) jsonParser.parse(map);
boardDTO.setTitle((String) obj.get("title"));
boardDTO.setWriter(SecurityContextHolder.getContext().getAuthentication().getName());
boardDTO.setContent((String) obj.get("content"));
List<AttachedFileDTO> attachedFileDTOList = new ArrayList<>();
for (MultipartFile mf : files) {
UUID uuid = UUID.randomUUID();
String savedFileName = uuid + "_" + mf.getOriginalFilename();
if (mf.getOriginalFilename() != null) {
File saveFile = new File("D:\\testFolder", savedFileName);
try {
mf.transferTo(saveFile);
AttachedFileDTO attachedFileDTO = new AttachedFileDTO();
attachedFileDTO.setStoredFileName(savedFileName);
attachedFileDTO.setOriginalFileName(mf.getOriginalFilename());
attachedFileDTOList.add(attachedFileDTO);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
Long bid = boardService.boardWrite(boardDTO);
if (attachedFileDTOList.size() > 0) {
for (AttachedFileDTO attachedFileDTO : attachedFileDTOList) {
attachedFileDTO.setBid(bid);
fileService.fileSave(attachedFileDTO);
}
}
return "redirect:/board";
}
파일을 포함한 게시글 작성

실제 로컬디스크에 저장된 모습

게시글에 파일이 첨부된 경우 클립표시를 해두도록 구현
@GetMapping("/board")
public String board(Model model) {
List<BoardDTO> boardDTOList = boardService.boardTotal();
List<TempBoardDTO> boardmap = new ArrayList<>();
for (BoardDTO boardDTO : boardDTOList) {
boardmap.add(new TempBoardDTO(boardDTO, fileService.isFileExists(boardDTO.getBoardId())));
}
model.addAttribute("boardList", boardDTOList);
model.addAttribute("boardMap", boardmap);
return "board";
}
TempBoardDTO는 해당 BoardDTO와 첨부파일의 유무를 갖도록 하여 View단에서 참/거짓 유무에 따라 이미지를 출력하도록 구현하기 위해 정의함
{{#isAttached}}
<img id="attached_{{BoardDTO.boardId}}" src="/images/attached_thumbnail.png" style="width:50px; height:50px;">
{{/isAttached}}
위처럼 첨부파일을 포함하여 작성하였다면, 해당 게시글에 대한 상세정보 조회시 모든 첨부파일의 원본명을 출력하도록 구현하고자 함.
각 게시글의 id에 대해 첨부파일 테이블에서 id를 기준으로 검색한 후 원본 파일 명만 리스트로 받아 이를 View 단으로 보내주면 됨
List<AttachedFileDTO> attachedFileDTOList = fileService.findAllFiles(bid);
if (attachedFileDTOList.size() > 0) {
model.addAttribute("fileExists", true);
model.addAttribute("files", attachedFileDTOList);
} else {
model.addAttribute("fileExists", false);
}
<h5>=======첨부파일=======</h5>
{{#fileExists}}
{{#files}}
<p>첨부파일명 : {{originalFileName}}</p>
{{/files}}
{{/fileExists}}
{{^fileExists}}
<p>첨부파일이 존재하지 않습니다.</p>
{{/fileExists}}
첨부파일이 있는 경우 게시글 상세보기

첨부파일이 없는 경우 게시글 상세보기

이처럼 게시글을 작성했다면, 게시글을 삭제하는 경우 파일명에 대한 정보를 DB에서 삭제하는 것 뿐만 아니라 로컬디스크에서도 삭제하게끔 구현할 필요가 있다.
$("#btn-delete").click(function() {
var writer = $("#writer").val();
var nowUser = $("#nowId").val();
var boardId = $("#boardId").val();
if (writer === nowUser) {
if (confirm("삭제하시겠습니까?")) {
$.ajax({
type : "get",
url : "/board/delete?bid=" + boardId,
contentType : "json",
success : function () {
alert("게시글이 삭제되었습니다.");
window.location.href = "/board";
}
});
}
} else {
alert("현재 접속중인 아이디가 작성자와 다릅니다.");
}
})
자바스크립트에서는 동일하지만, Controller 상에서 DB와 로컬디스크 상에서의 파일 삭제가 필요하였다.
@ResponseBody
@GetMapping("/board/delete")
public void boardDetail(@RequestParam(value="bid") Long bid) {
boardService.boardDelete(bid);
commentService.commentDeleteByBid(bid);
List<AttachedFileDTO> attachedFileDTOList = fileService.findAllFiles(bid);
fileService.deleteAllFiles(bid);
for (AttachedFileDTO attachedFileDTO : attachedFileDTOList) {
File file = new File("D:/testfolder/" + attachedFileDTO.getStoredFileName());
if (file.exists()) {
if (file.delete()) {
System.out.println(attachedFileDTO.getOriginalFileName() + " is deleted !!");
} else {
System.out.println(attachedFileDTO.getOriginalFileName() + " is not deleted !!");
}
}
}
}
View 단에서의 삭제 요청

삭제된 후의 DB

삭제된 후의 로컬디스크

추가로 구현할 사항
파일 첨부에 대해서는 게시글 상세보기에서 첨부파일을 변경하거나 첨부파일이 없는 게시글에 새로 첨부파일을 추가하는 방법에 대해서 구현해보고자 할 것이다.