diff --git a/src/main/java/com/kamco/cd/kamcoback/code/dto/CommonCodeDto.java b/src/main/java/com/kamco/cd/kamcoback/code/dto/CommonCodeDto.java index e735108a..ea1b34de 100644 --- a/src/main/java/com/kamco/cd/kamcoback/code/dto/CommonCodeDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/code/dto/CommonCodeDto.java @@ -155,7 +155,7 @@ public class CommonCodeDto { String[] sortParams = sort.split(","); String property = sortParams[0]; Sort.Direction direction = - sortParams.length > 1 ? Sort.Direction.fromString(sortParams[1]) : Sort.Direction.ASC; + sortParams.length > 1 ? Sort.Direction.fromString(sortParams[1]) : Sort.Direction.ASC; return PageRequest.of(page, size, Sort.by(direction, property)); } return PageRequest.of(page, size); diff --git a/src/main/java/com/kamco/cd/kamcoback/config/GlobalExceptionHandler.java b/src/main/java/com/kamco/cd/kamcoback/config/GlobalExceptionHandler.java index 6c0a9d90..3d5105f8 100644 --- a/src/main/java/com/kamco/cd/kamcoback/config/GlobalExceptionHandler.java +++ b/src/main/java/com/kamco/cd/kamcoback/config/GlobalExceptionHandler.java @@ -161,8 +161,6 @@ public class GlobalExceptionHandler { errorLog.getId()); } - - @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY) @ExceptionHandler(DataIntegrityViolationException.class) public ApiResponseDto handlerDataIntegrityViolationException( diff --git a/src/main/java/com/kamco/cd/kamcoback/mapsheet/MapSheetMngFileCheckerApiController.java b/src/main/java/com/kamco/cd/kamcoback/mapsheet/MapSheetMngFileCheckerApiController.java index eca12743..854f7a04 100644 --- a/src/main/java/com/kamco/cd/kamcoback/mapsheet/MapSheetMngFileCheckerApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/mapsheet/MapSheetMngFileCheckerApiController.java @@ -76,8 +76,24 @@ public class MapSheetMngFileCheckerApiController { public ApiResponseDto uploadFile( @RequestPart("file") MultipartFile file, @RequestParam("targetPath") String targetPath, - @RequestParam(value = "overwrite", required = false, defaultValue = "false") boolean overwrite) { - return ApiResponseDto.createOK(mapSheetMngFileCheckerService.uploadFile(file, targetPath, overwrite)); + @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 = "TFW/TIF 두 파일을 쌍으로 업로드 및 검증") + @PostMapping(value = "/upload-pair", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ApiResponseDto uploadPair( + @RequestPart("tfw") MultipartFile tfwFile, + @RequestPart("tif") MultipartFile tifFile, + @RequestParam("targetPath") String targetPath, + @RequestParam(value = "overwrite", required = false, defaultValue = "false") + boolean overwrite, + @RequestParam(value = "hstUid", required = false) Long hstUid) { + return ApiResponseDto.createOK( + mapSheetMngFileCheckerService.uploadPair(tfwFile, tifFile, targetPath, overwrite, hstUid)); } @Operation(summary = "파일 삭제", description = "중복 파일 등 파일 삭제") @@ -86,6 +102,14 @@ public class MapSheetMngFileCheckerApiController { 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( 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 index 55b24613..0659c8df 100644 --- a/src/main/java/com/kamco/cd/kamcoback/mapsheet/service/MapSheetMngFileCheckerService.java +++ b/src/main/java/com/kamco/cd/kamcoback/mapsheet/service/MapSheetMngFileCheckerService.java @@ -316,7 +316,7 @@ public class MapSheetMngFileCheckerService { } @Transactional - public String uploadFile(MultipartFile file, String targetPath, boolean overwrite) { + public String uploadFile(MultipartFile file, String targetPath, boolean overwrite, Long hstUid) { try { Path path = Paths.get(targetPath); if (Files.isDirectory(path)) { @@ -368,11 +368,11 @@ public class MapSheetMngFileCheckerService { // 안내: 같은 베이스의 TIF가 없으면 추후 TIF 업로드 필요 if (!Files.exists(tifPath)) { // DB 메타 저장은 진행 (향후 쌍 검증 위해) - saveUploadMeta(path); + saveUploadMeta(path, hstUid); return "TFW 업로드 성공 (매칭되는 TIF가 아직 없습니다)."; } // TIF가 존재하면 쌍 요건 충족 - saveUploadMeta(path); + saveUploadMeta(path, hstUid); return "TFW 업로드 성공"; } @@ -395,12 +395,12 @@ public class MapSheetMngFileCheckerService { throw new ValidationException( "유효하지 않은 TFW 파일입니다 (6줄 숫자 형식 검증 실패): " + tfwPath.getFileName()); } - saveUploadMeta(path); + saveUploadMeta(path, hstUid); return "TIF 업로드 성공"; } // 기타 확장자: 저장만 하고 메타 기록 - saveUploadMeta(path); + saveUploadMeta(path, hstUid); return "업로드 성공"; } catch (IOException e) { @@ -408,35 +408,123 @@ public class MapSheetMngFileCheckerService { } } - private void saveUploadMeta(Path savedPath) { + @Transactional + public String uploadPair( + MultipartFile tfwFile, + MultipartFile tifFile, + String targetPath, + boolean overwrite, + Long hstUid) { + try { + Path basePath = Paths.get(targetPath); + if (Files.isDirectory(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); + } + + // 파일 저장 + tfwFile.transferTo(tfwPath.toFile()); + tifFile.transferTo(tifPath.toFile()); + + // 검증 + boolean tfwOk = FIleChecker.checkTfw(tfwPath.toString()); + if (!tfwOk) { + Files.deleteIfExists(tfwPath); + Files.deleteIfExists(tifPath); + throw new ValidationException("유효하지 않은 TFW 파일입니다 (6줄 숫자 형식 검증 실패): " + tfwName); + } + boolean isValidTif = FIleChecker.cmmndGdalInfo(tifPath.toString()); + if (!isValidTif) { + Files.deleteIfExists(tfwPath); + Files.deleteIfExists(tifPath); + throw new ValidationException("유효하지 않은 TIF 파일입니다 (GDAL 검증 실패): " + tifName); + } + + // 메타 저장 (두 파일 각각 저장) + saveUploadMeta(tfwPath, hstUid); + saveUploadMeta(tifPath, hstUid); + return "TFW/TIF 페어 업로드 성공"; + } else { + throw new ValidationException("targetPath는 디렉토리여야 합니다."); + } + } catch (IOException e) { + throw new IllegalArgumentException("파일 I/O 처리 실패: " + e.getMessage()); + } + } + + private void saveUploadMeta(Path savedPath, Long hstUid) { String fullPath = savedPath.toAbsolutePath().toString(); String fileName = savedPath.getFileName().toString(); String ext = FilenameUtils.getExtension(fileName); - // 연도(mng_yyyy) 추출: 경로 내의 연도 폴더명을 찾음 (예: .../original-images/2022/2022_25cm/...) - Integer mngYyyy = extractYearFromPath(fullPath); + MapSheetMngFileEntity entity = new MapSheetMngFileEntity(); - // 도엽번호(map_sheet_num) 추정: 파일명 내 숫자 연속 부분을 추출해 정수화 - String mapSheetNum = extractMapSheetNumFromFileName(fileName); - - // ref_map_sheet_num: 1000으로 나눈 값(파일명 규칙에 따라 추정) - String refMapSheetNum = null; - if (mapSheetNum != null && !mapSheetNum.isEmpty()) { - try { - long num = Long.parseLong(mapSheetNum); - refMapSheetNum = String.valueOf(num / 1000); - } catch (NumberFormatException ignored) { + 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); } - MapSheetMngFileEntity entity = new MapSheetMngFileEntity(); - 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); } @@ -492,4 +580,17 @@ public class MapSheetMngFileCheckerService { } 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/postgres/core/CommonCodeCoreService.java b/src/main/java/com/kamco/cd/kamcoback/postgres/core/CommonCodeCoreService.java index 2e23ceb0..b4d3be32 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/core/CommonCodeCoreService.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/core/CommonCodeCoreService.java @@ -2,12 +2,12 @@ package com.kamco.cd.kamcoback.postgres.core; import com.kamco.cd.kamcoback.code.dto.CommonCodeDto; import com.kamco.cd.kamcoback.code.dto.CommonCodeDto.Basic; +import com.kamco.cd.kamcoback.code.dto.CommonCodeDto.SearchReq; import com.kamco.cd.kamcoback.common.service.BaseCoreService; import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ApiResponseCode; import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ResponseObj; import com.kamco.cd.kamcoback.postgres.entity.CommonCodeEntity; import com.kamco.cd.kamcoback.postgres.repository.code.CommonCodeRepository; -import com.kamco.cd.kamcoback.code.dto.CommonCodeDto.SearchReq; import jakarta.persistence.EntityNotFoundException; import java.util.List; import java.util.Optional; 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 7b07d7ec..462de13a 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,10 +52,7 @@ public class MapSheetMngCoreService { .findMapSheetMngHstInfo(hstUid) .orElseThrow(EntityNotFoundException::new)); - // TODO: local TEST 시 각자 경로 수정하기 - // TODO: application.yml 에 active profile : local 로 임시 변경하여 테스트 String localPath = ""; - // String localPath = "C:\\Users\\gypark\\Desktop\\file"; String rootDir = ORIGINAL_IMAGES_PATH + "/" + entity.get().getMngYyyy(); if (activeEnv.equals("local")) { rootDir = localPath + rootDir; @@ -68,6 +65,59 @@ public class MapSheetMngCoreService { count += 1; } + // 파일 크기 계산 및 저장 + try (Stream paths = Files.walk(Paths.get(rootDir))) { + List matched = + paths + .filter(Files::isRegularFile) + .filter( + p -> { + String name = p.getFileName().toString(); + return name.equals(filename + ".tif") || name.equals(filename + ".tfw"); + }) + .collect(Collectors.toList()); + + long tifSize = + matched.stream() + .filter(p -> p.getFileName().toString().endsWith(".tif")) + .mapToLong( + p -> { + try { + return Files.size(p); + } catch (IOException e) { + return 0L; + } + }) + .sum(); + + long tfwSize = + matched.stream() + .filter(p -> p.getFileName().toString().endsWith(".tfw")) + .mapToLong( + p -> { + try { + return Files.size(p); + } catch (IOException e) { + return 0L; + } + }) + .sum(); + + entity.get().setTifSizeBytes(tifSize); + entity.get().setTfwSizeBytes(tfwSize); + entity.get().setTotalSizeBytes(tifSize + tfwSize); + + // 엔터티 저장 -> 커스텀 업데이트로 변경 + mapSheetMngRepository.updateHstFileSizes( + entity.get().getHstUid(), tifSize, tfwSize, tifSize + tfwSize); + } catch (IOException e) { + // 크기 계산 실패 시 0으로 저장 + entity.get().setTifSizeBytes(0L); + entity.get().setTfwSizeBytes(0L); + entity.get().setTotalSizeBytes(0L); + mapSheetMngRepository.updateHstFileSizes(entity.get().getHstUid(), 0L, 0L, 0L); + } + /* MapSheetMngDto.DataState dataState = flag ? MapSheetMngDto.DataState.SUCCESS : MapSheetMngDto.DataState.FAIL; diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/core/MapSheetMngFileCheckerCoreService.java b/src/main/java/com/kamco/cd/kamcoback/postgres/core/MapSheetMngFileCheckerCoreService.java index 55cee555..57b7e4db 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/core/MapSheetMngFileCheckerCoreService.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/core/MapSheetMngFileCheckerCoreService.java @@ -2,7 +2,10 @@ package com.kamco.cd.kamcoback.postgres.core; import com.kamco.cd.kamcoback.common.utils.FIleChecker; import com.kamco.cd.kamcoback.mapsheet.dto.ImageryDto; +import com.kamco.cd.kamcoback.postgres.entity.MapSheetMngHstEntity; import com.kamco.cd.kamcoback.postgres.repository.mapsheet.MapSheetMngFileCheckerRepository; +import com.kamco.cd.kamcoback.postgres.repository.mapsheet.MapSheetMngRepository; +import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; @@ -13,6 +16,7 @@ import org.springframework.stereotype.Service; public class MapSheetMngFileCheckerCoreService { private final MapSheetMngFileCheckerRepository mapSheetMngFileCheckerRepository; + private final MapSheetMngRepository mapSheetMngRepository; private static final String ORIGINAL_IMAGES_PATH = "/app/original-images"; @@ -50,4 +54,8 @@ public class MapSheetMngFileCheckerCoreService { return new ImageryDto.SyncReturn(flag, syncCnt, tfwErrCnt, tifErrCnt); } + + public Optional findHstByUid(Long hstUid) { + return mapSheetMngRepository.findMapSheetMngHstInfo(hstUid); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetMngFileEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetMngFileEntity.java index 45970e91..0f81f5c5 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetMngFileEntity.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetMngFileEntity.java @@ -50,6 +50,4 @@ public class MapSheetMngFileEntity { @Column(name = "file_size") private Long fileSize; - - } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetMngHstEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetMngHstEntity.java index 50c85e18..def1c9b7 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetMngHstEntity.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetMngHstEntity.java @@ -78,4 +78,13 @@ public class MapSheetMngHstEntity extends CommonDateEntity { @Column(name = "sync_check_end_dttm") private ZonedDateTime syncCheckEndDttm; + + @Column(name = "tif_size_bytes") + private Long tifSizeBytes; + + @Column(name = "tfw_size_bytes") + private Long tfwSizeBytes; + + @Column(name = "total_size_bytes") + private Long totalSizeBytes; } 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 a5e4e107..f6348a66 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 @@ -24,4 +24,6 @@ public interface MapSheetMngRepositoryCustom { Page findMapSheetErrorList( MapSheetMngDto.@Valid ErrorSearchReq searchReq); + + void updateHstFileSizes(Long hstUid, long tifSizeBytes, long tfwSizeBytes, long totalSizeBytes); } 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 c50ae12c..aa33deac 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 @@ -266,6 +266,19 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport .fetchOne()); } + @Override + public void updateHstFileSizes( + Long hstUid, long tifSizeBytes, long tfwSizeBytes, long totalSizeBytes) { + String sql = + "UPDATE tb_map_sheet_mng_hst SET tif_size_bytes = :tif, tfw_size_bytes = :tfw, total_size_bytes = :tot WHERE hst_uid = :uid"; + Query query = (Query) em.createNativeQuery(sql); + query.setParameter("tif", tifSizeBytes); + query.setParameter("tfw", tfwSizeBytes); + query.setParameter("tot", totalSizeBytes); + query.setParameter("uid", hstUid); + query.executeUpdate(); + } + private NumberExpression rowNum() { return Expressions.numberTemplate( Integer.class, "row_number() over(order by {0} desc)", mapSheetMngHstEntity.createdDate); diff --git a/src/main/resources/db/migration/V8__Add_File_Size_Columns.sql b/src/main/resources/db/migration/V8__Add_File_Size_Columns.sql new file mode 100644 index 00000000..d99299ad --- /dev/null +++ b/src/main/resources/db/migration/V8__Add_File_Size_Columns.sql @@ -0,0 +1,6 @@ +-- Add file size columns for map sheet upload +ALTER TABLE tb_map_sheet_mng_hst + ADD COLUMN IF NOT EXISTS tif_size_bytes BIGINT, + ADD COLUMN IF NOT EXISTS tfw_size_bytes BIGINT, + ADD COLUMN IF NOT EXISTS total_size_bytes BIGINT; +