이어서 파일 관련 설정 ( 경로 , 저장 등) 을 하는 파일 핸들러부터 구현
● 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 |