From 5707a3130905afbfdce14c6d8044b2502f4d0c88 Mon Sep 17 00:00:00 2001 From: Moon Date: Fri, 26 Dec 2025 08:50:05 +0900 Subject: [PATCH] =?UTF-8?q?=EC=8B=B1=ED=81=AC=EA=B2=BD=EB=A1=9C=20yml?= =?UTF-8?q?=EC=97=90=20=EC=84=A4=EC=A0=95=ED=95=98=EA=B3=A0=20FileConfig?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C=20=EC=98=81=EC=83=81=EA=B4=80=EB=A6=AC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kamcoback/common/utils/FIleChecker.java | 15 + .../kamco/cd/kamcoback/config/FileConfig.java | 22 - .../mapsheet/MapSheetMngApiController.java | 46 ++ .../MapSheetMngFileCheckerApiController.java | 143 ----- .../MapSheetMngFileCheckerService.java | 530 ------------------ .../mapsheet/service/MapSheetMngService.java | 129 +++-- .../postgres/core/MapSheetMngCoreService.java | 4 + .../mapsheet/MapSheetMngRepositoryCustom.java | 2 + .../mapsheet/MapSheetMngRepositoryImpl.java | 12 + .../MapSheetMngFileJobController.java | 2 + src/main/resources/application.yml | 2 +- 11 files changed, 171 insertions(+), 736 deletions(-) delete mode 100644 src/main/java/com/kamco/cd/kamcoback/config/FileConfig.java delete mode 100644 src/main/java/com/kamco/cd/kamcoback/mapsheet/MapSheetMngFileCheckerApiController.java delete mode 100644 src/main/java/com/kamco/cd/kamcoback/mapsheet/service/MapSheetMngFileCheckerService.java diff --git a/src/main/java/com/kamco/cd/kamcoback/common/utils/FIleChecker.java b/src/main/java/com/kamco/cd/kamcoback/common/utils/FIleChecker.java index 3dd7854b..5f6909d0 100644 --- a/src/main/java/com/kamco/cd/kamcoback/common/utils/FIleChecker.java +++ b/src/main/java/com/kamco/cd/kamcoback/common/utils/FIleChecker.java @@ -478,6 +478,21 @@ public class FIleChecker { return fileTotSize; } + public static boolean multipartSaveTo(MultipartFile mfile, String targetPath) + { + Path tmpSavePath = Paths.get(targetPath); + + boolean fileUpload = true; + try { + mfile.transferTo(tmpSavePath); + } catch (IOException e) { + // throw new RuntimeException(e); + return false; + } + + return true; + } + public static boolean validationMultipart(MultipartFile mfile) { // 파일 유효성 검증 diff --git a/src/main/java/com/kamco/cd/kamcoback/config/FileConfig.java b/src/main/java/com/kamco/cd/kamcoback/config/FileConfig.java deleted file mode 100644 index 196fc9aa..00000000 --- a/src/main/java/com/kamco/cd/kamcoback/config/FileConfig.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.kamco.cd.kamcoback.config; - -import lombok.Getter; -import lombok.Setter; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; - -/** GeoJSON 파일 모니터링 설정 */ -@Component -@ConfigurationProperties(prefix = "file.config") -@Getter -@Setter -public class FileConfig { - - // private String rootSyncDir = "D:\\app\\original-images\\"; - // private String tmpSyncDir = rootSyncDir + "tmp\\"; - - private String rootSyncDir = "/app/original-images/"; - private String tmpSyncDir = rootSyncDir + "tmp/"; - - private String syncFileExt = "tfw,tif"; -} diff --git a/src/main/java/com/kamco/cd/kamcoback/mapsheet/MapSheetMngApiController.java b/src/main/java/com/kamco/cd/kamcoback/mapsheet/MapSheetMngApiController.java index 695dcebf..76ea5027 100644 --- a/src/main/java/com/kamco/cd/kamcoback/mapsheet/MapSheetMngApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/mapsheet/MapSheetMngApiController.java @@ -3,6 +3,10 @@ package com.kamco.cd.kamcoback.mapsheet; 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 com.kamco.cd.kamcoback.mapsheet.dto.FileDto.FilesDto; +import com.kamco.cd.kamcoback.mapsheet.dto.FileDto.FoldersDto; +import com.kamco.cd.kamcoback.mapsheet.dto.FileDto.SrchFilesDto; +import com.kamco.cd.kamcoback.mapsheet.dto.FileDto.SrchFoldersDto; import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto; import com.kamco.cd.kamcoback.mapsheet.service.MapSheetMngService; import io.swagger.v3.oas.annotations.Operation; @@ -222,4 +226,46 @@ public class MapSheetMngApiController { @RequestParam @Valid List fileUids) { return ApiResponseDto.ok(mapSheetMngService.deleteByFileUidMngFile(fileUids)); } + + + + @Operation(summary = "폴더 조회", description = "폴더 조회 (ROOT:/app/original-images 이하로 경로입력)") + @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) + }) + @PostMapping("/folder-list") + public ApiResponseDto getDir(@RequestBody SrchFoldersDto srchDto) { + + return ApiResponseDto.createOK(mapSheetMngService.getFolderAll(srchDto)); + } + + @Operation(summary = "지정폴더내 파일목록 조회", description = "지정폴더내 파일목록 조회") + @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) + }) + @PostMapping("/file-list") + public ApiResponseDto getFiles(@RequestBody SrchFilesDto srchDto) { + + return ApiResponseDto.createOK(mapSheetMngService.getFilesAll(srchDto)); + } + + } diff --git a/src/main/java/com/kamco/cd/kamcoback/mapsheet/MapSheetMngFileCheckerApiController.java b/src/main/java/com/kamco/cd/kamcoback/mapsheet/MapSheetMngFileCheckerApiController.java deleted file mode 100644 index f0a859bf..00000000 --- a/src/main/java/com/kamco/cd/kamcoback/mapsheet/MapSheetMngFileCheckerApiController.java +++ /dev/null @@ -1,143 +0,0 @@ -package com.kamco.cd.kamcoback.mapsheet; - -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 com.kamco.cd.kamcoback.mapsheet.dto.FileDto.FilesDto; -import com.kamco.cd.kamcoback.mapsheet.dto.FileDto.FoldersDto; -import com.kamco.cd.kamcoback.mapsheet.dto.FileDto.SrchFilesDto; -import com.kamco.cd.kamcoback.mapsheet.dto.FileDto.SrchFoldersDto; -import com.kamco.cd.kamcoback.mapsheet.service.MapSheetMngFileCheckerService; -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.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@Tag(name = "영상 관리", description = "영상 관리 API") -@RestController -@RequiredArgsConstructor -@RequestMapping({"/api/mapsheet"}) -public class MapSheetMngFileCheckerApiController { - - private final CommonCodeService commonCodeService; - private final MapSheetMngFileCheckerService mapSheetMngFileCheckerService; - - @Operation(summary = "폴더 조회", description = "폴더 조회 (ROOT:/app/original-images 이하로 경로입력)") - @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) - }) - @PostMapping("/folder-list") - public ApiResponseDto getDir(@RequestBody SrchFoldersDto srchDto) { - - return ApiResponseDto.createOK(mapSheetMngFileCheckerService.getFolderAll(srchDto)); - } - - @Operation(summary = "지정폴더내 파일목록 조회", description = "지정폴더내 파일목록 조회") - @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) - }) - @PostMapping("/file-list") - public ApiResponseDto getFiles(@RequestBody SrchFilesDto srchDto) { - - return ApiResponseDto.createOK(mapSheetMngFileCheckerService.getFilesAll(srchDto)); - } - - /* - @Operation(summary = "파일 업로드", description = "파일 업로드 및 TIF 검증") - @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ApiResponseDto uploadFile( - @RequestPart("file") MultipartFile file, - @RequestParam("targetPath") String targetPath, - @RequestParam(value = "overwrite", required = false, defaultValue = "false") - boolean overwrite, - @RequestParam(value = "hstUid", required = false) Long hstUid) { - return ApiResponseDto.createOK( - mapSheetMngFileCheckerService.uploadFile(file, targetPath, overwrite, hstUid)); - } - - */ - - /* - @Operation(summary = "파일 삭제", description = "중복 파일 등 파일 삭제") - @PostMapping("/delete") - public ApiResponseDto deleteFile(@RequestBody SrchFoldersDto dto) { - return ApiResponseDto.createOK(mapSheetMngFileCheckerService.deleteFile(dto.getDirPath())); - } - - @Operation(summary = "중복 파일 삭제", description = "중복 데이터 발견 시 기존 데이터를 삭제") - @PostMapping(value = "/delete-file") - public ApiResponseDto deleteDuplicateFile( - @RequestParam("filePath") String filePath, @RequestParam("fileName") String fileName) { - return ApiResponseDto.createOK( - mapSheetMngFileCheckerService.deleteDuplicate(filePath, fileName)); - } - - */ - - /* - @Operation(summary = "지정폴더(하위폴더포함) 파일목록 조회", description = "지정폴더(하위폴더포함) 파일목록 조회") - @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) - }) - @PostMapping("/file-all-list") - public ApiResponseDto getAllFiles(@RequestBody SrchFilesDepthDto srchDto) { - - return ApiResponseDto.createOK(mapSheetMngFileCheckerService.getFilesDepthAll(srchDto)); - } - - @Operation(summary = "영상데이터관리 > 영상파일 동기화", description = "영상파일 동기화") - @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) - }) - @PostMapping("/sync-process") - public ApiResponseDto syncProcess( - @RequestBody @Valid ImageryDto.searchReq searchReq) { - return ApiResponseDto.ok(mapSheetMngFileCheckerService.syncProcess(searchReq)); - } - - - */ -} diff --git a/src/main/java/com/kamco/cd/kamcoback/mapsheet/service/MapSheetMngFileCheckerService.java b/src/main/java/com/kamco/cd/kamcoback/mapsheet/service/MapSheetMngFileCheckerService.java deleted file mode 100644 index d1eeee31..00000000 --- a/src/main/java/com/kamco/cd/kamcoback/mapsheet/service/MapSheetMngFileCheckerService.java +++ /dev/null @@ -1,530 +0,0 @@ -package com.kamco.cd.kamcoback.mapsheet.service; - -import static java.lang.String.CASE_INSENSITIVE_ORDER; - -import com.kamco.cd.kamcoback.common.exception.DuplicateFileException; -import com.kamco.cd.kamcoback.common.exception.ValidationException; -import com.kamco.cd.kamcoback.common.utils.FIleChecker; -import com.kamco.cd.kamcoback.config.FileConfig; -import com.kamco.cd.kamcoback.mapsheet.dto.FileDto.FilesDto; -import com.kamco.cd.kamcoback.mapsheet.dto.FileDto.FoldersDto; -import com.kamco.cd.kamcoback.mapsheet.dto.FileDto.SrchFilesDepthDto; -import com.kamco.cd.kamcoback.mapsheet.dto.FileDto.SrchFilesDto; -import com.kamco.cd.kamcoback.mapsheet.dto.FileDto.SrchFoldersDto; -import com.kamco.cd.kamcoback.mapsheet.dto.ImageryDto; -import com.kamco.cd.kamcoback.postgres.core.MapSheetMngFileCheckerCoreService; -import com.kamco.cd.kamcoback.postgres.entity.MapSheetMngFileEntity; -import com.kamco.cd.kamcoback.postgres.repository.mapsheet.MapSheetMngFileRepository; -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.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.io.FilenameUtils; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; - -@Slf4j -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class MapSheetMngFileCheckerService { - - private final MapSheetMngFileCheckerCoreService mapSheetMngFileCheckerCoreService; - private final FileConfig fileConfig; - private final MapSheetMngFileRepository mapSheetMngFileRepository; - - // @Value("${mapsheet.upload.skipGdalValidation:false}") - // private boolean skipGdalValidation; - - public FoldersDto getFolderAll(SrchFoldersDto srchDto) { - - Path startPath = Paths.get(fileConfig.getRootSyncDir() + srchDto.getDirPath()); - String dirPath = fileConfig.getRootSyncDir() + srchDto.getDirPath(); - String sortType = "name desc"; - - List folderList = FIleChecker.getFolderAll(dirPath); - - int folderTotCnt = folderList.size(); - int folderErrTotCnt = - (int) - folderList.stream().filter(dto -> dto.getIsValid().toString().equals("false")).count(); - - return new FoldersDto(dirPath, folderTotCnt, folderErrTotCnt, folderList); - } - - public FilesDto getFilesAll(SrchFilesDto srchDto) { - - String dirPath = srchDto.getDirPath(); - int startPos = srchDto.getStartPos(); - int endPos = srchDto.getEndPos(); - - List files = - FIleChecker.getFilesFromAllDepth( - srchDto.getDirPath(), - "*", - srchDto.getExtension(), - 1, - srchDto.getSortType(), - startPos, - endPos); - - int fileListPos = 0; - int fileTotCnt = files.size(); - long fileTotSize = FIleChecker.getFileTotSize(files); - - return new FilesDto(dirPath, fileTotCnt, fileTotSize, files); - } - - public FilesDto getFilesDepthAll(SrchFilesDepthDto srchDto) { - - String dirPath = srchDto.getDirPath(); - int startPos = srchDto.getStartPos(); - int endPos = srchDto.getEndPos(); - - List files = - FIleChecker.getFilesFromAllDepth( - srchDto.getDirPath(), - "*", - srchDto.getExtension(), - srchDto.getMaxDepth(), - srchDto.getSortType(), - startPos, - endPos); - - int fileListPos = 0; - int fileTotCnt = files.size(); - long fileTotSize = FIleChecker.getFileTotSize(files); - - return new FilesDto(dirPath, fileTotCnt, fileTotSize, files); - } - - public Set 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 getFileComparator(String sortType) { - - // 파일 이름 비교 기본 Comparator (대소문자 무시) - Comparator nameComparator = - Comparator.comparing(path -> path.getFileName().toString(), CASE_INSENSITIVE_ORDER); - - Comparator 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; - } - } - - public ImageryDto.SyncReturn syncProcess(ImageryDto.searchReq searchReq) { - return mapSheetMngFileCheckerCoreService.syncProcess(searchReq); - } - - @Transactional - public String uploadFile(MultipartFile file, String targetPath, boolean overwrite, Long hstUid) { - try { - // 파일 유효성 검증 - if (file == null || file.isEmpty()) { - throw new ValidationException("업로드 파일이 비어있습니다."); - } - if (file.getOriginalFilename() == null || file.getOriginalFilename().isEmpty()) { - throw new ValidationException("파일명이 유효하지 않습니다."); - } - - Path path = Paths.get(targetPath); - - // targetPath가 존재하지 않으면 파일 경로로 가정하고 부모 디렉토리 생성 - if (!Files.exists(path)) { - // 경로가 확장자로 끝나면 파일로 간주 - if (targetPath.matches(".*\\.[a-zA-Z]{3,4}$")) { - if (path.getParent() != null) { - Files.createDirectories(path.getParent()); - } - } else { - // 확장자가 없으면 디렉토리로 간주 - Files.createDirectories(path); - path = path.resolve(file.getOriginalFilename()); - } - } else if (Files.isDirectory(path)) { - path = path.resolve(file.getOriginalFilename()); - } - - // 최종 파일의 부모 디렉토리 생성 - if (path.getParent() != null && !Files.exists(path.getParent())) { - Files.createDirectories(path.getParent()); - } - - String filename = path.getFileName().toString(); - String ext = FilenameUtils.getExtension(filename).toLowerCase(); - String baseName = FilenameUtils.getBaseName(filename); - Path tfwPath = - path.getParent() == null - ? Paths.get(baseName + ".tfw") - : path.getParent().resolve(baseName + ".tfw"); - Path tifPath = - path.getParent() == null - ? Paths.get(baseName + ".tif") - : path.getParent().resolve(baseName + ".tif"); - - // DB 중복 체크 - String parentPathStr = path.getParent() != null ? path.getParent().toString() : ""; - boolean dbExists = - mapSheetMngFileRepository.existsByFileNameAndFilePath(filename, parentPathStr); - // boolean fileExists = Files.exists(path); // 파일 시스템 존재 여부는 체크하지 않음 (DB 기준) - - // 이미 존재하는 경우 처리 (DB에만 있는 경우 체크) - if (!overwrite && dbExists) { - throw new DuplicateFileException("동일한 파일이 이미 존재합니다 (DB): " + path.getFileName()); - } - - // 덮어쓰기인 경우 기존 DB 데이터 삭제 (새로 저장하기 위함) - if (overwrite && dbExists) { - mapSheetMngFileRepository.deleteByFileNameAndFilePath(filename, parentPathStr); - } - - // 업로드 파일 저장(덮어쓰기 허용 시 replace) - file.transferTo(path.toFile()); - - if ("tfw".equals(ext)) { - // TFW 검증 - boolean tfwOk = FIleChecker.checkTfw(path.toString()); - if (!tfwOk) { - Files.deleteIfExists(path); - throw new ValidationException( - "유효하지 않은 TFW 파일입니다 (6줄 숫자 형식 검증 실패): " + path.getFileName()); - } - // 안내: 같은 베이스의 TIF가 없으면 추후 TIF 업로드 필요 - if (!Files.exists(tifPath)) { - // DB 메타 저장은 진행 (향후 쌍 검증 위해) - saveUploadMeta(path, hstUid); - return "TFW 업로드 성공 (매칭되는 TIF가 아직 없습니다)."; - } - // TIF가 존재하면 쌍 요건 충족 - saveUploadMeta(path, hstUid); - return "TFW 업로드 성공"; - } - - if ("tif".equals(ext) || "tiff".equals(ext)) { - // GDAL 검증 (항상 수행) - boolean isValidTif = FIleChecker.cmmndGdalInfo(path.toString()); - if (!isValidTif) { - Files.deleteIfExists(path); - throw new ValidationException("유효하지 않은 TIF 파일입니다 (GDAL 검증 실패): " + path.getFileName()); - } - - // TFW 존재/검증 - if (!Files.exists(tfwPath)) { - Files.deleteIfExists(path); - throw new ValidationException("TFW 파일이 존재하지 않습니다: " + tfwPath.getFileName()); - } - boolean tfwOk = FIleChecker.checkTfw(tfwPath.toString()); - if (!tfwOk) { - Files.deleteIfExists(path); - throw new ValidationException( - "유효하지 않은 TFW 파일입니다 (6줄 숫자 형식 검증 실패): " + tfwPath.getFileName()); - } - saveUploadMeta(path, hstUid); - return "TIF 업로드 성공"; - } - - // 기타 확장자: 저장만 하고 메타 기록 - saveUploadMeta(path, hstUid); - - return "업로드 성공"; - } catch (ValidationException | DuplicateFileException e) { - // 비즈니스 예외는 그대로 던짐 - throw e; - } catch (IOException e) { - throw new IllegalArgumentException("파일 I/O 처리 실패: " + e.getMessage(), e); - } catch (Exception e) { - throw new IllegalArgumentException("파일 업로드 처리 중 오류 발생: " + e.getMessage(), e); - } - } - - @Transactional - public String uploadPair( - MultipartFile tfwFile, - MultipartFile tifFile, - String targetPath, - boolean overwrite, - Long hstUid) { - try { - log.info( - "uploadPair 시작 - targetPath: {}, overwrite: {}, hstUid: {}", - targetPath, - overwrite, - hstUid); - - // 파일 유효성 검증 - if (tfwFile == null || tfwFile.isEmpty()) { - throw new ValidationException("TFW 파일이 비어있습니다."); - } - if (tifFile == null || tifFile.isEmpty()) { - throw new ValidationException("TIF 파일이 비어있습니다."); - } - if (tfwFile.getOriginalFilename() == null || tfwFile.getOriginalFilename().isEmpty()) { - throw new ValidationException("TFW 파일명이 유효하지 않습니다."); - } - if (tifFile.getOriginalFilename() == null || tifFile.getOriginalFilename().isEmpty()) { - throw new ValidationException("TIF 파일명이 유효하지 않습니다."); - } - - log.info( - "파일명 - TFW: {}, TIF: {}", tfwFile.getOriginalFilename(), tifFile.getOriginalFilename()); - - Path basePath = Paths.get(targetPath); - - // targetPath가 존재하지 않으면 디렉토리로 생성 - if (!Files.exists(basePath)) { - log.info("대상 경로가 존재하지 않아 디렉토리 생성: {}", basePath); - Files.createDirectories(basePath); - } - - // 파일인 경우 부모 디렉토리를 basePath로 사용 - if (Files.isRegularFile(basePath)) { - log.info("대상 경로가 파일이므로 부모 디렉토리 사용"); - basePath = basePath.getParent(); - } - - if (Files.isDirectory(basePath)) { - log.info("디렉토리 확인됨: {}", basePath); - // 디렉토리인 경우 파일명 기준으로 경로 생성 - Path tfwPath = basePath.resolve(tfwFile.getOriginalFilename()); - Path tifPath = basePath.resolve(tifFile.getOriginalFilename()); - // 동일 베이스명 확인 - String tfwBase = FilenameUtils.getBaseName(tfwPath.getFileName().toString()); - String tifBase = FilenameUtils.getBaseName(tifPath.getFileName().toString()); - if (!tfwBase.equalsIgnoreCase(tifBase)) { - throw new ValidationException("TFW/TIF 파일명이 동일한 베이스가 아닙니다."); - } - // 디렉토리는 이미 생성되었으므로 추가 생성 불필요 - // if (tfwPath.getParent() != null) Files.createDirectories(tfwPath.getParent()); - // if (tifPath.getParent() != null) Files.createDirectories(tifPath.getParent()); - - // DB 중복 체크 및 overwrite 처리 (각 파일별) - String parentPathStr = basePath.toString(); - String tfwName = tfwPath.getFileName().toString(); - String tifName = tifPath.getFileName().toString(); - boolean tfwDbExists = - mapSheetMngFileRepository.existsByFileNameAndFilePath(tfwName, parentPathStr); - boolean tifDbExists = - mapSheetMngFileRepository.existsByFileNameAndFilePath(tifName, parentPathStr); - if (!overwrite && (tfwDbExists || tifDbExists)) { - throw new DuplicateFileException("동일한 파일이 이미 존재합니다 (DB): " + tfwName + ", " + tifName); - } - if (overwrite) { - if (tfwDbExists) - mapSheetMngFileRepository.deleteByFileNameAndFilePath(tfwName, parentPathStr); - if (tifDbExists) - mapSheetMngFileRepository.deleteByFileNameAndFilePath(tifName, parentPathStr); - } - - // 파일 저장 - log.info("파일 저장 시작 - TFW: {}, TIF: {}", tfwPath, tifPath); - tfwFile.transferTo(tfwPath.toFile()); - tifFile.transferTo(tifPath.toFile()); - log.info("파일 저장 완료"); - - // 검증 - log.info("TFW 파일 검증 시작: {}", tfwPath); - boolean tfwOk = FIleChecker.checkTfw(tfwPath.toString()); - if (!tfwOk) { - log.warn("TFW 파일 검증 실패: {}", tfwName); - Files.deleteIfExists(tfwPath); - Files.deleteIfExists(tifPath); - throw new ValidationException("유효하지 않은 TFW 파일입니다 (6줄 숫자 형식 검증 실패): " + tfwName); - } - log.info("TFW 파일 검증 성공"); - - log.info("TIF 파일 검증 시작: {}", tifPath); - boolean isValidTif = FIleChecker.cmmndGdalInfo(tifPath.toString()); - if (!isValidTif) { - log.warn("TIF 파일 검증 실패: {}", tifName); - Files.deleteIfExists(tfwPath); - Files.deleteIfExists(tifPath); - throw new ValidationException("유효하지 않은 TIF 파일입니다 (GDAL 검증 실패): " + tifName); - } - log.info("TIF 파일 검증 성공"); - - // 메타 저장 (두 파일 각각 저장) - log.info("메타 데이터 저장 시작"); - saveUploadMeta(tfwPath, hstUid); - saveUploadMeta(tifPath, hstUid); - log.info("메타 데이터 저장 완료"); - return "TFW/TIF 페어 업로드 성공"; - } else { - throw new ValidationException("targetPath는 디렉토리여야 합니다."); - } - } catch (ValidationException | DuplicateFileException e) { - // 비즈니스 예외는 그대로 던짐 - log.warn("업로드 비즈니스 예외 발생: {}", e.getMessage()); - throw e; - } catch (IOException e) { - log.error("파일 I/O 처리 실패: {}", e.getMessage(), e); - throw new IllegalArgumentException("파일 I/O 처리 실패: " + e.getMessage(), e); - } catch (Exception e) { - log.error("파일 업로드 처리 중 예상치 못한 오류 발생: {}", e.getMessage(), e); - throw new IllegalArgumentException("파일 업로드 처리 중 오류 발생: " + e.getMessage(), e); - } - } - - private void saveUploadMeta(Path savedPath, Long hstUid) { - String fullPath = savedPath.toAbsolutePath().toString(); - String fileName = savedPath.getFileName().toString(); - String ext = FilenameUtils.getExtension(fileName); - - MapSheetMngFileEntity entity = new MapSheetMngFileEntity(); - - if (hstUid != null) { - // 히스토리에서 메타 가져오기 - var hstOpt = mapSheetMngFileCheckerCoreService.findHstByUid(hstUid); - hstOpt.ifPresent( - hst -> { - entity.setHstUid(hst.getHstUid()); - entity.setMngYyyy(hst.getMngYyyy()); - entity.setMapSheetNum(hst.getMapSheetNum()); - entity.setRefMapSheetNum(hst.getRefMapSheetNum()); - }); - } else { - // 기존 추정 로직 유지 - Integer mngYyyy = extractYearFromPath(fullPath); - String mapSheetNum = extractMapSheetNumFromFileName(fileName); - String refMapSheetNum = null; - if (mapSheetNum != null && !mapSheetNum.isEmpty()) { - try { - long num = Long.parseLong(mapSheetNum); - refMapSheetNum = String.valueOf(num / 1000); - } catch (NumberFormatException ignored) { - } - } - entity.setMngYyyy(mngYyyy); - entity.setMapSheetNum(mapSheetNum); - entity.setRefMapSheetNum(refMapSheetNum); - } - - entity.setFilePath(savedPath.getParent() != null ? savedPath.getParent().toString() : ""); - entity.setFileName(fileName); - entity.setFileExt(ext); - - // 파일 크기 설정 - try { - long size = Files.size(savedPath); - entity.setFileSize(size); - } catch (IOException e) { - entity.setFileSize(0L); - } - - mapSheetMngFileRepository.save(entity); - } - - private Integer extractYearFromPath(String fullPath) { - // 경로에서 4자리 연도를 찾아 가장 근접한 값을 사용 - // 예시 경로: /Users/.../original-images/2022/2022_25cm/1/34602 - String[] parts = fullPath.split("/"); - for (String p : parts) { - if (p.matches("\\d{4}")) { - try { - return Integer.parseInt(p); - } catch (NumberFormatException ignored) { - } - } - } - return null; - } - - private String extractMapSheetNumFromFileName(String fileName) { - // 파일명에서 연속된 숫자를 최대한 찾아 사용 (예: 34602027.tif -> 34602027) - String base = FilenameUtils.getBaseName(fileName); - String digits = base.replaceAll("[^0-9]", ""); - if (!digits.isEmpty()) { - return digits; - } - return null; - } - - @Transactional - public Boolean deleteFile(String filePath) { - try { - Path path = Paths.get(filePath); - return Files.deleteIfExists(path); - } catch (IOException e) { - throw new RuntimeException("파일 삭제 실패: " + e.getMessage()); - } - } - - @Transactional(readOnly = true) - public List findRecentFiles(int limit) { - // 간단히 전체를 불러 정렬/제한 (운영에선 Page 요청으로 변경 권장) - List all = new ArrayList<>(); - mapSheetMngFileRepository.findAll().forEach(all::add); - all.sort( - (a, b) -> { - // fileUid 기준 내림차순 - long av = a.getFileUid() == null ? 0L : a.getFileUid(); - long bv = b.getFileUid() == null ? 0L : b.getFileUid(); - return Long.compare(bv, av); - }); - if (all.size() > limit) { - return all.subList(0, limit); - } - return all; - } - - @Transactional - public String deleteDuplicate(String filePath, String fileName) { - try { - Path path = Paths.get(filePath, fileName); - boolean deleted = Files.deleteIfExists(path); - // DB에서도 삭제 - mapSheetMngFileRepository.deleteByFileNameAndFilePath(fileName, filePath); - return deleted ? "파일 및 DB 레코드 삭제 완료" : "DB 레코드 삭제 완료 (파일 미존재)"; - } catch (IOException e) { - throw new RuntimeException("중복 파일 삭제 실패: " + e.getMessage()); - } - } -} diff --git a/src/main/java/com/kamco/cd/kamcoback/mapsheet/service/MapSheetMngService.java b/src/main/java/com/kamco/cd/kamcoback/mapsheet/service/MapSheetMngService.java index c93a2382..d5f2c031 100644 --- a/src/main/java/com/kamco/cd/kamcoback/mapsheet/service/MapSheetMngService.java +++ b/src/main/java/com/kamco/cd/kamcoback/mapsheet/service/MapSheetMngService.java @@ -1,7 +1,10 @@ package com.kamco.cd.kamcoback.mapsheet.service; import com.kamco.cd.kamcoback.common.utils.FIleChecker; -import com.kamco.cd.kamcoback.config.FileConfig; +import com.kamco.cd.kamcoback.mapsheet.dto.FileDto.FilesDto; +import com.kamco.cd.kamcoback.mapsheet.dto.FileDto.FoldersDto; +import com.kamco.cd.kamcoback.mapsheet.dto.FileDto.SrchFilesDto; +import com.kamco.cd.kamcoback.mapsheet.dto.FileDto.SrchFoldersDto; import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto; import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.AddReq; import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.DmlReturn; @@ -31,8 +34,6 @@ import org.springframework.web.multipart.MultipartFile; public class MapSheetMngService { private final MapSheetMngCoreService mapSheetMngCoreService; - private final FileConfig fileConfig; - @Value("${file.sync-root-dir}") private String syncRootDir; @@ -113,37 +114,16 @@ public class MapSheetMngService { // 중복체크 if( !overwrite ) { - - int tfwCnt = FIleChecker.getFileCountFromAllDepth(targetYearDir, tfwFile.getOriginalFilename(), "tfw"); - int tifCnt = FIleChecker.getFileCountFromAllDepth(targetYearDir, tfwFile.getOriginalFilename(), "tfw"); - - if (tfwCnt > 0 || tifCnt > 0) { - String tfwtifMsg = ""; - if (tfwCnt > 0) - tfwtifMsg = tfwFile.getOriginalFilename(); - if (tifCnt > 0) { - if (tfwCnt > 0) - tfwtifMsg = "," + tifFile.getOriginalFilename(); - else - tfwtifMsg = tifFile.getOriginalFilename(); - } - return new DmlReturn("duplicate", tfwtifMsg); - } + dmlReturn = this.duplicateFile(errDto.getMngYyyy(), tfwFile.getOriginalFilename(), tifFile.getOriginalFilename()); + if( dmlReturn.getFlag().equals("duplicate") )return dmlReturn; } - File directory = new File(tmpPath); + //멀티파트 파일 tmp폴더 저장(파일형식 체크를 위해) String tfwTmpPath = tmpPath + tfwFile.getOriginalFilename(); - Path tfwTmpSavePath = Paths.get(tfwTmpPath); String tifTmpPath = tmpPath + tifFile.getOriginalFilename(); - Path tifTmpSavePath = Paths.get(tifTmpPath); - boolean fileUpload = true; - try { - tfwFile.transferTo(tfwTmpSavePath); - tifFile.transferTo(tifTmpSavePath); - } catch (IOException e) { - // throw new RuntimeException(e); - return new DmlReturn("fail", "UPLOAD ERROR"); - } + + if(!FIleChecker.multipartSaveTo(tfwFile, tfwTmpPath))return new DmlReturn("fail", "UPLOAD ERROR"); + if(!FIleChecker.multipartSaveTo(tifFile, tifTmpPath))return new DmlReturn("fail", "UPLOAD ERROR"); if (!FIleChecker.cmmndGdalInfo(tifTmpPath)) return new DmlReturn("fail", "TIF TYPE ERROR"); if (!FIleChecker.checkTfw(tfwTmpPath)) return new DmlReturn("fail", "TFW TYPE ERROR"); @@ -156,6 +136,9 @@ public class MapSheetMngService { break; } + + Path tfwTmpSavePath = Paths.get(tfwTmpPath); + Path tifTmpSavePath = Paths.get(tifTmpPath); Path tfwTargetPath = null; Path tifTargetPath = null; @@ -214,16 +197,6 @@ public class MapSheetMngService { return new DmlReturn("success", "파일 업로드 완료되었습니다."); } - public DmlReturn validationFile(MultipartFile tfwFile, MultipartFile tifFile) - { - if( !FIleChecker.validationMultipart(tfwFile) )return new DmlReturn("fail", "TFW SIZE 오류"); - else if( !FIleChecker.validationMultipart(tifFile) )return new DmlReturn("fail", "TFW SIZE 오류"); - else if (!FIleChecker.checkExtensions(tfwFile.getOriginalFilename(), "tfw"))return new DmlReturn("fail", "TFW FILENAME ERROR"); - else if (!FIleChecker.checkExtensions(tifFile.getOriginalFilename(), "tif"))return new DmlReturn("fail", "TIF FILENAME ERROR"); - - return new DmlReturn("success", "파일체크"); - } - public List findHstUidToMapSheetFileList(Long hstUid) { return mapSheetMngCoreService.findHstUidToMapSheetFileList(hstUid); } @@ -258,4 +231,80 @@ public class MapSheetMngService { return new DmlReturn("success", fileUids.size() + "개 파일이 삭제되었습니다."); } + + + + public DmlReturn validationFile(MultipartFile tfwFile, MultipartFile tifFile) + { + if( !FIleChecker.validationMultipart(tfwFile) )return new DmlReturn("fail", "TFW SIZE 오류"); + else if( !FIleChecker.validationMultipart(tifFile) )return new DmlReturn("fail", "TFW SIZE 오류"); + else if (!FIleChecker.checkExtensions(tfwFile.getOriginalFilename(), "tfw"))return new DmlReturn("fail", "TFW FILENAME ERROR"); + else if (!FIleChecker.checkExtensions(tifFile.getOriginalFilename(), "tif"))return new DmlReturn("fail", "TIF FILENAME ERROR"); + + return new DmlReturn("success", "파일체크"); + } + + public DmlReturn duplicateFile(int mngYyyy, String tfwFileName, String tifFileName) + { + int tfwCnt = mapSheetMngCoreService.findByYearFileNameFileCount(mngYyyy, tfwFileName); + int tifCnt = mapSheetMngCoreService.findByYearFileNameFileCount(mngYyyy, tifFileName); + + System.out.println("tfwCnt ==" + tfwCnt); + System.out.println("tifCnt ==" + tifCnt); + + if (tfwCnt > 0 || tifCnt > 0) { + String resMsg = ""; + if (tfwCnt > 0) + resMsg = tfwFileName; + + if (tifCnt > 0) { + if (tfwCnt > 0) + resMsg = resMsg + "," + tifFileName; + else + resMsg = tifFileName; + } + return new DmlReturn("duplicate", resMsg); + } + + return new DmlReturn("success", "파일체크"); + } + + public FoldersDto getFolderAll(SrchFoldersDto srchDto) { + + Path startPath = Paths.get(syncRootDir + srchDto.getDirPath()); + String dirPath = syncRootDir + srchDto.getDirPath(); + String sortType = "name desc"; + + List folderList = FIleChecker.getFolderAll(dirPath); + + int folderTotCnt = folderList.size(); + int folderErrTotCnt = + (int) + folderList.stream().filter(dto -> dto.getIsValid().toString().equals("false")).count(); + + return new FoldersDto(dirPath, folderTotCnt, folderErrTotCnt, folderList); + } + + public FilesDto getFilesAll(SrchFilesDto srchDto) { + + String dirPath = srchDto.getDirPath(); + int startPos = srchDto.getStartPos(); + int endPos = srchDto.getEndPos(); + + List files = + FIleChecker.getFilesFromAllDepth( + srchDto.getDirPath(), + "*", + srchDto.getExtension(), + 1, + srchDto.getSortType(), + startPos, + endPos); + + int fileListPos = 0; + int fileTotCnt = files.size(); + long fileTotSize = FIleChecker.getFileTotSize(files); + + return new FilesDto(dirPath, fileTotCnt, fileTotSize, files); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/core/MapSheetMngCoreService.java b/src/main/java/com/kamco/cd/kamcoback/postgres/core/MapSheetMngCoreService.java index 047b5231..4fe0807b 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/core/MapSheetMngCoreService.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/core/MapSheetMngCoreService.java @@ -52,6 +52,10 @@ public class MapSheetMngCoreService { mapSheetMngRepository.deleteByHstUidMngFile(hstUid); } + public int findByYearFileNameFileCount(int mngYyyy, String fileName) { + return mapSheetMngRepository.findByYearFileNameFileCount(mngYyyy, fileName); + } + public MapSheetMngDto.DmlReturn mngFileSave(@Valid MapSheetMngDto.MngFileAddReq addReq) { mapSheetMngRepository.mngFileSave(addReq); diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryCustom.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryCustom.java index b26508da..c1df8f08 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryCustom.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryCustom.java @@ -55,4 +55,6 @@ public interface MapSheetMngRepositoryCustom { MapSheetMngDto.MngFilesDto findIdToMapSheetFile(Long fileUid); void updateHstFileSizes(Long hstUid, long tifSizeBytes, long tfwSizeBytes, long totalSizeBytes); + + int findByYearFileNameFileCount(int mngYyyy, String fileName); } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryImpl.java index 76b53ae1..428350f4 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryImpl.java @@ -676,6 +676,18 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport .execute(); } + @Override + public int findByYearFileNameFileCount(int mngYyyy, String fileName){ + Long execCount = queryFactory + .select(mapSheetMngFileEntity.count()) + .from(mapSheetMngFileEntity) + .where(mapSheetMngFileEntity.mngYyyy.eq(mngYyyy) + .and(mapSheetMngFileEntity.fileName.eq(fileName))) + .fetchOne(); + + return Math.toIntExact(execCount); + } + @Override public void mngFileSave(@Valid MapSheetMngDto.MngFileAddReq addReq) { long fileCount = diff --git a/src/main/java/com/kamco/cd/kamcoback/scheduler/MapSheetMngFileJobController.java b/src/main/java/com/kamco/cd/kamcoback/scheduler/MapSheetMngFileJobController.java index 2c6110e7..95fe378e 100644 --- a/src/main/java/com/kamco/cd/kamcoback/scheduler/MapSheetMngFileJobController.java +++ b/src/main/java/com/kamco/cd/kamcoback/scheduler/MapSheetMngFileJobController.java @@ -25,6 +25,7 @@ public class MapSheetMngFileJobController { mapSheetMngFileJobService.checkMapSheetFileProcess(0, mngSyncPageSize); } + @Scheduled(fixedDelay = 5000) public void mngFileSyncJob01() { if (!isSchedulerEnabled) return; @@ -97,6 +98,7 @@ public class MapSheetMngFileJobController { mapSheetMngFileJobService.checkMapSheetFileProcess(9, mngSyncPageSize); } + // 3. 외부에서 플래그를 변경할 수 있는 Setter 메서드 public void setSchedulerEnabled(boolean enabled) { this.isSchedulerEnabled = enabled; diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index d64d7d1d..91e39d0f 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -66,6 +66,6 @@ management: file: - sync-root-dir: /app/original-images/ + sync-root-dir: D:/app/original-images/ sync-tmp-dir: ${file.sync-root-dir}/tmp sync-file-extention: tfw,tif