본문 바로가기

Spring 수업 정리

Spring 6일

5일에 이미지파일을 datail (상세) 페이지에 뿌린거부터 이어서 

 

● 이미지 파일이 아닌경우 파일 아이콘을 부트스트랩에서 하나 가져와서 눌러서 다운로드 할 수 있도록 설정

download ="" 안에 파일이름을 넣어서 다운로드할때 경로, uuid를 생략하고 파일이름만 나올 수 있도록 설정.

<c:otherwise>
	<!-- 일반파일 : 아이콘 하나 가져와서 다운로드 가능하게 생성 -->
	<a href="/upload/${fvo.saveDir }/${fvo.uuid}_${fvo.fileName}" download="${fvo.fileName}">
		<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-file-arrow-down" viewBox="0 0 16 16">
		<path d="M8 5a.5.5 0 0 1 .5.5v3.793l1.146-1.147a.5.5 0 0 1 .708.708l-2 2a.5.5 0 0 1-.708 0l-2-2a.5.5 0 1 1 .708-.708L7.5 9.293V5.5A.5.5 0 0 1 8 5"/>
		<path d="M4 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zm0 1h8a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1"/>
	</svg>
	</a>
</c:otherwise>

 

● BoardVO로 뿌렸었는데 DTO로 수정해서 뿌리게돼서 datail을 수정했으니 modify도 이어서 DTO로 수정해줘야함.

<c:set value="${bdto.bvo }" var="bvo"></c:set>

 

c:set으로 form태그안에있는 BoardVO객체들을 DTO의 bvo로 읽을 수있도록 해주고

<c:set value="${bdto.flist }" var="flist"></c:set>
	
<div class="mb-3">
	<ul class="list-group list-group-flush">
	<!-- 파일의 개수만큼 li를 반복하여 파일 표시 타입이 1인경우만 그림을 표시 -->
	<c:forEach items="${flist }" var="fvo">
		 <li class="list-group-item">
			<c:choose>
				<c:when test="${fvo.fileType > 0 }">
				  <div>
				  	<img alt="" src="/upload/${fvo.saveDir }/${fvo.uuid}_${fvo.fileName}">
				  </div>
				</c:when>
			<c:otherwise>
			<!-- 일반파일 : 아이콘 하나 가져와서 다운로드 가능하게 생성 -->
				<a href="/upload/${fvo.saveDir }/${fvo.uuid}_${fvo.fileName}" download="${fvo.fileName}">
					<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-file-arrow-down" viewBox="0 0 16 16">
						 <path d="M8 5a.5.5 0 0 1 .5.5v3.793l1.146-1.147a.5.5 0 0 1 .708.708l-2 2a.5.5 0 0 1-.708 0l-2-2a.5.5 0 1 1 .708-.708L7.5 9.293V5.5A.5.5 0 0 1 8 5"/>
						 <path d="M4 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zm0 1h8a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1"/>
					</svg>
				</a>
			</c:otherwise>
			</c:choose>
				<div class="fw-bold">${fvo.fileName }</div>
				 <span class="badge text-bg-primary rounded-pill">${fvo.regDate } / ${fvo.fileSize }Bytes</span>
				
				 </li>	 	
		</c:forEach> 
	</ul>
</div>

 

똑같이 c:set으로  bdto의 파일객체의 리스트인 flist를 flist로 편하게 쓸 수있게 해주고 detail 뿌린거처럼 modify에도 뿌려줌

 

● 수정페이지이므로 이미지를 삭제할 수 있는 버튼을 생성

이미지 파일의 정보를 가져와서 삭제하기위해 data-set으로 파일객체의 기본키인 uuid를 가져올 수 있도록함

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

 

● js를 만들어서 삭제버튼을 눌렀을 때 파일을 삭제하고 파일표시라인을 지우는 js를 구현

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

 

file-x 클래스가 포함돼있는버튼의 uuid와 li를 가져와서 저장

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);
    }
}

 

비동기로 파일 삭제 메서드를 구현하고 

 

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

 

삭제버튼을 누를때 구현한 삭제메서드를 넣어서 isOk를 리턴하는 result 의 값이 1일때 성공 0 일때 실패로 넣어주고,

성공으로 끝나면 표시라인을 remove()로 지울 수 있도록함.

 

● 컨트롤러는 구현한 비동기 메서드의 맞게 DeleteMapping으로 받아와서 DAO까지 연결하고 fileMapper에서 delete를 구현 성공 1 , 실패 0 을 isOk에 리턴.

	@ResponseBody
	@DeleteMapping(value="/file/{uuid}")
	public String fileDelete(@PathVariable("uuid") String uuid) {
		
		log.info(">>> modfiy uuid > {} ", uuid);
		
		int isOk= bsv.fileDelete(uuid);
		
		return isOk > 0 ? "1" : "0";
	}

 

● 삭제버튼을 만들었으니 파일추가버튼도 만들어야하는데 register에서 만들어둔 부분을 가져오면 됨.

<!-- 첨부파일 입력 라인 추가 -->
<div class="mb-3">
	<label for="file" class="form-label"></label> 
	<input type="file" class="form-control" id="file" name="files" multiple="multiple" style="display:none">
	<button type="button" class="btn btn-info" id="trigger">FileUpload</button>
div>
				
<!-- 첨부파일 표시 라인 추가 -->
	<div class="mb-3" id="fileZone">
	</div>

 

modify에 파일을 표시하는 부분위에다가 첨부파일 입력,표시를 그대로 가져와서 넣어주고

<script type="text/javascript" src="/resources/js/boardRegister.js"></script>

 

register에 만들어뒀던 스크립트를 가져와서 추가

<button type="submit" id="regBtn" class="btn btn-success">update</button>

 

update 수정버튼에 레지스트르에 파일첨부 입력버튼의 아이디를 가져와서 regBtn 아이디를 추가해줘서 스크립트가 작동하도록 해줌.

 

● 화면을 수정했으니까 컨트롤러에와서 update 부분에 bvo객체뿐만아니라 MultipartFile로 files를 받아주고

@PostMapping("/update")
	public String update(BoardVO bvo, @RequestParam(name="files", required = false)MultipartFile[] files) {
		
		List<FileVO> flist = null;
		
		if(files[0].getSize() > 0) {
			flist = fh.uploadFiles(files);
			log.info(">>> flist > {}", flist);
		}
		
		int isOk = bsv.update(new BoardDTO(bvo,flist));
			
		return "redirect:/board/detail?bno=" +bvo.getBno();
		
	}

 

FileVO 객체의 리스트인 flist를 null로 일단만들어주고 받아온 files의 크기가 0보다크다면 flist 안에 넣어주도록함.

boardService에 새로운 BoardDTO 객체로 bvo와 flist를 넣어서 받아 연결해주고 서비스,다오 구현

 

	@Override
	public int update(BoardDTO boardDTO) {
		// 
		int isOk = bdao.update(boardDTO.getBvo());
		if(boardDTO.getFlist() == null) {
			return isOk;
		}
		if(isOk > 0 && boardDTO.getFlist().size() > 0) {
			for(FileVO fvo : boardDTO.getFlist()) {
				fvo.setBno(boardDTO.getBvo().getBno());
				isOk *= fdao.insertFile(fvo);
			}
		}
		return isOk;
	}

 

DTO.getBvo()로 bvo 객체를 넣어서 update해줄 수 있도록하고 파일이 없을 수도 있으므로 null이라면 isOk를 리턴하면서 끝내줌. null이 아니고 size가 0보다 크다면 Board의 번호를 set해주고  inserFile로 file에 정보를 insert 해준다음 isOk 리턴.

Mapper에 먼저 사용하면서 만들어둔 update와 insertFile을 사용하므로 매퍼는 수정해줄 필요없음.


● 매일 정해진 시간에 스케줄러를 실행 
매일 등록된 파일과 (DB) <-> 해당 일자의 폴더에 있는 파일이 일치하는 파일은 남겨놓고, 일치하지않는 파일이있다면 삭제 (DB에는 없는데, 폴더에는 남아있는 파일이 있다면 삭제)
전에 했던 것처럼 file.delete() - fileRemoveHandler를 사용하여 삭제해도 무방함.

 

스케줄러 관련 디펜던시

- quartz 2.3.2 / quarts-jobs 2.3.2

 

스케쥴러 클래스 FileSweeper 생성 

@Slf4j
@RequiredArgsConstructor
@Component
@EnableScheduling
public class FileSweeper {
	// 직접 DB 접속을 해서 처리
	private final FileDAO fdao;
}

 

일일히 연결하기 너무 번거롭고 길어지니 직접 DB에 접속해서 처리하기위해 FileDAO를 생성자 주입으로 연결해주고

그러기위해 @RequiredArgsConstructor를 사용해주어야함 스케쥴러를 사용하기위해 @EnableScheduling도 넣어주고

RootConfig에 @EnableTransactionManagement를 추가해줌.

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

 

기본 경로를 BASE_PATH에 저장해줘서 사용하도록함.

 

● 스케쥴러를 생성해줄건데 스케쥴 기록 cron 방식 초 분 시 일 월 요일 년도(생략가능)

	@Scheduled(cron="40 51 14 * * *")
	public void fileSweeper() {
		log.info(">>> fileSweeper Running Start~!! : {} ", LocalDateTime.now());
		// 처리
		// DB에 등록된 모든 파일 목록을 가져오기
		List<FileVO> dbList = fdao.selectListAllFile();
		
		// D:\_myProject\_java\_fileUpload\2024\11\04\\uuid_파일명
		// 이미지 파일이라면 D:\_myProject\_java\_fileUpload\2024\11\04\\uuid_th_파일명
		// 파일경로+파일명을 붙힌 실제 존재해야하는 파일리스트
		List<String> currFiles = new  ArrayList<String>();
		for(FileVO fvo : dbList) {
			String filePath = fvo.getSaveDir()+File.separator+fvo.getUuid();
			String fileName = fvo.getFileName();
			currFiles.add(BASE_PATH+filePath+"_"+fileName);
			
			// 이미지라면 썸네일경로도 추가 이미지파일 = fileType 1 
			if(fvo.getFileType() > 0 ) {
				currFiles.add(BASE_PATH+filePath+"_th_"+fileName);
			}	
		}
		log.info(">>> currFiles >> {}", currFiles);

 

파일 DAO에 연결한다음 매퍼에서 파일 전체리스트를 가져와줌

<select id="selectListAllFile" resultType="com.ezen.spring.domain.FileVO">
 	select * from file
</select>

 

가져온 리스트안의 객체를 for문으로 가져와서 파일 경로와 이름으로 저장하고 리스트안의 붙혀서 add해줌

이미지파일이라면 썸네일 파일도 넣기위해 타입이 0보다크면 th를붙혀서 add해줌.

 

  실제 파일 경로 설정 

LocalDate now = LocalDate.now();
String today = now.toString();
today = today.replace("-", File.separator);

 

날짜 폴더 경로 설정을위해 today를 만들어주고

	File dir = Paths.get(BASE_PATH+today).toFile();
		// listFiles() : 경로안에 있는 모든 파일을 배열로 리턴
		File[] allFileObject = dir.listFiles();
		log.info(">>> all file object >> {}",allFileObject);
		
		// 실제 저장되어있는 파일목록과, DB의 존재 파일을 비교하여 없는 파일은 삭제 진행
		for(File file : allFileObject) {
			String storedFileName = file.toPath().toString();
			if(!currFiles.contains(storedFileName)) {
				file.delete(); // 파일 삭제
				log.info(">>>> delete files >> {}", storedFileName);
			}
		}
		
		
		
		log.info(">>> fileSweeper Running End~!! : {} ", LocalDateTime.now());
	}

 

Paths.get으로 저장해뒀던 기본경로와 날짜경로를 붙혀서 파일로 dir에 넣어주고 

File배열로 listFiles해서 안에있는 모든파일을 배열로 리턴해줌.

 

파일배열안에 있는 파일을 for문으로 빼서 존재하는 파일리스트안에 파일의 이름이 포함돼있지않다면 삭제하도록함.

이러면 원하는 시간에 스케줄러를 실행해서 DB안에 없는 파일을 삭제할 수 있음.


리스트에 cmt_qty : 댓글수 has_file : 첨부파일 수를 표시하기 

 

● 먼저 현재까지 넣어놨던 댓글과 첨부파일 수를 업데이트해주고

update board b set cmt_qty = (select Count(*) from comment c where c.bno = b.bno);
update board b set has_file = (select Count(*) from file f where f.bno = b.bno);

 

● 댓글이 등록될 때 cmtQty 가 1증가할 수 있도록 업데이트 해줌.

@PostMapping("/post")
	public ResponseEntity<String> post(@RequestBody CommentVO cvo) {
		log.info(">>>>> post cvo >  {} ", cvo );
		
		int isOk = csv.post(cvo);		
		 if (isOk > 0) {
	            bsv.updateCmtCount(cvo.getBno()); 
	            return new ResponseEntity<String>("1", HttpStatus.OK);
	        } else {
	            return new ResponseEntity<String>("0", HttpStatus.INTERNAL_SERVER_ERROR);  // 500 error 
	        }
	}

 

 bsv.updateCmtCount(cvo.getBno());  를 만들어서 연결해주고 매퍼에서 구현

update board b set cmt_qty = cmt_qty + 1 where bno = #{bno}

 

● 댓글이 삭제될 때 cmtQty가 1 감소할 수 있도록 업데이트 해줌.

@ResponseBody
	@DeleteMapping(value="/{cno}", produces = MediaType.APPLICATION_JSON_VALUE)
	public String delete(@PathVariable("cno") long cno) {
		
		int isOk2 = bsv.cmtQtyDelete(cno);

		int isOk = csv.delete(cno);
		
		
		return isOk > 0 ? "1" : "0";
	}

 

cmtQtyDelete로 cno를받아서 서비스에 연결해주고

@Override
	public int cmtQtyDelete(long cno) {
		
		CommentVO cvo = cdao.getCvo(cno);
		
		int isOk = bdao.cmtQtyDelete(cvo.getBno());
		return isOk;
	}

 

bno를 찾아와야하기때문에 cno로 cvo 객체를 가져와서 cvo의 getBno로 bno를 찾아옴

 

다오에 연결하고 매퍼에서 구현

update board set cmt_qty = cmt_qty - 1 where bno = #{bno}

 

파일이 등록될 때 hasFile이 증가할 수 있도록 업데이트 구현

@PostMapping("/insert")
	public String insert(BoardVO bvo, MultipartFile[] files) {
		log.info(">>> insert bvo > {} ", bvo);
		
		List<FileVO> flist = null;
		
		if(files[0].getSize() > 0) {
			// 파일의 내용이 있다면
			flist = fh.uploadFiles(files);
			log.info(">>> flist > {}", flist);
			int isOk = bsv.hasFile(bvo.getBno());
		}		
		// files 정보를 이용하여 => List<FileVO> 변환을 하는 핸들러
		// FileHandler => return List<FileVO> + 파일 저장 
		
		
		BoardDTO bdto = new BoardDTO(bvo, flist); // bvo, flist
		
		int isOk = bsv.insert(bdto);
		log.info(" insert >> {} ", ( isOk >0? "성공" : "실패" ));
//		// 컨트롤러의 mapping 위치로 연결할 때 redirect:
		return "redirect:/";
		
	}

 

files의 크기가 0보다크면 bsv.hasFile로 (select Count(*) from file f where f.bno = b.bno); 해서 등록하고 파일 수를 표현할 수 있도록해줌 

@GetMapping("/list")
	public String list(Model m, PagingVO pgvo , BoardVO bvo) {
		
		List<BoardVO> list = bsv.getList(pgvo);
		
		int isOk = bsv.hasFile(bvo.getBno());
		
		// totalCount 구해서 PagingHandler에 매개변수로 전달 
		int totalCount = bsv.getTotal(pgvo);
		
		log.info(">>> totalCount > {}" , totalCount);
		
		PagingHandler ph = new PagingHandler(totalCount, pgvo);
		
		m.addAttribute("list",list);
		m.addAttribute("ph",ph);
		return "/board/list";
		
	}

 

리스트에도 똑같이 뿌려줄 수 있도록하고

● 파일이 삭제될때 has_file을 -1할 수 있도록 서비스에 연결해서 메서드 구현

@ResponseBody
	@DeleteMapping(value="/file/{uuid}")
	public String fileDelete(@PathVariable("uuid") String uuid) {
		
		log.info(">>> modfiy uuid > {} ", uuid);
		
		int isOk2 = bsv.hasFileDelete(uuid);

		int isOk= bsv.fileDelete(uuid);
		
		
		return isOk > 0 ? "1" : "0";
	}

 

uuid로는 bno를 가져올 수 없으니 fvo객체를 uuid로 먼저가져와서 bno를 가져와 delete할때 -1를 해줄 수 있도록함

@Override
	public int hasFileDelete(String uuid) {
		
		FileVO fvo = fdao.getFvo(uuid);		
		int isOk = bdao.hasFileDelete(fvo.getBno());
		
		return isOk;
	}

 

다오에 연결하고 매퍼에서 구현

 <update id="hasFileDelete">
 	update board set has_file = has_file - 1 where bno = #{bno}
 </update>

 

이러면 댓글수, 첨부파일 수를 리스트에 표시할 수 있음.

<c:if test="${bvo.cmtQty > 0 }">
	<span style="color: red">[${bvo.cmtQty }]</span> 
</c:if>

<c:if test="${bvo.hasFile > 0 }"> 
	<span style="color: red">[${bvo.hasFile }]</span>
</c:if>

 

list 화면에 0보다 댓글 수 , 첨부파일 수가 크면 표시할 수 있도록 해줌.

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

Spring 8일  (0) 2024.11.06
Spring 7일  (0) 2024.11.05
Spring 5일  (1) 2024.11.01
Spring 3일차.  (0) 2024.10.30
Spring 1일차.  (0) 2024.10.28