Ajax를 이용해서 첨부파일을 사용하면 사용자가 게시물을 등록하거나 수정하기 전에
이미 업로드 시킨 파일들을 볼 수 있다는 장점이 있지만, 다음과 같은 문제가 생긴다.
- 첨부파일만을 등록하고 게시물을 등록하지 않을 떄의 문제
파일은 이미 서버에 업로드 되었지만, 게시물을 등록하지 않았으므로 의미 없이 파일들만 서버레 업로드된 상황
- 게시물을 수정할 때 파일을 삭제했지만 실제로 폴더에서 기존 파일은 삭제되지 않은 문제
데이터베이스에는 기존 파일이 삭제되었지만, 실제 폴더에는 남는 문제
이는 사용자가 Ajax로 어떤 작업을 한 후에 비정상적으로 브라우저를 종료하거나 페이지를 빠져나가는 문제이다.
이 문제를 해결하는 핵심은 정상적으로 사용자의 게시물에 첨부된 파일인지 아니면 사용자가 게시물을 수정할때
업로드 했지만 최종적으로 사용되는 파일인지 아닌지 파악해야 한다.
잘못 업로드된 파일의 정리
게시물에 필요한 모든 파일에 대한 정보는 최종적으로는 데이터베이스에 기록되어 있다.
만일 사용자가 게시물을 등록하거나 수정하기 위해서 첨부파일을 등록했지만, 최종적으로 제출을 하지않은 경우
폴더에 파일들은 생성되지만 DB에만 변화가 없게 되는것이다.
정상적으로 해당 첨부파일이 게시물에 사용되었다면 DB 테이블에 기록되어있을 것이므로, DB와 비교하는 작업을 거쳐서 업로드만 된 파일의 목록을 찾아야 한다.
파일의 목록을 찾을 때에 반드시 오늘날짜가 아닌 파일들을 대상으로 해야만 한다.
만일 오늘 날짜를 대상으로 하는 경우 지금 현재 게시물을 작성하거나 수정하기 위해서 업로드 하고있는 파일들을
삭제할 가능성이 있기 때문이다.
이 작업을 주기적으로 동잭해야하므로 스케줄링을 할 수 있는 Spring-Batch나 Quartz라이브러리를 이용한다.
<!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz-jobs -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.3.0</version>
</dependency>
쿼츠 라이브러리는 일반적으로 스케줄러를 구성하기 위해 사용한다.
서버를 운영하기위해서 간혹 주기적으로 매일, 매주, 매월 등 주기적으로 특정한
프로그램을 실행할 필요가 있다. 이 작업은 운영체제의 기능을 이용해서 작업할 수도 있지만,
스프링과 쿼츠 라이브러리를 이용하면 간단히 처리할 수 있다.
// (스프링 배치의 경우 많은 야으이 데이터를 주기적으로 읽고 쓰는 작업에는 유용하지만 설정이 복잡하다)
라이브러리를 추가해주도록 하자
그러면 이제 쿼트에 대한 설정은 XML과 어노테이션을 활용할 수 가있다.
어노테이션을 이용하기 위해서 root-context.xml을 수정해준다.
네임스페이스에 맨밑 하단에 task항목을 체크한다.
그리고 xml 파일의 내용에
<task:annotation-driven/>
를 추가한다.
Task작업의 처리
실제작업의 로직은 별도로 org.study.task라는 패키지 내에 FIleCheckTask라는 클래스를 작성해서 처리한다.
@Log4j
@Component
public class FileCheckTask {
@Scheduled(cron = "0 * * * * *")
public void checkFiles() throws Exception {
log.warn("File Check Task run.................");
log.warn("===========================================");
}
}
FileCkeckTask에는 @Component 어노테이션과 @Scheduled 어노테이션이 사용되었습니다.
@Scheduled어노테이션 내에는 cron이라는 속성을 부여해서 주기를 제어한다.
로그가 정상적으로 기록되는지 확인하기 위해서 log.warn()레벨을 이용해서 실행중에 확일 할 수 있다.
우선 FileCheckTask가 정상적으로 동작하는지 확인하기 위해서 root-context.xml에 FileCheckTask를 스프링의 빈으로 설정한다.
<context:component-scan base-package="org.study.task"></context:component-scan>
예제의 cron 설정은 위의 경우 매분 0초마다 한 번씩 실행되도록 지정되었으므로, 서버을 실행해 두고 1분에 한 번씩 로그가 기록되는지를 확인한다.
그런데 그걸 확인할 시간이없어 우선 계속 글을 작성하면서 확인이 되면 사진을 찍어 확인해보겠다.
BoardAttachMapper 수정
FileCheckTask 가 정상적으로 동작하는 지를 확인했다면, DB에서 어제 등록된 모든 파일의 목록이 필요하므로, BoardAttachMapper에 첨부파일 목록을 가져오는 메서드를 추가해준다.
public List<BoardAttachVO> getOldFiles();
BoardAttachMapper.xml 파일에는 아래와 같이 쿼리를 추가합니다. sysdate에서 1을 빼 어제 날짜를 처리해주도록 한다.
<select id="getOldFiles"
resultType="org.study.domain.BoardAttachVO">
select * from tbl_attach where uploadpath = to_char(sysdate
-1 ,'yyyy\mm\dd')
</select>
cron설정과 삭제처리
Cron은 원래 유닉스 계열에서 사용되는 스케줄러 프로그램의 이름이지만, 워낙 많이 사용되다 보니 각종 언어나
기술에 맞는 라이브러리 형태로 많이 사용된다.
작성된 FileCheckTask내에서는 @Scheduled(cron=0 * * * *')과 같이 표현식이 사용되었는데,
이때의 의미는
@Scheduled(cron=0 * * * *')
0 = second(0~ 59)
* = minutes(0~ 59)
* = hours(0~ 23)
* = day(1~31)
* = months(1~12)
(*) = year(optional)
파일의 목록처리
작업의 순서는
1. DB에서 어제 사용된 파일의 목록을 얻어고,
2. 해당 폴더의 파일 목록에서 DB에 없는 파일을 찾아낸다.
3. DB에 없는 파일들을 삭제
@Setter(onMethod_ = { @Autowired })
private BoardAttachMapper attachMapper;
private String getFolderYesterDay() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE, -1);
String str = sdf.format(cal.getTime());
return str.replace("-", File.separator);
}
@Scheduled(cron = "0 0 3 * * *")
public void checkFiles() throws Exception {
log.warn("File Check Task run.................");
log.warn(new Date());
// file list in database
List<BoardAttachVO> fileList = attachMapper.getOldFiles();
// ready for check file in directory with database file list
List<Path> fileListPaths = fileList.stream()
.map(vo -> Paths.get("C:\\upload", vo.getUploadPath(), vo.getUuid() + "_" + vo.getFileName()))
.collect(Collectors.toList());
// image file has thumnail file
fileList.stream().filter(vo -> vo.isFileType() == true)
.map(vo -> Paths.get("C:\\upload", vo.getUploadPath(), "s_" + vo.getUuid() + "_" + vo.getFileName()))
.forEach(p -> fileListPaths.add(p));
log.warn("===========================================");
fileListPaths.forEach(p -> log.warn(p));
// files in yesterday directory
File targetDir = Paths.get("C:\\upload", getFolderYesterDay()).toFile();
File[] removeFiles = targetDir.listFiles(file -> fileListPaths.contains(file.toPath()) == false);
log.warn("-----------------------------------------");
for (File file : removeFiles) {
log.warn(file.getAbsolutePath());
file.delete();
}
}
FileCheckTask의 checkFiles()는 매일 새벽 2시에 동작한다.
먼저 attachMapper를 이용해서 어제 날짜로 보관되는 모든 첨부파일의 목록을 가져온다.
그리고 DB내에서 가져온 파일 목록은 BoardAttachVo타입의 객체로, 나중에 비교를 위해서
java.nio.Paths 의 목록으로 변환처리 한다.
이때 이미지 파일의 경우 썸네일의 파일도 목록에 필요하기 때문에 별도 처리해서 해당 날짜의 예상 파일 목록을
완성한다.
코드에서는 fileListPaths라는 이름의 변수로 처리한다.
DB에 있는 파일들의 준비가 끝나면 실제 폴더에 있는 파일들의 목록에서 데이터 베이스에는 없는 파일들을 찾아서
목록으로 준비한다. 이결과는 removeFiles변수에 담아서 처리한다.
최종적으로는 삭제 대상이 되는 파일들을 삭제한다.
사실 이번단계작업은 실전에서 정말필요하나 개인적인 프로젝트를 만들때 사용자들에게 보여지는 기능은 아니다.
하지만 보여지는 부분이 아니라고 해서 안해도되는, 없어도 티안나는 그런게아니다
프로그램이던 웹개발이던간에 개발을 하고 고객에게 주는게 다반사가아닌 정말 오류가없고, 잘짜여지고 그리고
유지보수라던가 해당 프로그램이나 기능에대해 os적이라던가 프로세스, 용량 등 모든것들을 다 신경써야 한다.
그래야 정말 개발자라고 할수있다고 생각한다. 그래서 이번 과정은
예쁘고 잘보이고 이런게아닌 트랜잭션처리와 같이 정말로 중요한 기능을 구현했다고 보고있다.
'Spring공부 > 3-파일업로드' 카테고리의 다른 글
파일 formData와 String 서버에 보내기 / 파일 업로드 다른 정보도 같이 보내기 (0) | 2022.12.12 |
---|---|
파일업로드 마무리 (0) | 2021.10.15 |
프로젝트(5)-게시글 수정 및 기타작업 (0) | 2021.10.15 |
프로젝트(4)-게시물 삭제 (0) | 2021.10.15 |
프로젝트(3)-게시물 조회 (0) | 2021.10.15 |
댓글