싱크경로 yml에 설정하고 FileConfig 삭제

영상관리 수정
This commit is contained in:
Moon
2025-12-26 08:50:05 +09:00
parent 8bed8f5f38
commit 5707a31309
11 changed files with 171 additions and 736 deletions

View File

@@ -478,6 +478,21 @@ public class FIleChecker {
return fileTotSize; 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) public static boolean validationMultipart(MultipartFile mfile)
{ {
// 파일 유효성 검증 // 파일 유효성 검증

View File

@@ -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";
}

View File

@@ -3,6 +3,10 @@ package com.kamco.cd.kamcoback.mapsheet;
import com.kamco.cd.kamcoback.code.dto.CommonCodeDto; import com.kamco.cd.kamcoback.code.dto.CommonCodeDto;
import com.kamco.cd.kamcoback.code.service.CommonCodeService; import com.kamco.cd.kamcoback.code.service.CommonCodeService;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto; 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.dto.MapSheetMngDto;
import com.kamco.cd.kamcoback.mapsheet.service.MapSheetMngService; import com.kamco.cd.kamcoback.mapsheet.service.MapSheetMngService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -222,4 +226,46 @@ public class MapSheetMngApiController {
@RequestParam @Valid List<Long> fileUids) { @RequestParam @Valid List<Long> fileUids) {
return ApiResponseDto.ok(mapSheetMngService.deleteByFileUidMngFile(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<FoldersDto> 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<FilesDto> getFiles(@RequestBody SrchFilesDto srchDto) {
return ApiResponseDto.createOK(mapSheetMngService.getFilesAll(srchDto));
}
} }

View File

@@ -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<FoldersDto> 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<FilesDto> 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<String> 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<Boolean> deleteFile(@RequestBody SrchFoldersDto dto) {
return ApiResponseDto.createOK(mapSheetMngFileCheckerService.deleteFile(dto.getDirPath()));
}
@Operation(summary = "중복 파일 삭제", description = "중복 데이터 발견 시 기존 데이터를 삭제")
@PostMapping(value = "/delete-file")
public ApiResponseDto<String> 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<FilesDto> 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<ImageryDto.SyncReturn> syncProcess(
@RequestBody @Valid ImageryDto.searchReq searchReq) {
return ApiResponseDto.ok(mapSheetMngFileCheckerService.syncProcess(searchReq));
}
*/
}

View File

@@ -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<FIleChecker.Folder> 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<FIleChecker.Basic> 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<FIleChecker.Basic> 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<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;
}
}
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<MapSheetMngFileEntity> findRecentFiles(int limit) {
// 간단히 전체를 불러 정렬/제한 (운영에선 Page 요청으로 변경 권장)
List<MapSheetMngFileEntity> 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());
}
}
}

View File

@@ -1,7 +1,10 @@
package com.kamco.cd.kamcoback.mapsheet.service; package com.kamco.cd.kamcoback.mapsheet.service;
import com.kamco.cd.kamcoback.common.utils.FIleChecker; 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;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.AddReq; import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.AddReq;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.DmlReturn; import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.DmlReturn;
@@ -31,8 +34,6 @@ import org.springframework.web.multipart.MultipartFile;
public class MapSheetMngService { public class MapSheetMngService {
private final MapSheetMngCoreService mapSheetMngCoreService; private final MapSheetMngCoreService mapSheetMngCoreService;
private final FileConfig fileConfig;
@Value("${file.sync-root-dir}") @Value("${file.sync-root-dir}")
private String syncRootDir; private String syncRootDir;
@@ -113,37 +114,16 @@ public class MapSheetMngService {
// 중복체크 // 중복체크
if( !overwrite ) { if( !overwrite ) {
dmlReturn = this.duplicateFile(errDto.getMngYyyy(), tfwFile.getOriginalFilename(), tifFile.getOriginalFilename());
int tfwCnt = FIleChecker.getFileCountFromAllDepth(targetYearDir, tfwFile.getOriginalFilename(), "tfw"); if( dmlReturn.getFlag().equals("duplicate") )return dmlReturn;
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);
}
} }
File directory = new File(tmpPath); //멀티파트 파일 tmp폴더 저장(파일형식 체크를 위해)
String tfwTmpPath = tmpPath + tfwFile.getOriginalFilename(); String tfwTmpPath = tmpPath + tfwFile.getOriginalFilename();
Path tfwTmpSavePath = Paths.get(tfwTmpPath);
String tifTmpPath = tmpPath + tifFile.getOriginalFilename(); String tifTmpPath = tmpPath + tifFile.getOriginalFilename();
Path tifTmpSavePath = Paths.get(tifTmpPath);
boolean fileUpload = true; if(!FIleChecker.multipartSaveTo(tfwFile, tfwTmpPath))return new DmlReturn("fail", "UPLOAD ERROR");
try { if(!FIleChecker.multipartSaveTo(tifFile, tifTmpPath))return new DmlReturn("fail", "UPLOAD ERROR");
tfwFile.transferTo(tfwTmpSavePath);
tifFile.transferTo(tifTmpSavePath);
} catch (IOException e) {
// throw new RuntimeException(e);
return new DmlReturn("fail", "UPLOAD ERROR");
}
if (!FIleChecker.cmmndGdalInfo(tifTmpPath)) return new DmlReturn("fail", "TIF TYPE ERROR"); if (!FIleChecker.cmmndGdalInfo(tifTmpPath)) return new DmlReturn("fail", "TIF TYPE ERROR");
if (!FIleChecker.checkTfw(tfwTmpPath)) return new DmlReturn("fail", "TFW TYPE ERROR"); if (!FIleChecker.checkTfw(tfwTmpPath)) return new DmlReturn("fail", "TFW TYPE ERROR");
@@ -156,6 +136,9 @@ public class MapSheetMngService {
break; break;
} }
Path tfwTmpSavePath = Paths.get(tfwTmpPath);
Path tifTmpSavePath = Paths.get(tifTmpPath);
Path tfwTargetPath = null; Path tfwTargetPath = null;
Path tifTargetPath = null; Path tifTargetPath = null;
@@ -214,16 +197,6 @@ public class MapSheetMngService {
return new DmlReturn("success", "파일 업로드 완료되었습니다."); 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<MngFilesDto> findHstUidToMapSheetFileList(Long hstUid) { public List<MngFilesDto> findHstUidToMapSheetFileList(Long hstUid) {
return mapSheetMngCoreService.findHstUidToMapSheetFileList(hstUid); return mapSheetMngCoreService.findHstUidToMapSheetFileList(hstUid);
} }
@@ -258,4 +231,80 @@ public class MapSheetMngService {
return new DmlReturn("success", fileUids.size() + "개 파일이 삭제되었습니다."); 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<FIleChecker.Folder> 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<FIleChecker.Basic> 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);
}
} }

View File

@@ -52,6 +52,10 @@ public class MapSheetMngCoreService {
mapSheetMngRepository.deleteByHstUidMngFile(hstUid); mapSheetMngRepository.deleteByHstUidMngFile(hstUid);
} }
public int findByYearFileNameFileCount(int mngYyyy, String fileName) {
return mapSheetMngRepository.findByYearFileNameFileCount(mngYyyy, fileName);
}
public MapSheetMngDto.DmlReturn mngFileSave(@Valid MapSheetMngDto.MngFileAddReq addReq) { public MapSheetMngDto.DmlReturn mngFileSave(@Valid MapSheetMngDto.MngFileAddReq addReq) {
mapSheetMngRepository.mngFileSave(addReq); mapSheetMngRepository.mngFileSave(addReq);

View File

@@ -55,4 +55,6 @@ public interface MapSheetMngRepositoryCustom {
MapSheetMngDto.MngFilesDto findIdToMapSheetFile(Long fileUid); MapSheetMngDto.MngFilesDto findIdToMapSheetFile(Long fileUid);
void updateHstFileSizes(Long hstUid, long tifSizeBytes, long tfwSizeBytes, long totalSizeBytes); void updateHstFileSizes(Long hstUid, long tifSizeBytes, long tfwSizeBytes, long totalSizeBytes);
int findByYearFileNameFileCount(int mngYyyy, String fileName);
} }

View File

@@ -676,6 +676,18 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
.execute(); .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 @Override
public void mngFileSave(@Valid MapSheetMngDto.MngFileAddReq addReq) { public void mngFileSave(@Valid MapSheetMngDto.MngFileAddReq addReq) {
long fileCount = long fileCount =

View File

@@ -25,6 +25,7 @@ public class MapSheetMngFileJobController {
mapSheetMngFileJobService.checkMapSheetFileProcess(0, mngSyncPageSize); mapSheetMngFileJobService.checkMapSheetFileProcess(0, mngSyncPageSize);
} }
@Scheduled(fixedDelay = 5000) @Scheduled(fixedDelay = 5000)
public void mngFileSyncJob01() { public void mngFileSyncJob01() {
if (!isSchedulerEnabled) return; if (!isSchedulerEnabled) return;
@@ -97,6 +98,7 @@ public class MapSheetMngFileJobController {
mapSheetMngFileJobService.checkMapSheetFileProcess(9, mngSyncPageSize); mapSheetMngFileJobService.checkMapSheetFileProcess(9, mngSyncPageSize);
} }
// 3. 외부에서 플래그를 변경할 수 있는 Setter 메서드 // 3. 외부에서 플래그를 변경할 수 있는 Setter 메서드
public void setSchedulerEnabled(boolean enabled) { public void setSchedulerEnabled(boolean enabled) {
this.isSchedulerEnabled = enabled; this.isSchedulerEnabled = enabled;

View File

@@ -66,6 +66,6 @@ management:
file: file:
sync-root-dir: /app/original-images/ sync-root-dir: D:/app/original-images/
sync-tmp-dir: ${file.sync-root-dir}/tmp sync-tmp-dir: ${file.sync-root-dir}/tmp
sync-file-extention: tfw,tif sync-file-extention: tfw,tif