본문 바로가기

Spring Boot 수업 정리

Spring Boot 4일차

이어서 파일 관련 설정 ( 경로 , 저장 등) 을 하는 파일 핸들러부터 구현

 

● FileHandler 생성 후 구현 Spring에서 했던 그대로하면 됨. ( 모르겠으면 Spring쪽 파일부분 전 글 참고 )

@Slf4j
@Component
public class FileHandler {

    private String UP_DIR = "D:\\_myProject\\_java\\_fileUpload\\";


    public List<FileVO> uploadFiles(MultipartFile[] files) {
        List<FileVO> flist = new ArrayList<>();
        LocalDate date = LocalDate.now();
        // 2024-11-15  => 2024\\11\\15
        String today = date.toString().replace("-", File.separator);
        //  D:\_myProject\_java\_fileUpload\2024\11\15
        File folders = new File(UP_DIR, today);

        // mkdir = 1개의 폴더만 // mkdirs = 여러개
        if(!folders.exists()){
            folders.mkdirs();
        }

        // FileVO 생성
        for(MultipartFile file : files){

            FileVO fvo = new FileVO();
            fvo.setSaveDir(today);
            fvo.setFileSize(file.getSize());

            // file.name => 경로를 포함하는 경우도 있음. /test/test.txt
            String originalFileName = file.getOriginalFilename();
            String onlyFileName = originalFileName.substring(
                    originalFileName.lastIndexOf(File.separator)+1);
            fvo.setFileName(onlyFileName);

            UUID uuid = UUID.randomUUID();
            String uuidStr = uuid.toString();
            fvo.setUuid(uuidStr);

            String fileName = uuidStr+"_"+onlyFileName;
            File storeFile = new File(folders,fileName);

            try{
                file.transferTo(storeFile);
                // 파일 타입 : 그림파일만 썸네일 생성
                if(isImageFile(storeFile)){
                    fvo.setFileType(1);
                    File thumbnail = new File(folders, uuidStr+"_th_"+onlyFileName);
                    Thumbnails.of(storeFile).size(100,100).toFile(thumbnail);
                } else {
                    fvo.setFileType(0);
                }

            } catch (Exception e) {
                e.printStackTrace();
            }

            flist.add(fvo);
        }

        return flist;
    }
    private boolean isImageFile(File file) throws IOException {
        String mimeType = new Tika().detect(file);
        return mimeType.startsWith("image");
    }
}

 

- 파일 경로를 설정해주고 날짜별로 잘라서 폴더가 없다면 새로운 날짜 폴더를 생성해서 저장할 수 있도록 함.

 

- for문으로 files안의 요소를 file로 하나씩 가져와서 경로, 파일사이즈, 파일이름, 파일타입, uuid를 설정해서 flist에 add할 수 있도록 해줌.

 

- 파일타입은 image 파일이아니라면 false 맞다면 true를 리턴해서 파일타입을 구분할 수 있는 메서드를 생성해서 파일타입을 설정해줌

 

● 컨트롤러 파일추가해서 수정.

    @PostMapping("/register")
    public String register(BoardVO boardVO,
                           @RequestParam(name = "files", required = false)MultipartFile[] files){
        log.info(">>> boardVO >> {}", boardVO);
        List<FileVO> flist = null;

        if(files[0].getSize() > 0 || files != null){
            flist = fh.uploadFiles(files);
            log.info(">>> flist >> {}", flist);
        }

        int isOk = bsv.register(new BoardDTO(boardVO,flist));

        return "index";
    }

 

- 컨트롤러에서 register에 multiple로 배열타입으로 들어오므로  MultipartFile[] files로 매개변수로 받아주고

 

- files[0] 사이즈가 0 보다크고 null이 아닐 때 파일 핸들러에서 만들어놓은 uploadFiles를 이용해서 files안에 파일을 저장하고 flist안에 넣어줌. 그러기위해서 파일핸들러는 위에 아래 코드로 추가.

 private final FileHandler fh;

 

-  bsv.register안에  boardVO의 객체와 flist를 받아서 BoardDTO 를 생성해 넣어주는걸로 수정

 

● serviceImpl에 register 부분도 수정

    @Transactional
    @Override
    public int register(BoardDTO boardDTO) {

        int isOk = boardMapper.register(boardDTO.getBvo());
        if(isOk > 0 && boardDTO.getFlist().size() > 0 ){
            // 파일저장
            // board의 bno를 가져오기 => 가장 큰 bno
            long bno = boardMapper.getMaxBno();
            for(FileVO fvo : boardDTO.getFlist()){
                fvo.setBno(bno);
                isOk *= fileMapper.insertFile(fvo);
            }
        }

        return isOk;
    }

 

- 파일이 없다면 그냥 boardDTO의 BoardVO 객체를 가져와서 원래대로 register해서 추가 

- 파일이 있어 flist에 사이즈가 0 보다 크다면 bno를 받아오기위해 새로 등록된 글은 가장 큰 bno를 가지게 되므로 

max(bno)로 bno를 가져올 수 있도록하고 파일 객체의 가져온 bno를 set해서 FileMapper를 생성해서 이어주고 insertFile로 파일을 추가해줌

● fileMapper에 insertFile부분.

    <insert id="insertFile">
        insert into file(uuid,save_dir,file_name,file_type,bno,file_size)
        values(#{uuid}, #{saveDir}, #{fileName}, #{fileType}, #{bno}, #{fileSize})
    </insert>

 

화면에 파일을 가져오기위해서 detail 컨트롤러 부분 수정

BoardDTO boardDTO = bsv.getDetail(bno);

log.info(" bdto >> {} ", boardDTO);

m.addAttribute("bdto", boardDTO);

 

BoardVO 부분을 DTO로 전부 수정해준다.

 

  serviceImpl 부분도 DTO를 boardVO 객체와, flist를 가져와서 생성해주는걸로 수정

    @Override
    public BoardDTO getDetail(long bno) {
        // fileList가져와서 DTO 생성
        BoardDTO bdto = new BoardDTO(boardMapper.getDetail(bno),fileMapper.getFileList(bno));
        return bdto;
    }

 

파일매퍼부분.

    <select id="getFileList" resultType="com.ezen.spring.domain.FileVO">
        select * from file where bno = #{bno}
    </select>

 

● detail.html에 올라간 파일이 출력되는 부분을 추가해줌. th , 인라인 표현 필수!!

이미지타입이 0이상인 이미지파일이면 이미지태그로 이미지를 띄워주고

아니라면 이미지타입이 아니므로 누르면 다운로드할 수 있는 클립아이콘을 하나 추가해줌.

<div class="mb-3">
   <ul class="list-group list-group-flush">
          <li th:each="fvo:${bdto.flist}" class="list-group-item">
            <div th:if="${fvo.fileType} > 0" class="ms-2 me-auto">
              <img th:src="@{|/upload/${fvo.saveDir}/${fvo.uuid}_${fvo.fileName}|}" alt="img"/>
            </div>
            <div th:unless="${fvo.fileType} > 0" class="ms-2 me-auto">
            <!--/* icon */-->
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-paperclip" viewBox="0 0 16 16">
                  <path d="M4.5 3a2.5 2.5 0 0 1 5 0v9a1.5 1.5 0 0 1-3 0V5a.5.5 0 0 1 1 0v7a.5.5 0 0 0 1 0V3a1.5 1.5 0 1 0-3 0v9a2.5 2.5 0 0 0 5 0V5a.5.5 0 0 1 1 0v7a3.5 3.5 0 1 1-7 0z"/>
            </svg>
            </div>
            <div class="ms-2 me-auto">
            <div class="fw-bold text-success-emphasis">[[${fvo.fileName}]]</div>
                           [[${fvo.regDate}]]
            </div>
                 <span class="badge text-bg-success rounded-pill">[[${fvo.fileSize}]]Bytes</span>
          </li>
    </ul>
</div>

 

● 파일 삭제기능 추가를 위해 화면에 버튼도 추가해줌

 <button type="button" th:data-uuid="${fvo.uuid}" class="btn btn-outline-danger btn-sm file-x" disabled>x</button>

 

평소에는 disabled 돼있다가 modify를 누르면 false 되게만듬.

 

● 파일 삭제버튼을 누르면 disabled가 false되게 만드려면 파일마다의 X버튼을 for문으로 돌려서 false 해주어야함.

    let fileDelBtn = document.querySelectorAll(".file-x");
    console.log(fileDelBtn);
    for(let delBtn of fileDelBtn){
        delBtn.disabled = false;
    };

 

● 화면에서 file-x 클래스를 가지고있는 타겟을 가져와서 uuid를 가져올 수 있도록하고, closest로 가장 가까운 li를 찾을 수 있도록함. 파일 삭제 메서드를 만들어주고 isOk를 텍스트로 return하는 result가 "1"이면 성공 "0"이면 실패로 설정. 

document.addEventListener('click', (e) => {
    if(e.target.classList.contains('file-x')){
        let uuid = e.target.dataset.uuid;
        console.log(uuid);
        let li = e.target.closest('li');

        removeFileToServer(uuid).then(result => {
            if(result == "1"){
                li.remove();
                alert("파일삭제 성공!!");
            } else{
                alert("파일삭제 실패!!");
            }
        })
    }
})


● 비동기로 파일지우는 메서드 생성

async function removeFileToServer(uuid) {
    try {
        const url = ("/board/file/"+ uuid)
        const config = {
            method : 'delete'
        }
        const resp = await fetch(url, config); 
        const result = await resp.text();
        return result;

    } catch (error) {
        console.log(error);
    }
}

 

  컨트롤러에 DeleteMapping으로 uuid를 받아서 fileMapper까지 연결해서 파일을 지워주고 isOk 값을 리턴할 수 있도록함. 

    @ResponseBody
    @DeleteMapping(value = "/file/{uuid}")
    public String fileDelete(@PathVariable("uuid") String uuid){

        int isOk = bsv.fileDelete(uuid);

        return isOk > 0 ? "1" : "0";
    }

 

●  파일 수정 기능 구현을 위해 regsiter에서 파일 추가, 출력라인을 가지고온다음 modify 버튼을 눌렸을때만 파일업로드가 가능하도록 업로드버튼에 disabled를 넣어줌.

           <!--/* file 추가 라인 */-->
           <div class="mb-3">
               <label for="file" class="form-label">File</label>
               <input type="file" class="form-control" id="file" name="files" multiple style="display:none">
           </div>
           <button type="button" id="trigger" class="btn btn-primary" disabled>File Upload</button>

           <!--/* file 출력 라인 */-->
           <div class="mb-3" id="fileZone"></div>

 

js에 modify버튼을 누르면 trigger가 달려있는 버튼의 disabled가 false가 되도록해서 풀어주도록함.

document.getElementById('trigger').disabled = false;

 

●  컨트롤러 수정부분에 register처럼 멀티파트 파일의 배열을 files 받아와주고 BoardDTO에 BoardVO 객체와 flist를 넣고 

생성해서 modfiy 에 넣어서 파일매퍼까지 이어줌.

   @PostMapping("/modify")
    public String modify(BoardVO boardVO, RedirectAttributes redirectAttributes, @RequestParam(value = "files", required = false) MultipartFile[] files){
       List<FileVO> flist = null;

       if( files != null && files[0].getSize() > 0 ){
           flist = fh.uploadFiles(files);
       }
       int isOk = bsv.modify(new BoardDTO(boardVO,flist));

       redirectAttributes.addAttribute("bno",boardVO.getBno());

        return "redirect:/board/detail";
    }

 

● 파일리스트가 비지않고 존재한다면 insertFile에 파일객체 fvo를 넣어서 insert해주면됨.

    @Override
    public int modify(BoardDTO boardDTO) {

        int isOk = boardMapper.modify(boardDTO.getBvo());

        if(boardDTO.getFlist() == null){
            return isOk;
        }
        if(isOk > 0 && !boardDTO.getFlist().isEmpty()){
            for(FileVO fvo : boardDTO.getFlist()){
                fvo.setBno((boardDTO.getBvo().getBno()));
                isOk *= fileMapper.insertFile(fvo);
            }
        }
        return isOk;
    }

 

  파일폴더안에 파일이 삭제될 때 지워지도록 구현

- 파일을 지우기위한 핸들러를 생성하고 경로와 파일이름을 받아서 존재하면 파일을 delete()하는  메서드를 구현.

@Slf4j
public class FileDeleteHandler {

    private final String BASE_PATH = "D:\\_myProject\\_java\\_fileUpload\\";

    public int deleteFile(String saveDir, String uuid, String imageFileName){
        boolean isDel = false;
        File fileUuid = new File(uuid);
        File fileSaveDir = new File(saveDir);
        File removeFile = new File(BASE_PATH+fileSaveDir+File.separator+fileUuid+"_"+imageFileName);
        File removeThFile = new File(BASE_PATH+fileSaveDir+File.separator+fileUuid+"_th_"+imageFileName);

        if(removeFile.exists() || removeThFile.exists()) {
            isDel = removeFile.delete(); // 원래파일 삭제
            log.info(">>> removeFile !! > {} ", isDel);
            if(isDel) {
                isDel = removeThFile.delete();
                log.info(">>> removeThFile !! > {} ", isDel);
            }
        }
        return isDel ? 1 : 0 ;
    }
}

 

● 삭제 버튼을 눌렀을 때 uuid로 내가 삭제하는 파일의 정보를 가져와서 만든 핸들러의 deleteFile 메서드로

안에 필요한정보를 넣어 해당하는 파일이 폴더에서 파일이 지워질 수 있도록 구현

    @ResponseBody
    @DeleteMapping(value = "/file/{uuid}")
    public String fileDelete(@PathVariable("uuid") String uuid){

        FileVO fvo = bsv.getFile(uuid);

        log.info(" fvo >> {} >>  " ,fvo);

        // 파일 삭제
        FileDeleteHandler fileDeleteHandler = new FileDeleteHandler();

        int delOk = fileDeleteHandler.deleteFile(fvo.getSaveDir(),fvo.getUuid(),fvo.getFileName());

        log.info(" delOk >> {} ", delOk);

        int isOk = bsv.fileDelete(uuid);

        return isOk > 0 ? "1" : "0";
    }

'Spring Boot 수업 정리' 카테고리의 다른 글

Spring boot 6일차  (1) 2024.11.19
Spring Boot 5일차  (1) 2024.11.18
Spring boot 3일차  (3) 2024.11.14
Spring Boot 2일차  (1) 2024.11.13
Spring Boot 1일차  (0) 2024.11.13