REST API

[ Java / SpringBoot / MVC / RESTful] RESTful한 게시판 만들기8 / 첨부파일 삭제 및 수정 구현하기

낫쏘링 2023. 9. 11. 20:03
728x90
먼저 이미 첨부되어 있는 파일을 글 수정 과정에서 삭제하기 위해서
html과 자바스크립트를 이용해서 화면과 이벤트 핸들러를 구현해야 한다.

[ 현재 상태 ]
- 첨부 파일이 있다면 첨부파일 목록이 나타나게만 해둔 상태
<div class="file_list" th:if="${board.fileList != null and board.fileList.size() > 0}">
	[ 첨부파일 ]
	<p th:each="list : ${board.fileList}" th:text="|${list.originalFileName} (${list.fileSize} kb)|"></p>
</div>

            

[ 게시글 수정에서 필요한 로직 ]
1. 게시글 수정 (이미 게시글 수정 구현 과정에서 구현되어있음)
2. 이전에 첨부되어있던 파일 삭제(할 수도 있고 안 할 수도 있다.)   
     - 첨부파일이 존재한다면 옆에 파일 삭제 버튼도 같이 보여야 한다.
     - 파일 삭제 버튼을 누른다고 해서 바로 삭제되는 것은 아니다.
     - 파일 삭제 버튼을 누르면 파일 삭제 버튼이 삭제 취소 버튼으로 바뀐다.
     - 삭제 취소 버튼을 누르면 다시 파일 삭제 버튼으로 바뀐다.
     - 최종적으로 수정 완료 버튼을 눌렀을 때 파일 삭제 버튼이 눌린 파일만 완전히 삭제된다.
3.새롭게 첨부한 파일 등록(할 수도 있고 안 할 수도 있다.)  (이미 첨부파일 업로드 구현 과정에서 구현되어있음)
[ 1. BoardController 수정 ]
/** 기존 게시글 수정 처리 */
@PutMapping("/board/{boardIdx}")
public String modifyBoard(@PathVariable("boardIdx") int boardIdx, BoardDto board) throws Exception{
    boardService.modifyBoard(board);

    return "redirect:/board/" + boardIdx;
}​

/**
*수정 후 게시글 수정 처리 
* files는 새롭게 첨부할 파일을 등록할 때 필요하다.
* 이미 첨부되었는 파일을 삭제할 때 필요한 데이터는 이미 board에 들어있음 (경로)
*/
@PutMapping("/board/{boardIdx}")
public String modifyBoard(@PathVariable("boardIdx") int boardIdx, 
                          @ModelAttribute BoardDto board,
                          @RequestParam("files") List<MultipartFile> files) throws Exception{
    boardService.modifyBoard(board, files);

    return "redirect:/board/" + boardIdx;
}
[ 2. BoardService 수정 ]
/** 기존 게시글 수정 */
public void modifyBoard(BoardDto board) throws Exception{
    boardMapper.modifyBoard(board);
}​

/** 수정 후 게시글 수정 */
public void modifyBoard(BoardDto board, List<MultipartFile> files) throws Exception{
    // 게시글 수정
    boardMapper.modifyBoard(board);

    // 새로운 첨부파일 등록
    boardFileService.insertFile(board, files);

    // 기존 첨부파일 삭제
    boardFileService.deleteFile(board);	    
}​


[ 3. BoardFileService 추가 ]

/** 첨부파일 삭제 */
public void deleteFile(BoardDto board)throws Exception{
    List<BoardFileDto> fileList = board.getFileList();

	// fileList가 null이거나 비어있을 경우 종료(이미 첨부되어 있던 파일 삭제를 안할 경우)
    if(fileList == null || fileList.isEmpty()) {
        return;
    }

    // 서버 컴퓨터에서 삭제
    for(BoardFileDto oneFile : fileList) {
        String path = oneFile.getStoredFilePath();
        File file = new File(path);

        if(!file.exists()) {
            log.info("존재하지 않는 파일");
            return;
        }

        if(file.delete()) {
            log.info("삭제 성공",oneFile.getOriginalFileName());
        }else {
            log.info("삭제 실패",oneFile.getOriginalFileName());
        }

    }

    // 첨부파일 번호 리스트로 넣어주기
    List<Integer> idxs =new ArrayList<Integer>();
    for(BoardFileDto oneFile : fileList) {	
        idxs.add(oneFile.getIdx());
    }
    // 데이터베이스에서 파일 정보 상태 삭제로 바꾸기
    boardMapper.deleteFile(idxs);
}​
[ 4. 쿼리 추가 ]
<update id="deleteFile" parameterType ="int">
    /* 파일 삭제 상태로 변경 */
    <![CDATA[
        UPDATE
            t_file
        SET
            deleted_yn = 'Y',
            updator_id = 'admin',
            updated_datetime = Now()
        WHERE
            idx IN (
    ]]>
        <foreach collection="list" item="item" separator=",">
            #{item}
        </foreach>
        )
</update>​
[ 5. html 수정 ]
<form id="frm" method="post" th:action="'/board/'+ ${boardIdx}" enctype="multipart/form-data">
	
    .. 테이블(formdata) ..
    
    // 메서드 put으로 설정
    <input type="hidden" id="method" name="_method"  value="put"/>
    // 파일삭제 버튼은 동적으로 생성되기 때문에 첨부파일 없을 경우 존재하지 않기 때문에
    // 따로 div를 만들어서 이벤트 핸들러를 등록해줄 예정이다.
    <div class="eventDiv">
        <div class="file_list" th:if="${board.fileList != null  and board.fileList.size() > 0}">
            [ 첨부파일 ]
            <div th:each="list, stat : ${board.fileList}" >
                <span class="file_bg" th:href="@{/board/file(idx=${list.idx}, boardIdx=${list.boardIdx})}" th:text="|${list.originalFileName} (${list.fileSize} kb)|"></span>
                /* 
                * 선택된 파일만 name을 동적으로 추가해서 컨트롤러로 값을 넘겨 줄 예정
                * 컨트롤러에서는 BoardDto에서 board.setFileList(List<BoardFileDto>)를 통해서 매핑할 예정
                * 즉, fileList의 name은 List 형태여야한다.
                * 서버 컴퓨터에서 파일을 삭제할 때 필요한 경로와 데이터베이스에서 파일을 삭제할 때 필요한 pk만 받아온다.
                */
                <input type="hidden" class="file_idx"  th:value="${list.idx}">
                <input type="hidden" class="file_path" th:value="${list.storedFilePath}">
                <button type="button" class="deleteFileBtn btn">파일삭제</button>					
            </div>
        </div>	
    </div>
    <input type="file" id="files" name="files" multiple="multiple">
    <input type="hidden" id="boardIdx" name="boardIdx" th:value="${board.boardIdx}">
    <button type="button" id="modifyBtn" class="btn">수정완료</button>
</form>​
[ 6. 자바스크립트 수정 ] 
// 이전에 첨부되어있던 파일 삭제(할 수도 있고 안 할 수도 있다.)   
const eventDiv = document.querySelector('.eventDiv');

eventDiv.addEventListener('click', e => {
    const files = document.querySelector('#files');
    const filesInfo = files.files;
    console.log(filesInfo);
    if(e.target.classList.contains('deleteFileBtn')){
        const fileDiv = e.target.closest('div');
        const fileSpan = fileDiv.querySelector('span');				
        fileSpan.className = 'file_delete_bg';

        e.target.classList.toggle('deleteFileBtn');
        e.target.classList.toggle('cancleFileBtn');
        e.target.textContent = '삭제취소';

    }else if(e.target.classList.contains('cancleFileBtn')){		
        const fileDiv = e.target.closest('div');
        const fileSpan = fileDiv.querySelector('span');				
        fileSpan.className = 'file_bg';

        e.target.classList.toggle('deleteFileBtn');
        e.target.classList.toggle('cancleFileBtn');
        e.target.textContent = '파일삭제';
    }
});

 

수정할 때 이미 첨부되어있던 파일 삭제, 파일 새롭게 첨부 구현 끝

게시글 수정 화면
이미 첨부되어있던 파일 삭제 선택

 

파일 새롭게 첨부
수정 완료!

 

[ 게시글 삭제할 때 파일도 같이 삭제되게 하는 경우...]
- 굳이 삭제할 파일을 선택하지 않고 전부 삭제하면 되기 때문에
  boardDtail.html (게시글 상세 보기 화면에서) 을 아래와 같이 추가한다.
<div class="file_list" th:if="${board.fileList != null  and board.fileList.size() > 0}">
    [ 첨부파일 ]
    // 게시글 수정에서는 input 태그의 name을 자바스크립트를 통해 동적으로 추가해줬다.
    // 게시글 삭제에서는 모든 파일을 삭제할 것이기 때문에 굳이 선택해서 추가할 필요가 없다.
    // th:block을 추가해서 a태그와 input태그를 반복하게 한다.
    <th:block th:each="list, stat : ${board.fileList}">
        <a th:href="@{/board/file(idx=${list.idx}, boardIdx=${list.boardIdx})}" th:text="|${list.originalFileName} (${list.fileSize} kb)|"></a>
        <input type="hidden" class="file_idx"  th:name="'fileList[' + ${stat.index} + '].idx'"  th:value="${list.idx}">
        <input type="hidden" class="file_path" th:name="'fileList[' + ${stat.index} + '].storedFilePath'" th:value="${list.storedFilePath}">
    </th:block>
</div>


- BoardController

/** 기존 게시글 삭제 처리 */
@DeleteMapping("/board/{boardIdx}")
public String deleteBoard(@PathVariable("boardIdx") int boardIdx) throws Exception{
    boardService.deleteBoard(boardIdx);

    return "redirect:/board";
}
// 수정 후 게시글 삭제 처리
@DeleteMapping("/board/{boardIdx}")
public String deleteBoard(@ModelAttribute BoardDto board) throws Exception{
    boardService.deleteBoard(board);

    return "redirect:/board";
}


- BoardService

/** 기존 게시글 삭제 */
public void deleteBoard(int boardIdx) throws Exception{
    boardMapper.deleteBoard(boardIdx);  
}
/** 수정 후 게시글 삭제 */
public void deleteBoard(BoardDto board) throws Exception{
    // 게시글 삭제
    int boardIdx = board.getBoardIdx();
    boardMapper.deleteBoard(boardIdx);  

    // 첨부파일 삭제
    boardFileService.deleteFile(board);	    
}

 

728x90