영상파일싱크 추가

This commit is contained in:
Moon
2025-12-16 09:19:02 +09:00
parent adfc404f79
commit 9855886b42
11 changed files with 1210 additions and 0 deletions

View File

@@ -0,0 +1,53 @@
package com.kamco.cd.kamcoback.scheduler;
import com.kamco.cd.kamcoback.code.dto.CommonCodeDto;
import com.kamco.cd.kamcoback.code.service.CommonCodeService;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "스캐쥴러 API", description = "스캐쥴러 API")
@RestController
@RequiredArgsConstructor
@RequestMapping({"/api/job"})
public class MapSheetMngFileJobApiController {
private final CommonCodeService commonCodeService;
private final MapSheetMngFileJobController mapSheetMngFileJobController;
@Operation(summary = "영상관리 파일 싱크 스캐쥴러 Start/Stop", description = "영상관리 파일 싱크 스캐쥴러 Start/Stop API")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "조회 성공",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = CommonCodeDto.Basic.class))),
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@PutMapping("/mng-sync-job")
public ApiResponseDto<String> mngSyncOnOff(
@RequestParam boolean jobStart,
@RequestParam int pageSize) {
mapSheetMngFileJobController.setSchedulerEnabled(jobStart);
mapSheetMngFileJobController.setMngSyncPageSize(pageSize);
return ApiResponseDto.createOK("OK");
}
}

View File

@@ -0,0 +1,116 @@
package com.kamco.cd.kamcoback.scheduler;
import com.kamco.cd.kamcoback.scheduler.service.MapSheetMngFileJobService;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class MapSheetMngFileJobController {
private final MapSheetMngFileJobService mapSheetMngFileJobService;
// 현재 상태 확인용 Getter
@Getter
private boolean isSchedulerEnabled = false;
@Getter
private int mngSyncPageSize = 20;
// 매일 새벽 3시에 실행 (초 분 시 일 월 요일)
@Scheduled(fixedDelay = 5000)
public void mngFileSyncJob00() {
if( !isSchedulerEnabled ) return;
System.out.println("mngFileSyncJob00 === " + System.currentTimeMillis());
mapSheetMngFileJobService.checkMapSheetFileProcess(0, mngSyncPageSize);
}
@Scheduled(fixedDelay = 5000)
public void mngFileSyncJob01() {
if( !isSchedulerEnabled ) return;
System.out.println("mngFileSyncJob01 === " + System.currentTimeMillis());
mapSheetMngFileJobService.checkMapSheetFileProcess(1, mngSyncPageSize);
}
@Scheduled(fixedDelay = 5000)
public void mngFileSyncJob02() {
if( !isSchedulerEnabled ) return;
System.out.println("mngFileSyncJob00 === " + System.currentTimeMillis());
mapSheetMngFileJobService.checkMapSheetFileProcess(2, mngSyncPageSize);
}
@Scheduled(fixedDelay = 5000)
public void mngFileSyncJob03() {
if( !isSchedulerEnabled ) return;
System.out.println("mngFileSyncJob03 === " + System.currentTimeMillis());
mapSheetMngFileJobService.checkMapSheetFileProcess(3, mngSyncPageSize);
}
@Scheduled(fixedDelay = 5000)
public void mngFileSyncJob04() {
if( !isSchedulerEnabled ) return;
System.out.println("mngFileSyncJob04 === " + System.currentTimeMillis());
mapSheetMngFileJobService.checkMapSheetFileProcess(4, mngSyncPageSize);
}
@Scheduled(fixedDelay = 5000)
public void mngFileSyncJob05() {
if( !isSchedulerEnabled ) return;
System.out.println("mngFileSyncJob05 === " + System.currentTimeMillis());
mapSheetMngFileJobService.checkMapSheetFileProcess(5, mngSyncPageSize);
}
@Scheduled(fixedDelay = 5000)
public void mngFileSyncJob06() {
if( !isSchedulerEnabled ) return;
System.out.println("mngFileSyncJob06 === " + System.currentTimeMillis());
mapSheetMngFileJobService.checkMapSheetFileProcess(6, mngSyncPageSize);
}
@Scheduled(fixedDelay = 5000)
public void mngFileSyncJob07() {
if( !isSchedulerEnabled ) return;
System.out.println("mngFileSyncJob07 === " + System.currentTimeMillis());
mapSheetMngFileJobService.checkMapSheetFileProcess(7, mngSyncPageSize);
}
@Scheduled(fixedDelay = 5000)
public void mngFileSyncJob08() {
if( !isSchedulerEnabled ) return;
System.out.println("mngFileSyncJob08 === " + System.currentTimeMillis());
mapSheetMngFileJobService.checkMapSheetFileProcess(8, mngSyncPageSize);
}
@Scheduled(fixedDelay = 5000)
public void mngFileSyncJob09() {
if( !isSchedulerEnabled ) return;
System.out.println("mngFileSyncJob09 === " + System.currentTimeMillis());
mapSheetMngFileJobService.checkMapSheetFileProcess(9, mngSyncPageSize);
}
// 3. 외부에서 플래그를 변경할 수 있는 Setter 메서드
public void setSchedulerEnabled(boolean enabled) {
this.isSchedulerEnabled = enabled;
System.out.println("스케줄러 상태 변경됨: "+( enabled ? "ON" : "OFF"));
}
public void setMngSyncPageSize(int pageSize) {
this.mngSyncPageSize = pageSize;
System.out.println("스케줄러 처리 개수 변경됨: "+pageSize);
}
}

View File

@@ -0,0 +1,159 @@
package com.kamco.cd.kamcoback.scheduler.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
public class FileDto {
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class SrchFoldersDto {
@Schema(description = "디렉토리경로(ROOT:/app/original-images)", example = "")
@NotNull
private String dirPath;
}
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class SrchFilesDto {
@Schema(description = "디렉토리경로", example = "D:\\kamco\\2022\\캠코_2021_2022_34602060_D1")
@NotNull
private String dirPath;
@Schema(description = "전체(*), cpg,dbf,geojson등", example = "*")
@NotNull
private String extension;
@Schema(description = "전체(*), 3878687.tif", example = "*")
@NotNull
private String fileNm;
@Schema(description = "파일명(name), 최종수정일(date)", example = "name")
@NotNull
private String sortType;
@Schema(description = "파일시작위치", example = "1")
@NotNull
private Integer startPos = 0;
@Schema(description = "파일종료위치", example = "100")
@NotNull
private Integer endPos = 100;
}
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class SrchFilesDepthDto extends SrchFilesDto {
@Schema(description = "최대폴더Depth", example = "5")
@NotNull
private Integer maxDepth;
}
@Schema(name = "FolderDto", description = "폴더 정보")
@Getter
public static class FolderDto {
private final String folderNm;
private final String parentFolderNm;
private final String parentPath;
private final String fullPath;
private final int depth;
private final long childCnt;
private final String lastModified;
private final Boolean isValid;
public FolderDto(
String folderNm,
String parentFolderNm,
String parentPath,
String fullPath,
int depth,
long childCnt,
String lastModified,
Boolean isValid) {
this.folderNm = folderNm;
this.parentFolderNm = parentFolderNm;
this.parentPath = parentPath;
this.fullPath = fullPath;
this.depth = depth;
this.childCnt = childCnt;
this.lastModified = lastModified;
this.isValid = isValid;
}
}
@Schema(name = "FoldersDto", description = "폴더목록 정보")
@Getter
public static class FoldersDto {
private final String dirPath;
private final int folderTotCnt;
private final int folderErrTotCnt;
private final List<FolderDto> folders;
public FoldersDto(
String dirPath, int folderTotCnt, int folderErrTotCnt, List<FolderDto> folders) {
this.dirPath = dirPath;
this.folderTotCnt = folderTotCnt;
this.folderErrTotCnt = folderErrTotCnt;
this.folders = folders;
}
}
@Schema(name = "File Basic", description = "파일 기본 정보")
@Getter
public static class Basic {
private final String fileNm;
private final String parentFolderNm;
private final String parentPath;
private final String fullPath;
private final String extension;
private final long fileSize;
private final String lastModified;
public Basic(
String fileNm,
String parentFolderNm,
String parentPath,
String fullPath,
String extension,
long fileSize,
String lastModified) {
this.fileNm = fileNm;
this.parentFolderNm = parentFolderNm;
this.parentPath = parentPath;
this.fullPath = fullPath;
this.extension = extension;
this.fileSize = fileSize;
this.lastModified = lastModified;
}
}
@Schema(name = "FilesDto", description = "파일 목록 정보")
@Getter
public static class FilesDto {
private final String dirPath;
private final int fileTotCnt;
private final long fileTotSize;
private final List<Basic> files;
public FilesDto(String dirPath, int fileTotCnt, long fileTotSize, List<Basic> files) {
this.dirPath = dirPath;
this.fileTotCnt = fileTotCnt;
this.fileTotSize = fileTotSize;
this.files = files;
}
}
}

View File

@@ -0,0 +1,129 @@
package com.kamco.cd.kamcoback.scheduler.dto;
import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm;
import com.kamco.cd.kamcoback.config.enums.EnumType;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.ZonedDateTime;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
public class MapSheetMngDto {
@Schema(name = "MngSearchReq", description = "영상관리 검색 요청")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class MngSearchReq {
// 페이징 파라미터
@Schema(description = "페이지 번호 (0부터 시작) ", example = "0")
private int page = 0;
@Schema(description = "페이지 크기", example = "20")
private int size = 20;
@Schema(description = "년도", example = "2025")
private Integer mngYyyy;
public Pageable toPageable() {
return PageRequest.of(page, size);
}
}
@Schema(name = "MngDto", description = "영상관리 검색 리턴")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class MngDto {
private int rowNum;
private int mngYyyy;
private String mngState;
private String syncState;
private String syncCheckState;
private Long syncTotCnt;
private Long syncStateDoneCnt;
private Long syncCheckStateDoneCnt;
private Long syncNotFileCnt;
private Long syncTypeErrorCnt;
private Long syncSizeErrorCnt;
@JsonFormatDttm private ZonedDateTime rgstStrtDttm;
@JsonFormatDttm private ZonedDateTime rgstEndDttm;
}
@Schema(name = "MngHstDto", description = "영상관리내역 검색 리턴")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class MngHstDto {
private long hstUid;
private int mngYyyy;
private String mapSheetNum;
private String refMapSheetNum;
private String dataState;
private String syncState;
private String syncCheckState;
@JsonFormatDttm private ZonedDateTime syncStrtDttm;
@JsonFormatDttm private ZonedDateTime syncEndDttm;
@JsonFormatDttm private ZonedDateTime syncCheckStrtDttm;
@JsonFormatDttm private ZonedDateTime syncCheckEndDttm;
private String syncMngPath;
}
@Schema(name = "MngFileAddReq", description = "영상관리파일 등록 요청")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class MngFileAddReq {
private int mngYyyy;
private String mapSheetNum;
private String refMapSheetNum;
private String filePath;
private String fileName;
private String fileExt;
private Long hstUid;
private Long fileSize;
private String fileState;
}
@Schema(name = "MngFilesDto", description = "영상관리내역 검색 리턴")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class MngFilesDto {
private long fileUid;
private int mngYyyy;
private String mapSheetNum;
private String refMapSheetNum;
private String filePath;
private String fileName;
private String fileExt;
private Long hstUid;
private Long fileSize;
}
@Schema(name = "DmlReturn", description = "영상관리 DML 수행 후 리턴")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class DmlReturn {
private String flag;
private String message;
}
}

View File

@@ -0,0 +1,299 @@
package com.kamco.cd.kamcoback.scheduler.service;
import static java.lang.String.CASE_INSENSITIVE_ORDER;
import com.kamco.cd.kamcoback.scheduler.dto.FileDto;
import com.kamco.cd.kamcoback.scheduler.dto.FileDto.FilesDto;
import com.kamco.cd.kamcoback.scheduler.dto.FileDto.SrchFilesDepthDto;
import com.kamco.cd.kamcoback.scheduler.dto.FileDto.SrchFilesDto;
import com.kamco.cd.kamcoback.scheduler.dto.MapSheetMngDto;
import com.kamco.cd.kamcoback.scheduler.dto.MapSheetMngDto.MngDto;
import com.kamco.cd.kamcoback.postgres.core.MapSheetMngFileJobCoreService;
import com.kamco.cd.kamcoback.scheduler.dto.MapSheetMngDto.MngHstDto;
import com.kamco.cd.kamcoback.common.utils.FIleChecker;
import com.kamco.cd.kamcoback.common.utils.NameValidator;
import com.kamco.cd.kamcoback.config.FileConfig;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.FileTime;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.RequiredArgsConstructor;
import org.apache.commons.io.FilenameUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class MapSheetMngFileJobService {
private final MapSheetMngFileJobCoreService mapSheetMngFileJobCoreService;
@Transactional
public void checkMapSheetFileProcess(long targetNum, int mngSyncPageSize) {
List<MngHstDto> mapSheetFileNotYetList = findTargetMapSheetFileList(targetNum, mngSyncPageSize);
Long hstUid = 0L;
String syncState = "";
String syncCheckState = "";
String fileState = "";
String dataState = "";
SrchFilesDepthDto srchDto = new SrchFilesDepthDto();
List<FIleChecker.Basic> basicList = new ArrayList<>();
for (MngHstDto item : mapSheetFileNotYetList) {
//도엽별 파일 체크 진행중으로 변경
item.setDataState("PROCESSING");
mngHstDataSyncStateUpdate(item);
// 1. MngHstDto 객체의 필드 값에 접근
//hstUid = item.getHstUid();
//syncState = item.getSyncState();
srchDto.setMaxDepth(10);
srchDto.setDirPath(item.getSyncMngPath());
srchDto.setExtension("tif,tfw");
srchDto.setFileNm(item.getMapSheetNum());
//srchDto.setFileNm("34602047");
System.out.println("UID: " + hstUid + ", 상태: " + syncState + ", 관리경로: " + item.getSyncMngPath() + ", 파일명 " + item.getMapSheetNum() + " .tif,tfw");
//도엽번호로 파일 찾기
//basicList = this.getFilesDepthAll(srchDto);
basicList = FIleChecker.getFilesFromAllDepth(srchDto.getDirPath(), srchDto.getFileNm(),
srchDto.getExtension(), srchDto.getMaxDepth(), srchDto.getSortType(), 0, 100);
int tfwCnt =
(int)
basicList.stream()
.filter(dto -> dto.getExtension().toString().equals("tfw"))
.count();
int tifCnt =
(int)
basicList.stream()
.filter(dto -> dto.getExtension().toString().equals("tif"))
.count();
syncState = "";
syncCheckState = "";
if( tfwCnt == 0 && tifCnt == 0)syncState="NOFILE";
for (FIleChecker.Basic item2 : basicList) {
System.out.println("path: " + item2.getParentPath());
System.out.println("path: " + item2.getFileNm());
System.out.println("path: " + item2.getFullPath());
MapSheetMngDto.MngFileAddReq addReq = new MapSheetMngDto.MngFileAddReq();
addReq.setMngYyyy(item.getMngYyyy());
addReq.setMapSheetNum(item.getMapSheetNum());
addReq.setRefMapSheetNum(item.getRefMapSheetNum());
addReq.setFilePath(item2.getParentPath());
addReq.setFileName(item2.getFileNm());
addReq.setFileExt(item2.getExtension());
addReq.setFileSize(item2.getFileSize());
addReq.setHstUid(item.getHstUid());
fileState = "DONE";
if(item2.getExtension().equals("tfw") ) {
if( tifCnt == 0){fileState = "NOTPAIR";syncState = fileState;}
else if( tfwCnt > 1){fileState = "DUPLICATE";syncState = fileState;}
else if( item2.getFileSize() == 0 ){fileState = "SIZEERROR";syncState = fileState;}
else if( ! FIleChecker.checkTfw(item2.getFullPath()) ){fileState = "TYPEERROR";syncState = fileState;}
}
else if(item2.getExtension().equals("tif") ){
if( tfwCnt == 0){fileState = "NOTPAIR";syncState = fileState;}
else if( tifCnt > 1){fileState = "DUPLICATE";syncState = fileState;}
else if( item2.getFileSize() == 0 ){fileState = "SIZEERROR";syncState = fileState;}
else if( ! FIleChecker.cmmndGdalInfo(item2.getFullPath()) ){fileState = "TYPEERROR";syncState = fileState;}
}
addReq.setFileState(fileState);
MapSheetMngDto.DmlReturn DmlReturn = mngDataSave(addReq);
}
//도엽별 파일 체크 완료로 변경
item.setDataState("DONE");
if(syncState.isEmpty())syncState="DONE";
item.setSyncState(syncState);
mngHstDataSyncStateUpdate(item);
//srchDto.
// 2. 출력
//System.out.println("UID: " + hstUid + ", 상태: " + syncState + ", 관리경로: " + item.getSyncMngPath());
// 3. (필요하다면) 다른 로직 수행
// ...
}
}
public int checkIsNoFile(List<FileDto.Basic> basicList)
{
if( basicList == null || basicList.size() == 0 )
{
return 0;
}
return basicList.size();
}
public List<MngHstDto> findTargetMapSheetFileList(long targetNum, int pageSize) {
return mapSheetMngFileJobCoreService.findTargetMapSheetFileList(targetNum, pageSize);
}
public MapSheetMngDto.DmlReturn mngHstDataSyncStateUpdate(MapSheetMngDto.MngHstDto UpdateReq) {
return mapSheetMngFileJobCoreService.mngHstDataSyncStateUpdate(UpdateReq);
}
public MapSheetMngDto.DmlReturn mngDataSave(MapSheetMngDto.MngFileAddReq AddReq) {
return mapSheetMngFileJobCoreService.mngFileSave(AddReq);
}
public List<FileDto.Basic> getFilesDepthAll(SrchFilesDepthDto srchDto) {
Path startPath = Paths.get(srchDto.getDirPath());
int maxDepth = srchDto.getMaxDepth();
String dirPath = srchDto.getDirPath();
String targetFileNm = srchDto.getFileNm();
String extension = srchDto.getExtension();
String sortType = srchDto.getSortType();
int startPos = srchDto.getStartPos();
int endPos = srchDto.getEndPos();
int limit = endPos - startPos + 1;
Set<String> targetExtensions = createExtensionSet(extension);
List<FileDto.Basic> fileDtoList = new ArrayList<>();
SimpleDateFormat dttmFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
int fileTotCnt = 0;
long fileTotSize = 0;
try (Stream<Path> stream = Files.walk(startPath, maxDepth)) {
fileDtoList =
stream
.filter(Files::isRegularFile)
.filter(
p ->
extension == null
|| extension.equals("")
|| extension.equals("*")
|| targetExtensions.contains(extractExtension(p)))
.sorted(getFileComparator(sortType))
.filter(
p -> p.getFileName().toString().contains(targetFileNm)
)
.skip(startPos)
.limit(limit)
.map(
path -> {
int depth = path.getNameCount();
String fileNm = path.getFileName().toString();
String ext = FilenameUtils.getExtension(fileNm);
String parentFolderNm = path.getParent().getFileName().toString();
String parentPath = path.getParent().toString();
String fullPath = path.toAbsolutePath().toString();
File file = new File(fullPath);
long fileSize = file.length();
String lastModified = dttmFormat.format(new Date(file.lastModified()));
return new FileDto.Basic(
fileNm, parentFolderNm, parentPath, fullPath, ext, fileSize, lastModified);
})
.collect(Collectors.toList());
//fileTotCnt = fileDtoList.size();
//fileTotSize = fileDtoList.stream().mapToLong(FileDto.Basic::getFileSize).sum();
} catch (IOException e) {
System.err.println("파일 I/O 오류 발생: " + e.getMessage());
}
return fileDtoList;
}
public Set<String> createExtensionSet(String extensionString) {
if (extensionString == null || extensionString.isBlank()) {
return Set.of();
}
// "java, class" -> ["java", " class"] -> [".java", ".class"]
return Arrays.stream(extensionString.split(","))
.map(ext -> ext.trim())
.filter(ext -> !ext.isEmpty())
.map(ext -> "." + ext.toLowerCase())
.collect(Collectors.toSet());
}
public String extractExtension(Path path) {
String filename = path.getFileName().toString();
int lastDotIndex = filename.lastIndexOf('.');
// 확장자가 없거나 파일명이 .으로 끝나는 경우
if (lastDotIndex == -1 || lastDotIndex == filename.length() - 1) {
return ""; // 빈 문자열 반환
}
// 확장자 추출 및 소문자 변환
return filename.substring(lastDotIndex).toLowerCase();
}
public Comparator<Path> getFileComparator(String sortType) {
// 파일 이름 비교 기본 Comparator (대소문자 무시)
Comparator<Path> nameComparator =
Comparator.comparing(path -> path.getFileName().toString(), CASE_INSENSITIVE_ORDER);
Comparator<Path> dateComparator =
Comparator.comparing(
path -> {
try {
return Files.getLastModifiedTime(path);
} catch (IOException e) {
return FileTime.fromMillis(0);
}
});
if ("name desc".equalsIgnoreCase(sortType)) {
return nameComparator.reversed();
} else if ("date".equalsIgnoreCase(sortType)) {
return dateComparator;
} else if ("date desc".equalsIgnoreCase(sortType)) {
return dateComparator.reversed();
} else {
return nameComparator;
}
}
}