diff --git a/build.gradle b/build.gradle index 223d47bf..c71c8a94 100644 --- a/build.gradle +++ b/build.gradle @@ -38,6 +38,9 @@ dependencies { //geometry implementation 'com.fasterxml.jackson.core:jackson-databind' + implementation "org.geotools:gt-shapefile:30.0" + implementation "org.geotools:gt-referencing:30.0" + implementation "org.geotools:gt-geojson:30.0" implementation 'org.locationtech.jts.io:jts-io-common:1.20.0' implementation 'org.locationtech.jts:jts-core:1.19.0' implementation 'org.hibernate:hibernate-spatial:6.2.7.Final' diff --git a/src/main/java/com/kamco/cd/kamcoback/common/enums/MngStateType.java b/src/main/java/com/kamco/cd/kamcoback/common/enums/MngStateType.java index 202e3757..d1d98df1 100644 --- a/src/main/java/com/kamco/cd/kamcoback/common/enums/MngStateType.java +++ b/src/main/java/com/kamco/cd/kamcoback/common/enums/MngStateType.java @@ -11,7 +11,7 @@ import lombok.Getter; public enum MngStateType implements EnumType { NOTYET("동기화 시작"), PROCESSING("데이터 체크"), - DONE("동기화작업종료"), + DONE("동기화 작업 종료"), TAKINGERROR("오류 데이터 처리중"); private final String desc; 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 08b7ab8d..ce622964 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 @@ -475,6 +475,20 @@ 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) { // 파일 유효성 검증 if (mfile == null || mfile.isEmpty() || mfile.getSize() == 0) { 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/config/SecurityConfig.java b/src/main/java/com/kamco/cd/kamcoback/config/SecurityConfig.java index bf45341b..1acaf5f4 100644 --- a/src/main/java/com/kamco/cd/kamcoback/config/SecurityConfig.java +++ b/src/main/java/com/kamco/cd/kamcoback/config/SecurityConfig.java @@ -2,6 +2,7 @@ package com.kamco.cd.kamcoback.config; import com.kamco.cd.kamcoback.auth.CustomAuthenticationProvider; import com.kamco.cd.kamcoback.auth.JwtAuthenticationFilter; +import com.kamco.cd.kamcoback.auth.MenuAuthorizationManager; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; @@ -28,6 +29,7 @@ public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthenticationFilter; private final CustomAuthenticationProvider customAuthenticationProvider; + private final MenuAuthorizationManager menuAuthorizationManager; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { diff --git a/src/main/java/com/kamco/cd/kamcoback/inference/InferenceResultShpApiController.java b/src/main/java/com/kamco/cd/kamcoback/inference/InferenceResultShpApiController.java new file mode 100644 index 00000000..3027acbc --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/inference/InferenceResultShpApiController.java @@ -0,0 +1,33 @@ +package com.kamco.cd.kamcoback.inference; + +import com.kamco.cd.kamcoback.config.api.ApiResponseDto; +import com.kamco.cd.kamcoback.inference.service.InferenceResultShpService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "추론결과 데이터 생성", description = "추론결과 데이터 생성 API") +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/inference/shp") +public class InferenceResultShpApiController { + + private final InferenceResultShpService inferenceResultShpService; + + @Operation(summary = "추론결과 데이터 저장", description = "추론결과 데이터 저장") + @PostMapping("/save") + public ApiResponseDto saveInferenceData() { + inferenceResultShpService.saveInferenceResultData(); + return ApiResponseDto.createOK("OK"); + } + + @Operation(summary = "shp 파일 생성", description = "shp 파일 생성") + @PostMapping("/create") + public ApiResponseDto createShpFile() { + inferenceResultShpService.createShpFile(); + return ApiResponseDto.createOK("OK"); + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/inference/ShpWriter.java b/src/main/java/com/kamco/cd/kamcoback/inference/ShpWriter.java new file mode 100644 index 00000000..85e03248 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/inference/ShpWriter.java @@ -0,0 +1,13 @@ +package com.kamco.cd.kamcoback.inference; + +import com.kamco.cd.kamcoback.inference.dto.InferenceResultShpDto; +import java.util.List; + +public interface ShpWriter { + + // SHP (.shp/.shx/.dbf) + void writeShp(String shpBasePath, List rows); + + // GeoJSON (.geojson) + void writeGeoJson(String geoJsonPath, List rows); +} diff --git a/src/main/java/com/kamco/cd/kamcoback/inference/dto/InferenceResultShpDto.java b/src/main/java/com/kamco/cd/kamcoback/inference/dto/InferenceResultShpDto.java new file mode 100644 index 00000000..9581bab2 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/inference/dto/InferenceResultShpDto.java @@ -0,0 +1,64 @@ +package com.kamco.cd.kamcoback.inference.dto; + +import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataInferenceGeomEntity; +import java.util.UUID; +import lombok.Getter; +import lombok.Setter; +import org.locationtech.jts.geom.Geometry; + +public class InferenceResultShpDto { + + @Getter + @Setter + public static class Basic { + + // ===== 식별 ===== + private Long geoUid; + private UUID uuid; + + // ===== 그룹 키 ===== + private Integer stage; + private Long mapId; + private Integer input1; // compare_yyyy + private Integer input2; // target_yyyy + + // ===== 추론 결과 ===== + private Float cdProb; + + private String beforeClass; + private Float beforeProbability; + + private String afterClass; + private Float afterProbability; + + // ===== 공간 정보 ===== + private Geometry geometry; + private Float area; + + /** Entity → DTO 변환 */ + public static Basic from(MapSheetAnalDataInferenceGeomEntity e) { + Basic d = new Basic(); + + d.geoUid = e.getGeoUid(); + d.uuid = e.getUuid(); + + d.stage = e.getStage(); + d.mapId = e.getMapSheetNum(); + d.input1 = e.getCompareYyyy(); + d.input2 = e.getTargetYyyy(); + + d.cdProb = e.getCdProb(); + + d.beforeClass = e.getClassBeforeCd(); + d.beforeProbability = e.getClassBeforeProb(); + + d.afterClass = e.getClassAfterCd(); + d.afterProbability = e.getClassAfterProb(); + + d.geometry = e.getGeom(); + d.area = e.getArea(); + + return d; + } + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/inference/service/GeoToolsShpWriter.java b/src/main/java/com/kamco/cd/kamcoback/inference/service/GeoToolsShpWriter.java new file mode 100644 index 00000000..d20af592 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/inference/service/GeoToolsShpWriter.java @@ -0,0 +1,401 @@ +package com.kamco.cd.kamcoback.inference.service; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.kamco.cd.kamcoback.inference.ShpWriter; +import com.kamco.cd.kamcoback.inference.dto.InferenceResultShpDto; +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStreamWriter; +import java.io.Serializable; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; +import org.geotools.api.data.SimpleFeatureSource; +import org.geotools.api.data.SimpleFeatureStore; +import org.geotools.api.data.Transaction; +import org.geotools.api.feature.simple.SimpleFeature; +import org.geotools.api.feature.simple.SimpleFeatureType; +import org.geotools.api.referencing.crs.CoordinateReferenceSystem; +import org.geotools.data.shapefile.ShapefileDataStore; +import org.geotools.data.shapefile.ShapefileDataStoreFactory; +import org.geotools.feature.DefaultFeatureCollection; +import org.geotools.feature.simple.SimpleFeatureBuilder; +import org.geotools.feature.simple.SimpleFeatureTypeBuilder; +import org.geotools.geojson.geom.GeometryJSON; +import org.geotools.referencing.CRS; +import org.locationtech.jts.geom.Geometry; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class GeoToolsShpWriter implements ShpWriter { + + // DBF 파일 한글 깨짐 방지를 위해 EUC-KR 사용 + private static final Charset DBF_CHARSET = Charset.forName("EUC-KR"); + + // GeoJSON 출력은 UTF-8 + private static final Charset GEOJSON_CHARSET = StandardCharsets.UTF_8; + + // 좌표계: Korea 2000 / Central Belt 2010 + private static final String EPSG_5186 = "EPSG:5186"; + + /** + * SHP 파일(.shp/.shx/.dbf/.prj)을 생성한다. + * + *

- shpBasePath를 기준으로 파일을 생성한다. 예) /Users/kim/export/shp/1_map_2021_2022 → 1_map_2021_2022.shp + * → 1_map_2021_2022.shx → 1_map_2021_2022.dbf → 1_map_2021_2022.prj + * + *

- geometry 타입은 첫 번째 유효 geometry 기준으로 스키마를 생성한다. - 좌표계는 EPSG:5186으로 설정하며, .prj 파일을 직접 생성한다. + * + * @param shpBasePath 확장자를 제외한 SHP 파일 기본 경로 + * @param rows 동일 그룹(stage, mapId, input1, input2)의 데이터 목록 + */ + @Override + public void writeShp(String shpBasePath, List rows) { + + if (rows == null || rows.isEmpty()) { + return; + } + + // SHP는 Geometry.class를 허용하지 않으므로 + // 첫 번째 유효 geometry의 "구체 타입"을 기준으로 스키마를 생성한다. + Geometry firstGeom = firstNonNullGeometry(rows); + if (firstGeom == null) { + throw new IllegalArgumentException("SHP 생성 실패: geometry가 전부 null 입니다. path=" + shpBasePath); + } + + @SuppressWarnings("unchecked") + Class geomType = (Class) firstGeom.getClass(); + + ShapefileDataStore dataStore = null; + + try { + File shpFile = new File(shpBasePath + ".shp"); + createDirectories(shpFile); + + // EPSG:5186 CRS 로딩 + CoordinateReferenceSystem crs = CRS.decode(EPSG_5186, false); + + // FeatureType(schema) 생성 + SimpleFeatureType schema = createSchema(geomType, crs); + + // ShapefileDataStore 생성 + dataStore = createDataStore(shpFile, schema); + + // FeatureCollection 생성 + DefaultFeatureCollection collection = buildFeatureCollection(schema, rows); + + // 실제 SHP 파일에 feature 쓰기 + writeFeatures(dataStore, collection); + + // .prj 파일 직접 생성 (EPSG:5186) + writePrjFile(shpBasePath, crs); + + log.info("SHP 생성 완료: {} ({} features)", shpFile.getAbsolutePath(), collection.size()); + + } catch (Exception e) { + throw new RuntimeException("SHP 생성 실패: " + shpBasePath, e); + } finally { + if (dataStore != null) { + try { + dataStore.dispose(); + } catch (Exception ignore) { + } + } + } + } + + /** + * GeoJSON 파일(.geojson)을 생성한다. + * + *

- FeatureCollection 형태로 출력한다. - 최상단에 name / crs / properties를 포함한다. - 각 Feature는 polygon 단위로 + * 생성된다. - geometry는 GeoTools GeometryJSON을 사용하여 직렬화한다. + * + *

GeoJSON 구조 예: { "type": "FeatureCollection", "name": "stage_input1_input2_mapId", "crs": { + * "type": "name", "properties": { "name": "urn:ogc:def:crs:EPSG::5186" } }, "properties": { ... + * }, "features": [ ... ] } + * + * @param geoJsonPath 생성할 GeoJSON 파일의 전체 경로 (.geojson 포함) + * @param rows 동일 그룹(stage, mapId, input1, input2)의 데이터 목록 + */ + @Override + public void writeGeoJson(String geoJsonPath, List rows) { + + if (rows == null || rows.isEmpty()) { + return; + } + + try { + File geoJsonFile = new File(geoJsonPath); + createDirectories(geoJsonFile); + + // 그룹 공통 메타 정보는 첫 row 기준 + InferenceResultShpDto.Basic first = rows.get(0); + + ObjectMapper om = new ObjectMapper(); + GeometryJSON gj = new GeometryJSON(15); + + // FeatureCollection 루트 + ObjectNode root = om.createObjectNode(); + root.put("type", "FeatureCollection"); + + // name: stage_input1_input2_mapId + String name = + String.format( + "%d_%d_%d_%d", + first.getStage(), first.getInput1(), first.getInput2(), first.getMapId()); + root.put("name", name); + + // CRS (EPSG:5186) + ObjectNode crs = om.createObjectNode(); + crs.put("type", "name"); + ObjectNode crsProps = om.createObjectNode(); + crsProps.put("name", "urn:ogc:def:crs:EPSG::5186"); + crs.set("properties", crsProps); + root.set("crs", crs); + + // 그룹 공통 properties + ObjectNode groupProps = om.createObjectNode(); + groupProps.put("stage", first.getStage()); + groupProps.put("input1", first.getInput1()); + groupProps.put("input2", first.getInput2()); + groupProps.put("map_id", first.getMapId()); + // 학습서버 버전은 추후 추가 + // groupProps.put("m1", "v1.2222.251223121212"); + // groupProps.put("m2", "v2.211.251223121213"); + // groupProps.put("m3", "v3.233.251223121214"); + root.set("properties", groupProps); + + // features 배열 + ArrayNode features = om.createArrayNode(); + + for (InferenceResultShpDto.Basic dto : rows) { + if (dto.getGeometry() == null) { + continue; + } + + ObjectNode feature = om.createObjectNode(); + feature.put("type", "Feature"); + + // feature properties + ObjectNode p = om.createObjectNode(); + p.put("polygon_id", dto.getUuid() != null ? dto.getUuid().toString() : null); + if (dto.getCdProb() != null) { + p.put("cd_prob", dto.getCdProb()); + } + if (dto.getInput1() != null) { + p.put("input1", dto.getInput1()); + } + if (dto.getInput2() != null) { + p.put("input2", dto.getInput2()); + } + if (dto.getMapId() != null) { + p.put("map_id", dto.getMapId()); + } + if (dto.getArea() != null) { + p.put("area", dto.getArea()); + } + p.put("before_c", dto.getBeforeClass()); + if (dto.getBeforeProbability() != null) { + p.put("before_p", dto.getBeforeProbability()); + } + p.put("after_c", dto.getAfterClass()); + if (dto.getAfterProbability() != null) { + p.put("after_p", dto.getAfterProbability()); + } + + feature.set("properties", p); + + // geometry + String geomJson = gj.toString(dto.getGeometry()); + JsonNode geomNode = om.readTree(geomJson); + feature.set("geometry", geomNode); + + features.add(feature); + } + + root.set("features", features); + + // 파일 쓰기 + try (OutputStreamWriter w = + new OutputStreamWriter(new FileOutputStream(geoJsonFile), GEOJSON_CHARSET)) { + om.writerWithDefaultPrettyPrinter().writeValue(w, root); + } + + log.info("GeoJSON 생성 완료: {} ({} features)", geoJsonFile.getAbsolutePath(), features.size()); + + } catch (Exception e) { + throw new RuntimeException("GeoJSON 생성 실패: " + geoJsonPath, e); + } + } + + /** + * rows 목록에서 첫 번째로 발견되는 non-null Geometry를 반환한다. + * + *

- SHP 스키마 생성 시 geometry 타입 결정을 위해 사용된다. + * + * @param rows DTO 목록 + * @return 첫 번째 non-null Geometry, 없으면 null + */ + private Geometry firstNonNullGeometry(List rows) { + for (InferenceResultShpDto.Basic r : rows) { + if (r != null && r.getGeometry() != null) { + return r.getGeometry(); + } + } + return null; + } + + /** + * SHP 파일에 사용할 SimpleFeatureType(schema)를 생성한다. + * + *

- geometry 컬럼은 반드시 첫 번째 컬럼이어야 한다. - DBF 컬럼은 SHP 제약(컬럼명 10자, 길이 제한)을 고려한다. + * + * @param geomType geometry의 구체 타입 (Polygon, MultiPolygon 등) + * @param crs 좌표계(EPSG:5186) + * @return SimpleFeatureType + */ + private SimpleFeatureType createSchema( + Class geomType, CoordinateReferenceSystem crs) { + SimpleFeatureTypeBuilder b = new SimpleFeatureTypeBuilder(); + b.setName("inference_result"); + b.setCRS(crs); + + // geometry는 반드시 첫 컬럼 + b.add("the_geom", geomType); + + // DBF 컬럼 정의 (10자 제한 고려) + b.length(36).add("poly_id", String.class); + b.add("cd_prob", Double.class); + b.add("input1", Integer.class); + b.add("input2", Integer.class); + b.add("map_id", Long.class); + b.add("area", Double.class); + b.length(20).add("before_c", String.class); + b.add("before_p", Double.class); + b.length(20).add("after_c", String.class); + b.add("after_p", Double.class); + + return b.buildFeatureType(); + } + + /** + * ShapefileDataStore를 생성하고 스키마를 등록한다. + * + *

- DBF 파일 인코딩은 EUC-KR로 설정한다. - spatial index(.qix)를 생성한다. + * + * @param shpFile SHP 파일 객체 + * @param schema SimpleFeatureType + * @return 생성된 ShapefileDataStore + */ + private ShapefileDataStore createDataStore(File shpFile, SimpleFeatureType schema) + throws Exception { + + Map params = new HashMap<>(); + params.put("url", shpFile.toURI().toURL()); + params.put("create spatial index", Boolean.TRUE); + + ShapefileDataStoreFactory factory = new ShapefileDataStoreFactory(); + ShapefileDataStore dataStore = (ShapefileDataStore) factory.createNewDataStore(params); + + dataStore.setCharset(DBF_CHARSET); + dataStore.createSchema(schema); + + return dataStore; + } + + /** + * DTO 목록을 SimpleFeatureCollection으로 변환한다. + * + *

- DTO 1건당 Feature 1개 생성 - geometry가 null인 데이터는 제외한다. + * + * @param schema FeatureType + * @param rows DTO 목록 + * @return DefaultFeatureCollection + */ + private DefaultFeatureCollection buildFeatureCollection( + SimpleFeatureType schema, List rows) { + DefaultFeatureCollection collection = new DefaultFeatureCollection(); + SimpleFeatureBuilder builder = new SimpleFeatureBuilder(schema); + + for (InferenceResultShpDto.Basic dto : rows) { + if (dto == null || dto.getGeometry() == null) { + continue; + } + + builder.add(dto.getGeometry()); + builder.add(dto.getUuid() != null ? dto.getUuid().toString() : null); + builder.add(dto.getCdProb() != null ? dto.getCdProb().doubleValue() : null); + builder.add(dto.getInput1()); + builder.add(dto.getInput2()); + builder.add(dto.getMapId()); + builder.add(dto.getArea() != null ? dto.getArea().doubleValue() : null); + builder.add(dto.getBeforeClass()); + builder.add( + dto.getBeforeProbability() != null ? dto.getBeforeProbability().doubleValue() : null); + builder.add(dto.getAfterClass()); + builder.add( + dto.getAfterProbability() != null ? dto.getAfterProbability().doubleValue() : null); + + SimpleFeature feature = builder.buildFeature(null); + collection.add(feature); + builder.reset(); + } + + return collection; + } + + /** + * FeatureCollection을 SHP 파일에 실제로 기록한다. + * + * @param dataStore ShapefileDataStore + * @param collection FeatureCollection + */ + private void writeFeatures(ShapefileDataStore dataStore, DefaultFeatureCollection collection) + throws Exception { + + String typeName = dataStore.getTypeNames()[0]; + SimpleFeatureSource featureSource = dataStore.getFeatureSource(typeName); + + if (!(featureSource instanceof SimpleFeatureStore store)) { + throw new IllegalStateException("FeatureStore 생성 실패"); + } + + store.setTransaction(Transaction.AUTO_COMMIT); + store.addFeatures(collection); + store.getTransaction().commit(); + } + + /** + * SHP 좌표계 정보를 담은 .prj 파일을 생성한다. + * + * @param shpBasePath SHP 기본 경로 (확장자 제외) + * @param crs 좌표계(EPSG:5186) + */ + private void writePrjFile(String shpBasePath, CoordinateReferenceSystem crs) throws Exception { + + File prjFile = new File(shpBasePath + ".prj"); + createDirectories(prjFile); + + Files.writeString(prjFile.toPath(), crs.toWKT(), StandardCharsets.UTF_8); + } + + /** + * 파일이 생성될 디렉토리가 없으면 생성한다. + * + * @param file 생성 대상 파일 + */ + private void createDirectories(File file) throws Exception { + File parent = file.getParentFile(); + if (parent != null) { + Files.createDirectories(parent.toPath()); + } + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceResultShpService.java b/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceResultShpService.java new file mode 100644 index 00000000..78f7c80c --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceResultShpService.java @@ -0,0 +1,82 @@ +package com.kamco.cd.kamcoback.inference.service; + +import com.kamco.cd.kamcoback.inference.ShpWriter; +import com.kamco.cd.kamcoback.inference.dto.InferenceResultShpDto; +import com.kamco.cd.kamcoback.postgres.core.InferenceResultShpCoreService; +import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataInferenceGeomEntity; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class InferenceResultShpService { + + private final InferenceResultShpCoreService coreService; + private final ShpWriter shpWriter; + + /** inference_results -> tb_map_sheet_anal_data_inference / geom 업서트 */ + @Transactional + public void saveInferenceResultData() { + coreService.buildInferenceData(); + } + + /** + * dataUid 단위로 재생성(덮어쓰기) - reset(inference false + geom 전부 false) - geom 엔티티 조회 -> dto 변환 - + * shp/geojson 생성 - 성공 geo_uid만 true - inference true + */ + @Transactional + public void createShpFile() { + + // TODO 경로는 설정으로 빼는 게 좋음 + String baseDir = System.getProperty("user.home") + "/export"; + + int batchSize = 100; // 한번에 처리할 data_uid 개수 + int geomLimit = 500000; // data_uid 당 최대 geom 로딩 수 (메모리/시간 보고 조절) + + List dataUids = coreService.findPendingDataUids(batchSize); + + for (Long dataUid : dataUids) { + + // 1) 덮어쓰기 시작: 리셋 + coreService.resetForRegenerate(dataUid); + + // 2) 생성 대상 조회(엔티티) + List entities = + coreService.loadGeomEntities(dataUid, geomLimit); + + if (entities.isEmpty()) { + // 실패 상태(false 유지) -> 다음 배치에서 다시 덮어쓰기로 시도 + continue; + } + + // 3) 엔티티 -> DTO + List dtoList = + entities.stream().map(InferenceResultShpDto.Basic::from).toList(); + + // 4) 파일명: stage_mapSheet_compare_target (첫 row 기준) + MapSheetAnalDataInferenceGeomEntity first = entities.get(0); + String baseName = + String.format( + "%d_%d_%d_%d", + first.getStage(), + first.getMapSheetNum(), + first.getCompareYyyy(), + first.getTargetYyyy()); + + String shpBasePath = baseDir + "/shp/" + baseName; // 확장자 없이 + String geoJsonPath = baseDir + "/geojson/" + baseName + ".geojson"; + + // 5) 파일 생성 (예외 발생 시 성공 마킹 안 됨 -> 다음에 덮어쓰기 재시도) + shpWriter.writeShp(shpBasePath, dtoList); + shpWriter.writeGeoJson(geoJsonPath, dtoList); + + // 6) 성공 마킹: geo_uid만 true + List geoUids = + entities.stream().map(MapSheetAnalDataInferenceGeomEntity::getGeoUid).toList(); + coreService.markSuccess(dataUid, geoUids); + } + } +} 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 0a871d75..f733eb8b 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; @@ -143,6 +147,7 @@ public class MapSheetMngApiController { return ApiResponseDto.ok(mapSheetMngService.findMapSheetErrorList(searchReq)); } + /* @Operation(summary = "오류데이터 팝업 > 업로드 처리", description = "오류데이터 팝업 > 업로드 처리") @ApiResponses( value = { @@ -163,6 +168,9 @@ public class MapSheetMngApiController { return ApiResponseDto.ok(mapSheetMngService.uploadProcess(hstUidList)); } + */ + + /* @Operation(summary = "오류데이터 팝업 > 추론 제외", description = "오류데이터 팝업 > 추론 제외") @PutMapping("/except-inference") public ApiResponseDto updateExceptUseInference( @@ -170,6 +178,10 @@ public class MapSheetMngApiController { return ApiResponseDto.ok(mapSheetMngService.updateExceptUseInference(hstUidList)); } + + + */ + @Operation(summary = "페어 파일 업로드", description = "TFW/TIF 두 파일을 쌍으로 업로드 및 검증") @PostMapping(value = "/upload-pair", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ApiResponseDto uploadPair( @@ -187,7 +199,7 @@ public class MapSheetMngApiController { value = { @ApiResponse( responseCode = "201", - description = "파일삭제 처리 성공", + description = "파일조회 성공", content = @Content( mediaType = "application/json", @@ -197,17 +209,19 @@ public class MapSheetMngApiController { @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) }) @GetMapping("/mng-file-list") - public ApiResponseDto> findHstUidToMapSheetFileList( + public ApiResponseDto> findByHstUidMapSheetFileList( @RequestParam @Valid Long hstUid) { - return ApiResponseDto.ok(mapSheetMngService.findHstUidToMapSheetFileList(hstUid)); + return ApiResponseDto.ok(mapSheetMngService.findByHstUidMapSheetFileList(hstUid)); } - @Operation(summary = "영상관리 > 파일삭제", description = "영상관리 > 파일삭제") + @Operation( + summary = "영상관리 > 파일사용설정 및 중복제거", + description = "영상관리 >파일사용설정 및 중복제거(중복파일제거 및 선택파일사용설정)") @ApiResponses( value = { @ApiResponse( responseCode = "201", - description = "파일삭제 처리 성공", + description = "파일사용설정 처리 성공", content = @Content( mediaType = "application/json", @@ -216,9 +230,47 @@ public class MapSheetMngApiController { @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) }) - @PutMapping("/del-mng-files") - public ApiResponseDto deleteByFileUidMngFile( + @PutMapping("/update-use-mng-files") + public ApiResponseDto updateUseByFileUidMngFile( @RequestParam @Valid List fileUids) { - return ApiResponseDto.ok(mapSheetMngService.deleteByFileUidMngFile(fileUids)); + return ApiResponseDto.ok(mapSheetMngService.setUseByFileUidMngFile(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/dto/MapSheetMngDto.java b/src/main/java/com/kamco/cd/kamcoback/mapsheet/dto/MapSheetMngDto.java index 8ab46894..dd760df4 100644 --- a/src/main/java/com/kamco/cd/kamcoback/mapsheet/dto/MapSheetMngDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/mapsheet/dto/MapSheetMngDto.java @@ -91,17 +91,6 @@ public class MapSheetMngDto { @JsonFormatDttm private ZonedDateTime rgstStrtDttm; @JsonFormatDttm private ZonedDateTime rgstEndDttm; - public String getMngState() { - - if (this.syncStateDoneCnt == 0) return "NOTYET"; - else if (this.syncStateDoneCnt < this.syncTotCnt) return "PROCESSING"; - - if ((this.syncNotPaireExecCnt + this.syncDuplicateExecCnt + this.syncFaultExecCnt) > 0) - return "TAKINGERROR"; - - return "DONE"; - } - public String getSyncState() { if (this.syncStateDoneCnt == 0) return "NOTYET"; @@ -140,8 +129,21 @@ public class MapSheetMngDto { return this.syncNotPaireExecCnt + this.syncDuplicateExecCnt + this.syncFaultExecCnt; } + public String getMngState() { + + String mngState = "DONE"; + + if (this.syncStateDoneCnt == 0) mngState = "NOTYET"; + else if (this.syncStateDoneCnt < this.syncTotCnt) mngState = "PROCESSING"; + + if ((this.syncNotPaireExecCnt + this.syncDuplicateExecCnt + this.syncFaultExecCnt) > 0) + mngState = "TAKINGERROR"; + + return mngState; + } + public String getMngStateName() { - String enumId = this.mngState; + String enumId = this.getMngState(); if (enumId == null || enumId.isEmpty()) { enumId = "NOTYET"; } 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 e64ada93..c7ff1203 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; @@ -11,7 +14,6 @@ import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.MngDto; import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.MngFilesDto; import com.kamco.cd.kamcoback.postgres.core.MapSheetMngCoreService; import jakarta.validation.Valid; -import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -31,7 +33,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; @@ -71,12 +72,9 @@ public class MapSheetMngService { } @Transactional - public DmlReturn mngDataSave(AddReq AddReq) { - return mapSheetMngCoreService.mngDataSave(AddReq); - } - - public DmlReturn uploadProcess(@Valid List hstUidList) { - return mapSheetMngCoreService.uploadProcess(hstUidList); + public DmlReturn mngDataSave(AddReq addReq) { + int execCnt = mapSheetMngCoreService.mngDataSave(addReq); + return new MapSheetMngDto.DmlReturn("success", addReq.getMngYyyy() + "년, " + execCnt + "건 생성"); } public DmlReturn updateExceptUseInference(@Valid List hstUidList) { @@ -112,48 +110,34 @@ 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); + + 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"); // 싱크파일목록으로 업로드 경로 확인 - List mngFiles = mapSheetMngCoreService.findIdToMapSheetFileList(hstUid); + List mngFiles = mapSheetMngCoreService.findByHstUidMapSheetFileList(hstUid); String uploadPath = ""; for (MngFilesDto dto : mngFiles) { uploadPath = dto.getFilePath(); break; } + Path tfwTmpSavePath = Paths.get(tfwTmpPath); + Path tifTmpSavePath = Paths.get(tifTmpPath); Path tfwTargetPath = null; Path tifTargetPath = null; @@ -212,6 +196,41 @@ public class MapSheetMngService { return new DmlReturn("success", "파일 업로드 완료되었습니다."); } + public List findByHstUidMapSheetFileList(Long hstUid) { + return mapSheetMngCoreService.findByHstUidMapSheetFileList(hstUid); + } + + @Transactional + public DmlReturn setUseByFileUidMngFile(List fileUids) { + + long hstUid = 0; + + DmlReturn dmlReturn = new DmlReturn("success", "정상처리되었습니다."); + + MapSheetMngDto.SyncCheckStateReqUpdateDto reqDto = + new MapSheetMngDto.SyncCheckStateReqUpdateDto(); + + for (Long uid : fileUids) { + MapSheetMngDto.MngFilesDto dto = mapSheetMngCoreService.findByFileUidMapSheetFile(uid); + + reqDto.setHstUid(dto.getHstUid()); + reqDto.setFilePath(dto.getFilePath()); + reqDto.setSyncCheckState("DONE"); + + if (dto.getFileExt().equals("tif")) reqDto.setSyncCheckTifFileName(dto.getFileName()); + else if (dto.getFileExt().equals("tfw")) reqDto.setSyncCheckTfwFileName(dto.getFileName()); + + mapSheetMngCoreService.updateByFileUidFileState(uid, "DONE"); + } + + // 선택제외 삭제처리 + mapSheetMngCoreService.deleteByNotInFileUidMngFile(reqDto.getHstUid(), fileUids); + // Hst(내역) 테이블 상태 업데이트 + mapSheetMngCoreService.updateByHstUidSyncCheckState(reqDto); + + 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 오류"); @@ -223,38 +242,60 @@ public class MapSheetMngService { return new DmlReturn("success", "파일체크"); } - public List findHstUidToMapSheetFileList(Long hstUid) { - return mapSheetMngCoreService.findHstUidToMapSheetFileList(hstUid); - } + public DmlReturn duplicateFile(int mngYyyy, String tfwFileName, String tifFileName) { + int tfwCnt = mapSheetMngCoreService.findByYearFileNameFileCount(mngYyyy, tfwFileName); + int tifCnt = mapSheetMngCoreService.findByYearFileNameFileCount(mngYyyy, tifFileName); - @Transactional - public DmlReturn deleteByFileUidMngFile(List fileUids) { + if (tfwCnt > 0 || tifCnt > 0) { + String resMsg = ""; + if (tfwCnt > 0) resMsg = tfwFileName; - long hstUid = 0; - // hstUid = 149049; - - for (Long uid : fileUids) { - MapSheetMngDto.MngFilesDto dto = mapSheetMngCoreService.findIdToMapSheetFile(uid); - hstUid = dto.getHstUid(); - - String filePath = dto.getFilePath() + "/" + dto.getFileName(); - Path path = Paths.get(filePath); - try { - boolean isDeleted = Files.deleteIfExists(path); - if (isDeleted) { - System.out.println("파일 삭제 성공: " + filePath); - } else { - System.out.println("삭제 실패: 파일이 존재하지 않습니다."); - } - } catch (IOException e) { - System.err.println("파일 삭제 중 오류 발생: " + e.getMessage()); + if (tifCnt > 0) { + if (tfwCnt > 0) resMsg = resMsg + "," + tifFileName; + else resMsg = tifFileName; } - DmlReturn dmlReturn = mapSheetMngCoreService.deleteByFileUidMngFile(uid); + return new DmlReturn("duplicate", resMsg); } - // 중복제거 확인후 처리상태(DONE)변경 - if (hstUid > 0) mapSheetMngCoreService.updateByHstUidSyncCheckState(hstUid); + return new DmlReturn("success", "파일체크"); + } - return new DmlReturn("success", fileUids.size() + "개 파일이 삭제되었습니다."); + 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/model/dto/ModelMngDto.java b/src/main/java/com/kamco/cd/kamcoback/model/dto/ModelMngDto.java index 31005da6..ea9da363 100644 --- a/src/main/java/com/kamco/cd/kamcoback/model/dto/ModelMngDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/model/dto/ModelMngDto.java @@ -93,6 +93,7 @@ public class ModelMngDto { @Getter @Setter @NoArgsConstructor + @AllArgsConstructor public static class ModelList { private Integer rowNum; diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/core/InferenceResultCoreService.java b/src/main/java/com/kamco/cd/kamcoback/postgres/core/InferenceResultCoreService.java index c66d5138..8ff0c07c 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/core/InferenceResultCoreService.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/core/InferenceResultCoreService.java @@ -6,7 +6,7 @@ import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.MapSheet; import com.kamco.cd.kamcoback.postgres.entity.MapInkx5kEntity; import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataEntity; import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataGeomEntity; -import com.kamco.cd.kamcoback.postgres.repository.Inference.InferenceResultRepository; +import com.kamco.cd.kamcoback.postgres.repository.Inference.MapSheetAnalDataRepository; import com.kamco.cd.kamcoback.postgres.repository.scene.MapInkx5kRepository; import jakarta.persistence.EntityNotFoundException; import jakarta.validation.constraints.NotNull; @@ -20,7 +20,7 @@ import org.springframework.transaction.annotation.Transactional; @RequiredArgsConstructor public class InferenceResultCoreService { - private final InferenceResultRepository inferenceResultRepository; + private final MapSheetAnalDataRepository mapSheetAnalDataRepository; private final MapInkx5kRepository mapInkx5kRepository; /** @@ -31,7 +31,7 @@ public class InferenceResultCoreService { */ public Page getInferenceResultList( InferenceResultDto.SearchReq searchReq) { - return inferenceResultRepository.getInferenceResultList(searchReq); + return mapSheetAnalDataRepository.getInferenceResultList(searchReq); } /** @@ -42,7 +42,7 @@ public class InferenceResultCoreService { */ public InferenceResultDto.AnalResSummary getInferenceResultSummary(Long id) { InferenceResultDto.AnalResSummary summary = - inferenceResultRepository + mapSheetAnalDataRepository .getInferenceResultSummary(id) .orElseThrow(() -> new EntityNotFoundException("요약정보를 찾을 수 없습니다. " + id)); return summary; @@ -55,7 +55,7 @@ public class InferenceResultCoreService { * @return */ public List getDashboard(Long id) { - return inferenceResultRepository.getDashboard(id); + return mapSheetAnalDataRepository.getDashboard(id); } /** @@ -66,7 +66,7 @@ public class InferenceResultCoreService { */ public Page getInferenceResultGeomList( Long id, InferenceResultDto.SearchGeoReq searchGeoReq) { - return inferenceResultRepository.getInferenceGeomList(id, searchGeoReq); + return mapSheetAnalDataRepository.getInferenceGeomList(id, searchGeoReq); } /** @@ -80,13 +80,13 @@ public class InferenceResultCoreService { @NotNull Long analyId, InferenceResultDto.SearchGeoReq searchReq) { // 분석 ID 에 해당하는 dataids를 가져온다. List dataIds = - inferenceResultRepository.listAnalyGeom(analyId).stream() + mapSheetAnalDataRepository.listAnalyGeom(analyId).stream() .mapToLong(MapSheetAnalDataEntity::getId) .boxed() .toList(); // 해당데이터의 폴리곤데이터를 가져온다 Page mapSheetAnalDataGeomEntities = - inferenceResultRepository.listInferenceResultWithGeom(dataIds, searchReq); + mapSheetAnalDataRepository.listInferenceResultWithGeom(dataIds, searchReq); return mapSheetAnalDataGeomEntities.map(MapSheetAnalDataGeomEntity::toEntity); } @@ -97,13 +97,13 @@ public class InferenceResultCoreService { * @return */ public List getSheets(Long id) { - return inferenceResultRepository.getSheets(id); + return mapSheetAnalDataRepository.getSheets(id); } @Transactional(readOnly = true) public List listGetScenes5k(Long analyId) { List sceneCodes = - inferenceResultRepository.listAnalyGeom(analyId).stream() + mapSheetAnalDataRepository.listAnalyGeom(analyId).stream() .mapToLong(MapSheetAnalDataEntity::getMapSheetNum) .mapToObj(String::valueOf) .toList(); diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/core/InferenceResultShpCoreService.java b/src/main/java/com/kamco/cd/kamcoback/postgres/core/InferenceResultShpCoreService.java new file mode 100644 index 00000000..1a8f9052 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/core/InferenceResultShpCoreService.java @@ -0,0 +1,51 @@ +package com.kamco.cd.kamcoback.postgres.core; + +import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataInferenceGeomEntity; +import com.kamco.cd.kamcoback.postgres.repository.Inference.InferenceResultRepository; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class InferenceResultShpCoreService { + + private final InferenceResultRepository repo; + + /** inference_results -> (inference, geom) upsert */ + @Transactional + public void buildInferenceData() { + repo.upsertGroupsFromInferenceResults(); + repo.upsertGeomsFromInferenceResults(); + } + + /** file_created_yn = false/null 인 data_uid 목록 */ + @Transactional(readOnly = true) + public List findPendingDataUids(int limit) { + return repo.findPendingDataUids(limit); + } + + /** + * 재생성 시작: 덮어쓰기 기준(무조건 처음부터) - inference.file_created_yn = false, file_created_dttm = null - + * geom(file_created_yn) 전부 false 리셋 + */ + @Transactional + public void resetForRegenerate(Long dataUid) { + repo.resetInferenceCreated(dataUid); + repo.resetGeomCreatedByDataUid(dataUid); + } + + /** 생성 대상 geom 엔티티 로드 (file_created_yn=false/null + geom not null) */ + @Transactional(readOnly = true) + public List loadGeomEntities(Long dataUid, int limit) { + return repo.findGeomEntitiesByDataUid(dataUid, limit); + } + + /** 성공 마킹: - 성공 geo_uid만 geom.file_created_yn=true - inference.file_created_yn=true */ + @Transactional + public void markSuccess(Long dataUid, List geoUids) { + repo.markGeomCreatedByGeoUids(geoUids); + repo.markInferenceCreated(dataUid); + } +} 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..9850f882 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 @@ -6,16 +6,9 @@ import com.kamco.cd.kamcoback.postgres.entity.MapSheetMngHstEntity; import com.kamco.cd.kamcoback.postgres.repository.mapsheet.MapSheetMngRepository; import jakarta.persistence.EntityNotFoundException; import jakarta.validation.Valid; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; @@ -27,8 +20,6 @@ public class MapSheetMngCoreService { private final MapSheetMngRepository mapSheetMngRepository; - private static final String ORIGINAL_IMAGES_PATH = "/app/original-images"; - @Value("{spring.profiles.active}") private String activeEnv; @@ -52,10 +43,13 @@ 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); - // int hstCnt = mapSheetMngRepository.insertMapSheetOrgDataToMapSheetMngHst(saved.getMngYyyy()); return new MapSheetMngDto.DmlReturn("success", "파일정보저장되었습니다."); } @@ -73,102 +67,16 @@ public class MapSheetMngCoreService { return mapSheetMngRepository.findMapSheetError(hstUid); } - public List findIdToMapSheetFileList(Long hstUid) { - return mapSheetMngRepository.findIdToMapSheetFileList(hstUid); + public List findByHstUidMapSheetFileList(Long hstUid) { + return mapSheetMngRepository.findByHstUidMapSheetFileList(hstUid); } public MapSheetMngDto.MngFilesDto findYyyyToMapSheetFilePathRefer(int mngYyyy) { return mapSheetMngRepository.findYyyyToMapSheetFilePathRefer(mngYyyy); } - public MapSheetMngDto.MngFilesDto findIdToMapSheetFile(Long fileUid) { - return mapSheetMngRepository.findIdToMapSheetFile(fileUid); - } - - public MapSheetMngDto.DmlReturn uploadProcess(@Valid List hstUidList) { - int count = 0; - if (!Objects.isNull(hstUidList) && !hstUidList.isEmpty()) { - for (Long hstUid : hstUidList) { - Optional entity = - Optional.ofNullable( - mapSheetMngRepository - .findMapSheetMngHstInfo(hstUid) - .orElseThrow(EntityNotFoundException::new)); - - String localPath = ""; - String rootDir = ORIGINAL_IMAGES_PATH + "/" + entity.get().getMngYyyy(); - if (activeEnv.equals("local")) { - rootDir = localPath + rootDir; - } - - String filename = entity.get().getMapSheetNum(); - String[] extensions = {"tif", "tfw"}; - boolean flag = allExtensionsExist(rootDir, filename, extensions); - if (flag) { - 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; - entity.get().updateDataState(dataState); - */ - } - } - return new MapSheetMngDto.DmlReturn("success", count + "개 업로드 성공하였습니다."); + public MapSheetMngDto.MngFilesDto findByFileUidMapSheetFile(Long fileUid) { + return mapSheetMngRepository.findByFileUidMapSheetFile(fileUid); } public MapSheetMngDto.DmlReturn updateExceptUseInference(@Valid List hstUidList) { @@ -186,40 +94,7 @@ public class MapSheetMngCoreService { return new MapSheetMngDto.DmlReturn("success", hstUidList.size() + "개 추론제외 업데이트 하였습니다."); } - /** - * 특정 파일명 + 여러 확장자가 모두 존재하는지 확인 - * - * @param rootDir 검색할 최상위 디렉토리 - * @param filename 파일명 (확장자 제외) - * @param extensions 확인할 확장자 배열 (예: {"tif", "tfw"}) - * @return 모든 확장자가 존재하면 true, 하나라도 없으면 false - */ - public static boolean allExtensionsExist(String rootDir, String filename, String... extensions) { - try (Stream paths = Files.walk(Paths.get(rootDir))) { - - // 모든 파일명을 Set으로 저장 - Set fileNames = - paths - .filter(Files::isRegularFile) - .map(p -> p.getFileName().toString()) - .collect(Collectors.toSet()); - - // 모든 확장자 파일 존재 여부 확인 - for (String ext : extensions) { - String target = filename + "." + ext; - if (!fileNames.contains(target)) { - return false; // 하나라도 없으면 false - } - } - - return true; // 모두 존재하면 true - - } catch (IOException e) { - throw new RuntimeException("File search error", e); - } - } - - public MapSheetMngDto.DmlReturn mngDataSave(@Valid MapSheetMngDto.AddReq addReq) { + public int mngDataSave(@Valid MapSheetMngDto.AddReq addReq) { MapSheetMngEntity entity = new MapSheetMngEntity(); entity.setMngYyyy(addReq.getMngYyyy()); @@ -228,45 +103,31 @@ public class MapSheetMngCoreService { mapSheetMngRepository.deleteByMngYyyyMngAll(addReq.getMngYyyy()); MapSheetMngEntity saved = mapSheetMngRepository.save(entity); - int hstCnt = mapSheetMngRepository.insertMapSheetOrgDataToMapSheetMngHst(saved.getMngYyyy()); + int hstCnt = + mapSheetMngRepository.insertMapSheetOrgDataToMapSheetMngHst( + saved.getMngYyyy(), saved.getMngPath()); mapSheetMngRepository.updateYearState(saved.getMngYyyy(), "DONE"); - return new MapSheetMngDto.DmlReturn("success", saved.getMngYyyy().toString()); + return hstCnt; } public List findHstUidToMapSheetFileList(Long hstUid) { return mapSheetMngRepository.findHstUidToMapSheetFileList(hstUid); } - public MapSheetMngDto.DmlReturn deleteByFileUidMngFile(Long fileUid) { - + public void deleteByFileUidMngFile(Long fileUid) { mapSheetMngRepository.deleteByFileUidMngFile(fileUid); - - return new MapSheetMngDto.DmlReturn("success", fileUid + " : 삭제되었습니다."); } - public MapSheetMngDto.DmlReturn updateByHstUidSyncCheckState(Long hstUid) { - - MapSheetMngDto.SyncCheckStateReqUpdateDto reqDto = - new MapSheetMngDto.SyncCheckStateReqUpdateDto(); - reqDto.setHstUid(hstUid); - - List filesDto = - mapSheetMngRepository.findHstUidToMapSheetFileList(hstUid); - for (MapSheetMngDto.MngFilesDto dto : filesDto) { - if (dto.getFileExt().equals("tif")) reqDto.setSyncCheckTifFileName(dto.getFileName()); - else if (dto.getFileExt().equals("tfw")) reqDto.setSyncCheckTfwFileName(dto.getFileName()); - reqDto.setFilePath(dto.getFilePath()); - } - - String fileState = "DONE"; - if (filesDto.size() > 2) fileState = "DONE"; - - reqDto.setSyncCheckState(fileState); - + public void updateByHstUidSyncCheckState(MapSheetMngDto.SyncCheckStateReqUpdateDto reqDto) { mapSheetMngRepository.updateMapSheetMngHstSyncCheckState(reqDto); - mapSheetMngRepository.updateByHstUidMngFileState(hstUid, fileState); + } - return new MapSheetMngDto.DmlReturn("success", hstUid + " : 상태변경되었습니다."); + public void updateByFileUidFileState(Long fileUid, String fileState) { + mapSheetMngRepository.updateByFileUidMngFileState(fileUid, fileState); + } + + public void deleteByNotInFileUidMngFile(Long hstUid, List fileUids) { + mapSheetMngRepository.deleteByNotInFileUidMngFile(hstUid, fileUids); } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/InferenceResultEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/InferenceResultEntity.java new file mode 100644 index 00000000..a4c731b6 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/InferenceResultEntity.java @@ -0,0 +1,79 @@ +package com.kamco.cd.kamcoback.postgres.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.time.ZonedDateTime; +import java.util.UUID; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.annotations.ColumnDefault; +import org.locationtech.jts.geom.Geometry; + +@Getter +@Setter +@Entity +@Table(name = "inference_results") +public class InferenceResultEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "uid", nullable = false) + private Long id; + + @NotNull + @ColumnDefault("uuid_generate_v4()") + @Column(name = "uuid", nullable = false) + private UUID uuid; + + @Column(name = "stage") + private Integer stage; + + @Column(name = "cd_prob") + private Float cdProb; + + @Column(name = "input1") + private Integer input1; + + @Column(name = "input2") + private Integer input2; + + @Column(name = "map_id") + private Long mapId; + + @Size(max = 20) + @Column(name = "before_class", length = 20) + private String beforeClass; + + @Column(name = "before_probability") + private Float beforeProbability; + + @Size(max = 20) + @Column(name = "after_class", length = 20) + private String afterClass; + + @Column(name = "after_probability") + private Float afterProbability; + + @ColumnDefault("st_area(geometry)") + @Column(name = "area") + private Float area; + + @NotNull + @ColumnDefault("now()") + @Column(name = "created_dttm", nullable = false) + private ZonedDateTime createdDttm; + + @NotNull + @ColumnDefault("now()") + @Column(name = "updated_dttm", nullable = false) + private ZonedDateTime updatedDttm; + + @Column(name = "geometry", columnDefinition = "geometry not null") + private Geometry geometry; +} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetAnalDataInferenceEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetAnalDataInferenceEntity.java new file mode 100644 index 00000000..ff73d651 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetAnalDataInferenceEntity.java @@ -0,0 +1,167 @@ +package com.kamco.cd.kamcoback.postgres.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.validation.constraints.Size; +import java.time.ZonedDateTime; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.annotations.ColumnDefault; + +@Getter +@Setter +@Entity +@Table(name = "tb_map_sheet_anal_data_inference") +public class MapSheetAnalDataInferenceEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "data_uid", nullable = false) + private Long id; + + @Size(max = 128) + @Column(name = "data_name", length = 128) + private String dataName; + + @Size(max = 255) + @Column(name = "data_path") + private String dataPath; + + @Size(max = 128) + @Column(name = "data_type", length = 128) + private String dataType; + + @Size(max = 128) + @Column(name = "data_crs_type", length = 128) + private String dataCrsType; + + @Size(max = 255) + @Column(name = "data_crs_type_name") + private String dataCrsTypeName; + + @ColumnDefault("now()") + @Column(name = "created_dttm") + private ZonedDateTime createdDttm; + + @Column(name = "created_uid") + private Long createdUid; + + @ColumnDefault("now()") + @Column(name = "updated_dttm") + private ZonedDateTime updatedDttm; + + @Column(name = "updated_uid") + private Long updatedUid; + + @Column(name = "compare_yyyy") + private Integer compareYyyy; + + @Column(name = "target_yyyy") + private Integer targetYyyy; + + @Column(name = "data_json", length = Integer.MAX_VALUE) + private String dataJson; + + @Size(max = 20) + @ColumnDefault("'0'") + @Column(name = "data_state", length = 20) + private String dataState; + + @ColumnDefault("now()") + @Column(name = "data_state_dttm") + private ZonedDateTime dataStateDttm; + + @Column(name = "anal_strt_dttm") + private ZonedDateTime analStrtDttm; + + @Column(name = "anal_end_dttm") + private ZonedDateTime analEndDttm; + + @ColumnDefault("0") + @Column(name = "anal_sec") + private Long analSec; + + @Size(max = 20) + @Column(name = "anal_state", length = 20) + private String analState; + + @Column(name = "anal_uid") + private Long analUid; + + @Column(name = "map_sheet_num") + private Long mapSheetNum; + + @ColumnDefault("0") + @Column(name = "detecting_cnt") + private Long detectingCnt; + + @ColumnDefault("0") + @Column(name = "pnu") + private Long pnu; + + @Size(max = 20) + @Column(name = "down_state", length = 20) + private String downState; + + @Column(name = "down_state_dttm") + private ZonedDateTime downStateDttm; + + @Size(max = 20) + @Column(name = "fit_state", length = 20) + private String fitState; + + @Column(name = "fit_state_dttm") + private ZonedDateTime fitStateDttm; + + @Column(name = "labeler_uid") + private Long labelerUid; + + @Size(max = 20) + @ColumnDefault("NULL") + @Column(name = "label_state", length = 20) + private String labelState; + + @Column(name = "label_state_dttm") + private ZonedDateTime labelStateDttm; + + @Column(name = "tester_uid") + private Long testerUid; + + @Size(max = 20) + @Column(name = "test_state", length = 20) + private String testState; + + @Column(name = "test_state_dttm") + private ZonedDateTime testStateDttm; + + @Column(name = "fit_state_cmmnt", length = Integer.MAX_VALUE) + private String fitStateCmmnt; + + @Column(name = "ref_map_sheet_num") + private Long refMapSheetNum; + + @Column(name = "stage") + private Integer stage; + + @Column(name = "file_created_yn") + private Boolean fileCreatedYn; + + @Size(max = 100) + @Column(name = "m1", length = 100) + private String m1; + + @Size(max = 100) + @Column(name = "m2", length = 100) + private String m2; + + @Size(max = 100) + @Column(name = "m3", length = 100) + private String m3; + + @Column(name = "file_created_dttm") + private ZonedDateTime fileCreatedDttm; +} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetAnalDataInferenceGeomEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetAnalDataInferenceGeomEntity.java new file mode 100644 index 00000000..7ce986d6 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetAnalDataInferenceGeomEntity.java @@ -0,0 +1,149 @@ +package com.kamco.cd.kamcoback.postgres.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.validation.constraints.Size; +import java.time.ZonedDateTime; +import java.util.UUID; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.annotations.ColumnDefault; +import org.locationtech.jts.geom.Geometry; + +@Getter +@Setter +@Entity +@Table(name = "tb_map_sheet_anal_data_inference_geom") +public class MapSheetAnalDataInferenceGeomEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "geo_uid") + private Long geoUid; + + @Column(name = "cd_prob") + private Float cdProb; + + @Size(max = 40) + @Column(name = "class_before_cd", length = 40) + private String classBeforeCd; + + @Column(name = "class_before_prob") + private Float classBeforeProb; + + @Size(max = 40) + @Column(name = "class_after_cd", length = 40) + private String classAfterCd; + + @Column(name = "class_after_prob") + private Float classAfterProb; + + @Column(name = "map_sheet_num") + private Long mapSheetNum; + + @Column(name = "compare_yyyy") + private Integer compareYyyy; + + @Column(name = "target_yyyy") + private Integer targetYyyy; + + @Column(name = "area") + private Float area; + + @Size(max = 100) + @Column(name = "geo_type", length = 100) + private String geoType; + + @Column(name = "data_uid") + private Long dataUid; + + @ColumnDefault("now()") + @Column(name = "created_dttm") + private ZonedDateTime createdDttm; + + @Column(name = "created_uid") + private Long createdUid; + + @ColumnDefault("now()") + @Column(name = "updated_dttm") + private ZonedDateTime updatedDttm; + + @Column(name = "updated_uid") + private Long updatedUid; + + @ColumnDefault("0") + @Column(name = "geom_cnt") + private Long geomCnt; + + @ColumnDefault("0") + @Column(name = "pnu") + private Long pnu; + + @Size(max = 20) + @ColumnDefault("'0'") + @Column(name = "fit_state", length = 20) + private String fitState; + + @ColumnDefault("now()") + @Column(name = "fit_state_dttm") + private ZonedDateTime fitStateDttm; + + @Column(name = "labeler_uid") + private Long labelerUid; + + @Size(max = 20) + @ColumnDefault("'0'") + @Column(name = "label_state", length = 20) + private String labelState; + + @ColumnDefault("now()") + @Column(name = "label_state_dttm") + private ZonedDateTime labelStateDttm; + + @Column(name = "tester_uid") + private Long testerUid; + + @Size(max = 20) + @ColumnDefault("'0'") + @Column(name = "test_state", length = 20) + private String testState; + + @ColumnDefault("now()") + @Column(name = "test_state_dttm") + private ZonedDateTime testStateDttm; + + @Column(name = "fit_state_cmmnt", length = Integer.MAX_VALUE) + private String fitStateCmmnt; + + @Column(name = "ref_map_sheet_num") + private Long refMapSheetNum; + + @ColumnDefault("uuid_generate_v4()") + @Column(name = "uuid") + private UUID uuid; + + @Column(name = "stage") + private Integer stage; + + @Column(name = "map_5k_id") + private Long map5kId; + + @Column(name = "file_created_yn") + private Boolean fileCreatedYn; + + @Column(name = "geom", columnDefinition = "geometry") + private Geometry geom; + + @Column(name = "geom_center", columnDefinition = "geometry") + private Geometry geomCenter; + + @Column(name = "before_geom", columnDefinition = "geometry") + private Geometry beforeGeom; + + @Column(name = "file_created_dttm") + private ZonedDateTime fileCreatedDttm; +} 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 70542b4f..cadf2ce2 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 @@ -10,6 +10,7 @@ import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import lombok.Getter; import lombok.Setter; +import org.hibernate.annotations.ColumnDefault; @Getter @Setter @@ -54,4 +55,9 @@ public class MapSheetMngFileEntity { @Size(max = 20) @Column(name = "file_state", length = 20) private String fileState; + + @NotNull + @ColumnDefault("false") + @Column(name = "file_del", nullable = false) + private Boolean fileDel = false; } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepository.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepository.java index 524b462a..9a6ef2a8 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepository.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepository.java @@ -1,7 +1,7 @@ package com.kamco.cd.kamcoback.postgres.repository.Inference; -import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalEntity; import org.springframework.data.jpa.repository.JpaRepository; public interface InferenceResultRepository - extends JpaRepository, InferenceResultRepositoryCustom {} + extends JpaRepository, + InferenceResultRepositoryCustom {} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryCustom.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryCustom.java index 7b9ac305..3808d7d9 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryCustom.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryCustom.java @@ -1,31 +1,23 @@ package com.kamco.cd.kamcoback.postgres.repository.Inference; -import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto; -import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.Dashboard; -import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.SearchGeoReq; -import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataEntity; -import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataGeomEntity; -import jakarta.validation.constraints.NotNull; +import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataInferenceGeomEntity; import java.util.List; -import java.util.Optional; -import org.springframework.data.domain.Page; public interface InferenceResultRepositoryCustom { - Page getInferenceResultList( - InferenceResultDto.SearchReq searchReq); + int upsertGroupsFromInferenceResults(); - Optional getInferenceResultSummary(Long id); + int upsertGeomsFromInferenceResults(); - Page getInferenceGeomList( - Long id, InferenceResultDto.SearchGeoReq searchGeoReq); + List findPendingDataUids(int limit); - Page listInferenceResultWithGeom( - List dataIds, SearchGeoReq searchReq); + int resetInferenceCreated(Long dataUid); - List getSheets(Long id); + int markInferenceCreated(Long dataUid); - List getDashboard(Long id); + int resetGeomCreatedByDataUid(Long dataUid); - List listAnalyGeom(@NotNull Long id); + int markGeomCreatedByGeoUids(List geoUids); + + List findGeomEntitiesByDataUid(Long dataUid, int limit); } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java index 58e7e818..05c085de 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java @@ -1,27 +1,14 @@ package com.kamco.cd.kamcoback.postgres.repository.Inference; -import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto; -import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.Dashboard; -import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.SearchGeoReq; -import com.kamco.cd.kamcoback.postgres.entity.*; -import com.querydsl.core.BooleanBuilder; -import com.querydsl.core.types.Order; -import com.querydsl.core.types.OrderSpecifier; -import com.querydsl.core.types.Projections; -import com.querydsl.core.types.dsl.BooleanExpression; -import com.querydsl.core.types.dsl.Expressions; -import com.querydsl.jpa.JPAExpressions; -import com.querydsl.jpa.JPQLQuery; -import com.querydsl.jpa.impl.JPAQuery; +import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataInferenceGeomEntity; +import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceEntity; +import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceGeomEntity; import com.querydsl.jpa.impl.JPAQueryFactory; -import java.util.ArrayList; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import java.time.ZonedDateTime; import java.util.List; -import java.util.Optional; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; import org.springframework.stereotype.Repository; @Repository @@ -29,335 +16,215 @@ import org.springframework.stereotype.Repository; public class InferenceResultRepositoryImpl implements InferenceResultRepositoryCustom { private final JPAQueryFactory queryFactory; - private final QModelMngBakEntity tmm = QModelMngBakEntity.modelMngBakEntity; - private final QModelVerEntity tmv = QModelVerEntity.modelVerEntity; - private final QMapSheetAnalEntity mapSheetAnalEntity = QMapSheetAnalEntity.mapSheetAnalEntity; - private final QMapSheetAnalDataEntity mapSheetAnalDataEntity = - QMapSheetAnalDataEntity.mapSheetAnalDataEntity; - private final QMapSheetAnalDataGeomEntity mapSheetAnalDataGeomEntity = - QMapSheetAnalDataGeomEntity.mapSheetAnalDataGeomEntity; - private final QMapSheetAnalSttcEntity mapSheetAnalSttcEntity = - QMapSheetAnalSttcEntity.mapSheetAnalSttcEntity; - /** - * 분석결과 목록 조회 - * - * @param searchReq - * @return - */ + @PersistenceContext private final EntityManager em; + + private final QMapSheetAnalDataInferenceEntity i = + QMapSheetAnalDataInferenceEntity.mapSheetAnalDataInferenceEntity; + + private final QMapSheetAnalDataInferenceGeomEntity g = + QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity; + + // =============================== + // Upsert (Native only) + // =============================== + @Override - public Page getInferenceResultList( - InferenceResultDto.SearchReq searchReq) { - Pageable pageable = searchReq.toPageable(); - // "0000" 전체조회 - BooleanBuilder builder = new BooleanBuilder(); - if (searchReq.getStatCode() != null && !"0000".equals(searchReq.getStatCode())) { - builder.and(mapSheetAnalEntity.analState.eq(searchReq.getStatCode())); - } + public int upsertGroupsFromInferenceResults() { - // 제목 - if (searchReq.getTitle() != null) { - builder.and(mapSheetAnalEntity.analTitle.like("%" + searchReq.getTitle() + "%")); - } + String sql = + """ + INSERT INTO tb_map_sheet_anal_data_inference ( + stage, + compare_yyyy, + target_yyyy, + map_sheet_num, + created_dttm, + updated_dttm, + file_created_yn, + detecting_cnt + ) + SELECT + r.stage, + r.input1 AS compare_yyyy, + r.input2 AS target_yyyy, + r.map_id AS map_sheet_num, + now() AS created_dttm, + now() AS updated_dttm, + false AS file_created_yn, + count(*) AS detecting_cnt + FROM inference_results r + GROUP BY r.stage, r.input1, r.input2, r.map_id + ON CONFLICT (stage, compare_yyyy, target_yyyy, map_sheet_num) + DO UPDATE SET + updated_dttm = now(), + detecting_cnt = EXCLUDED.detecting_cnt + """; - List content = - queryFactory - .select( - Projections.constructor( - InferenceResultDto.AnalResList.class, - mapSheetAnalEntity.id, - mapSheetAnalEntity.analTitle, - mapSheetAnalEntity.analMapSheet, - mapSheetAnalEntity.detectingCnt, - mapSheetAnalEntity.analStrtDttm, - mapSheetAnalEntity.analEndDttm, - mapSheetAnalEntity.analSec, - mapSheetAnalEntity.analPredSec, - mapSheetAnalEntity.analState, - Expressions.stringTemplate( - "fn_code_name({0}, {1})", "0002", mapSheetAnalEntity.analState), - mapSheetAnalEntity.gukyuinUsed)) - .from(mapSheetAnalEntity) - .where(builder) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .orderBy(mapSheetAnalEntity.id.desc()) - .fetch(); - - long total = - queryFactory - .select(mapSheetAnalEntity.id) - .from(mapSheetAnalEntity) - .where(builder) - .fetchCount(); - - return new PageImpl<>(content, pageable, total); + return em.createNativeQuery(sql).executeUpdate(); } - /** - * 분석결과 요약정보 - * - * @param id - * @return - */ @Override - public Optional getInferenceResultSummary(Long id) { + public int upsertGeomsFromInferenceResults() { - // 1. 최신 버전 UID를 가져오는 서브쿼리 - JPQLQuery latestVerUidSub = - JPAExpressions.select(tmv.id.max()).from(tmv).where(tmv.modelUid.eq(tmm.id)); + // class_after_prob 컬럼 매핑 오타 주의(여기 제대로 넣음) + String sql = + """ + INSERT INTO tb_map_sheet_anal_data_inference_geom ( + uuid, stage, cd_prob, compare_yyyy, target_yyyy, map_sheet_num, + class_before_cd, class_before_prob, class_after_cd, class_after_prob, + geom, area, data_uid, created_dttm, updated_dttm, + file_created_yn + ) + SELECT + x.uuid, x.stage, x.cd_prob, x.compare_yyyy, x.target_yyyy, x.map_sheet_num, + x.class_before_cd, x.class_before_prob, x.class_after_cd, x.class_after_prob, + x.geom, x.area, x.data_uid, x.created_dttm, x.updated_dttm, + false AS file_created_yn + FROM ( + SELECT DISTINCT ON (r.uuid) + r.uuid, + r.stage, + r.cd_prob, + r.input1 AS compare_yyyy, + r.input2 AS target_yyyy, + r.map_id AS map_sheet_num, + r.before_class AS class_before_cd, + r.before_probability AS class_before_prob, + r.after_class AS class_after_cd, + r.after_probability AS class_after_prob, + CASE + WHEN r.geometry IS NULL THEN NULL + WHEN left(r.geometry, 2) = '01' + THEN ST_SetSRID(ST_GeomFromWKB(decode(r.geometry, 'hex')), 5186) + ELSE ST_GeomFromText(r.geometry, 5186) + END AS geom, + r.area, + di.data_uid, + r.created_dttm, + r.updated_dttm + FROM inference_results r + JOIN tb_map_sheet_anal_data_inference di + ON di.stage = r.stage + AND di.compare_yyyy = r.input1 + AND di.target_yyyy = r.input2 + AND di.map_sheet_num = r.map_id + ORDER BY r.uuid, r.updated_dttm DESC NULLS LAST, r.uid DESC + ) x + ON CONFLICT (uuid) + DO UPDATE SET + stage = EXCLUDED.stage, + cd_prob = EXCLUDED.cd_prob, + compare_yyyy = EXCLUDED.compare_yyyy, + target_yyyy = EXCLUDED.target_yyyy, + map_sheet_num = EXCLUDED.map_sheet_num, + class_before_cd = EXCLUDED.class_before_cd, + class_before_prob = EXCLUDED.class_before_prob, + class_after_cd = EXCLUDED.class_after_cd, + class_after_prob = EXCLUDED.class_after_prob, + geom = EXCLUDED.geom, + area = EXCLUDED.area, + data_uid = EXCLUDED.data_uid, + updated_dttm = now() + """; - Optional content = - Optional.ofNullable( - queryFactory - .select( - Projections.constructor( - InferenceResultDto.AnalResSummary.class, - mapSheetAnalEntity.id, - mapSheetAnalEntity.analTitle, - tmm.modelNm.concat(" ").concat(tmv.modelVer).as("modelInfo"), - mapSheetAnalEntity.targetYyyy, - mapSheetAnalEntity.compareYyyy, - mapSheetAnalEntity.analMapSheet, - mapSheetAnalEntity.analStrtDttm, - mapSheetAnalEntity.analEndDttm, - mapSheetAnalEntity.analSec, - mapSheetAnalEntity.analPredSec, - mapSheetAnalEntity.resultUrl, - mapSheetAnalEntity.detectingCnt, - mapSheetAnalEntity.accuracy, - mapSheetAnalEntity.analState, - Expressions.stringTemplate( - "fn_code_name({0}, {1})", "0002", mapSheetAnalEntity.analState))) - .from(mapSheetAnalEntity) - .leftJoin(tmm) - .on(mapSheetAnalEntity.modelUid.eq(tmm.id)) - .leftJoin(tmv) - .on(tmv.modelUid.eq(tmm.id).and(tmv.id.eq(latestVerUidSub))) - .where(mapSheetAnalEntity.id.eq(id)) - .fetchOne()); - return content; + return em.createNativeQuery(sql).executeUpdate(); } - /** - * 분석결과 상세 class name별 탐지 개수 - * - * @param id - * @return - */ + // =============================== + // Jobs + // =============================== + @Override - public List getDashboard(Long id) { + public List findPendingDataUids(int limit) { return queryFactory - .select( - Projections.constructor( - Dashboard.class, - mapSheetAnalSttcEntity.id.classAfterCd, - mapSheetAnalSttcEntity.classAfterCnt.sum())) - .from(mapSheetAnalSttcEntity) - .where(mapSheetAnalSttcEntity.id.analUid.eq(id)) - .groupBy(mapSheetAnalSttcEntity.id.classAfterCd) - .orderBy(mapSheetAnalSttcEntity.id.classAfterCd.asc()) + .select(i.id) // data_uid + .from(i) + .where(i.fileCreatedYn.isFalse().or(i.fileCreatedYn.isNull())) + .orderBy(i.id.asc()) + .limit(limit) .fetch(); } + // =============================== + // Reset / Mark (전부 ZonedDateTime) + // =============================== + @Override - public List listAnalyGeom(Long id) { - QMapSheetAnalDataEntity analy = QMapSheetAnalDataEntity.mapSheetAnalDataEntity; - return queryFactory.selectFrom(analy).where(analy.analUid.eq(id)).fetch(); + public int resetInferenceCreated(Long dataUid) { + ZonedDateTime now = ZonedDateTime.now(); + + return (int) + queryFactory + .update(i) + .set(i.fileCreatedYn, false) + .set(i.fileCreatedDttm, (ZonedDateTime) null) + .set(i.updatedDttm, now) + .where(i.id.eq(dataUid)) + .execute(); } - /** - * 분석결과 상세 목록 - * - * @param searchReq - * @return - */ @Override - public Page listInferenceResultWithGeom( - List ids, SearchGeoReq searchReq) { + public int markInferenceCreated(Long dataUid) { + ZonedDateTime now = ZonedDateTime.now(); - // 분석 차수 - QMapSheetAnalDataGeomEntity detectedEntity = - QMapSheetAnalDataGeomEntity.mapSheetAnalDataGeomEntity; - Pageable pageable = searchReq.toPageable(); - - // 검색조건 - JPAQuery query = + return (int) queryFactory - .selectFrom(detectedEntity) - .where( - detectedEntity.dataUid.in(ids), - eqTargetClass(detectedEntity, searchReq.getTargetClass()), - eqCompareClass(detectedEntity, searchReq.getCompareClass()), - containsMapSheetNum(detectedEntity, searchReq.getMapSheetNum())); - - // count - long total = query.fetchCount(); - - // Pageable에서 정렬 가져오기, 없으면 기본 정렬(createdDttm desc) 사용 - List> orders = getOrderSpecifiers(pageable.getSort()); - if (orders.isEmpty()) { - orders.add(detectedEntity.createdDttm.desc()); - } - - List content = - query - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .orderBy(orders.toArray(new OrderSpecifier[0])) - .fetch(); - - return new PageImpl<>(content, pageable, total); + .update(i) + .set(i.fileCreatedYn, true) + .set(i.fileCreatedDttm, now) + .set(i.updatedDttm, now) + .where(i.id.eq(dataUid)) + .execute(); } - /** - * 분석결과 상세 목록 - * - * @param searchGeoReq - * @return - */ @Override - public Page getInferenceGeomList(Long id, SearchGeoReq searchGeoReq) { - Pageable pageable = searchGeoReq.toPageable(); - BooleanBuilder builder = new BooleanBuilder(); + public int resetGeomCreatedByDataUid(Long dataUid) { + ZonedDateTime now = ZonedDateTime.now(); - // 추론결과 id - builder.and(mapSheetAnalEntity.id.eq(id)); - - // 기준년도 분류 - if (searchGeoReq.getTargetClass() != null && !searchGeoReq.getTargetClass().equals("")) { - builder.and( - mapSheetAnalDataGeomEntity - .classAfterCd - .toLowerCase() - .eq(searchGeoReq.getTargetClass().toLowerCase())); - } - - // 비교년도 분류 - if (searchGeoReq.getCompareClass() != null && !searchGeoReq.getCompareClass().equals("")) { - builder.and( - mapSheetAnalDataGeomEntity - .classBeforeCd - .toLowerCase() - .eq(searchGeoReq.getCompareClass().toLowerCase())); - } - - // 분석도엽 - if (searchGeoReq.getMapSheetNum() != null && !searchGeoReq.getMapSheetNum().isEmpty()) { - List mapSheetNum = searchGeoReq.getMapSheetNum(); - builder.and(mapSheetAnalDataGeomEntity.mapSheetNum.in(mapSheetNum)); - } - - List content = + return (int) queryFactory - .select( - Projections.constructor( - InferenceResultDto.Geom.class, - mapSheetAnalDataGeomEntity.compareYyyy, - mapSheetAnalDataGeomEntity.targetYyyy, - mapSheetAnalDataGeomEntity.classBeforeCd, - mapSheetAnalDataGeomEntity.classBeforeProb, - mapSheetAnalDataGeomEntity.classAfterCd, - mapSheetAnalDataGeomEntity.classAfterProb, - mapSheetAnalDataGeomEntity.mapSheetNum, - mapSheetAnalDataGeomEntity.geom, - mapSheetAnalDataGeomEntity.geomCenter)) - .from(mapSheetAnalEntity) - .join(mapSheetAnalDataEntity) - .on(mapSheetAnalDataEntity.analUid.eq(mapSheetAnalEntity.id)) - .join(mapSheetAnalDataGeomEntity) - .on(mapSheetAnalDataGeomEntity.dataUid.eq(mapSheetAnalDataEntity.id)) - .where(builder) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .fetch(); - - long total = - queryFactory - .select(mapSheetAnalDataGeomEntity.id) - .from(mapSheetAnalEntity) - .join(mapSheetAnalDataEntity) - .on(mapSheetAnalDataEntity.analUid.eq(mapSheetAnalEntity.id)) - .join(mapSheetAnalDataGeomEntity) - .on(mapSheetAnalDataGeomEntity.dataUid.eq(mapSheetAnalDataEntity.id)) - .where(builder) - .fetchCount(); - - return new PageImpl<>(content, pageable, total); + .update(g) + .set(g.fileCreatedYn, false) + .set(g.fileCreatedDttm, (ZonedDateTime) null) + .set(g.updatedDttm, now) // ✅ 엔티티/Q가 ZonedDateTime이면 정상 + .where(g.dataUid.eq(dataUid)) + .execute(); } - /** - * 추론된 5000:1 도엽 목록 - * - * @param id - * @return - */ @Override - public List getSheets(Long id) { + public int markGeomCreatedByGeoUids(List geoUids) { + if (geoUids == null || geoUids.isEmpty()) { + return 0; + } + + ZonedDateTime now = ZonedDateTime.now(); + + return (int) + queryFactory + .update(g) + .set(g.fileCreatedYn, true) + .set(g.fileCreatedDttm, now) + .set(g.updatedDttm, now) + .where(g.geoUid.in(geoUids)) + .execute(); + } + + // =============================== + // Export source (Entity only) + // =============================== + + @Override + public List findGeomEntitiesByDataUid( + Long dataUid, int limit) { return queryFactory - .select(mapSheetAnalDataEntity.mapSheetNum) - .from(mapSheetAnalEntity) - .join(mapSheetAnalDataEntity) - .on(mapSheetAnalDataEntity.analUid.eq(mapSheetAnalEntity.id)) - .where(mapSheetAnalEntity.id.eq(id)) - .groupBy(mapSheetAnalDataEntity.mapSheetNum) + .selectFrom(g) + .where( + g.dataUid.eq(dataUid), + g.geom.isNotNull(), + g.fileCreatedYn.isFalse().or(g.fileCreatedYn.isNull())) + .orderBy(g.geoUid.asc()) + .limit(limit) .fetch(); } - - /** Pageable의 Sort를 QueryDSL OrderSpecifier로 변환 */ - @SuppressWarnings({"unchecked", "rawtypes"}) - private List> getOrderSpecifiers(Sort sort) { - List> orders = new ArrayList<>(); - - if (sort.isSorted()) { - QMapSheetAnalDataGeomEntity entity = QMapSheetAnalDataGeomEntity.mapSheetAnalDataGeomEntity; - - for (Sort.Order order : sort) { - Order direction = order.isAscending() ? Order.ASC : Order.DESC; - String property = order.getProperty(); - - // 유효한 필드만 처리 - switch (property) { - case "classBeforeCd" -> orders.add(new OrderSpecifier(direction, entity.classBeforeCd)); - case "classBeforeProb" -> - orders.add(new OrderSpecifier(direction, entity.classBeforeProb)); - case "classAfterCd" -> orders.add(new OrderSpecifier(direction, entity.classAfterCd)); - case "classAfterProb" -> orders.add(new OrderSpecifier(direction, entity.classAfterProb)); - case "mapSheetNum" -> orders.add(new OrderSpecifier(direction, entity.mapSheetNum)); - case "compareYyyy" -> orders.add(new OrderSpecifier(direction, entity.compareYyyy)); - case "targetYyyy" -> orders.add(new OrderSpecifier(direction, entity.targetYyyy)); - case "area" -> orders.add(new OrderSpecifier(direction, entity.area)); - case "createdDttm" -> orders.add(new OrderSpecifier(direction, entity.createdDttm)); - case "updatedDttm" -> orders.add(new OrderSpecifier(direction, entity.updatedDttm)); - // 유효하지 않은 필드는 무시 - default -> {} - } - } - } - - return orders; - } - - private BooleanExpression eqTargetClass( - QMapSheetAnalDataGeomEntity detectedEntity, String targetClass) { - return targetClass != null && !targetClass.isEmpty() - ? detectedEntity.classAfterCd.toLowerCase().eq(targetClass.toLowerCase()) - : null; - } - - private BooleanExpression eqCompareClass( - QMapSheetAnalDataGeomEntity detectedEntity, String compareClass) { - return compareClass != null && !compareClass.isEmpty() - ? detectedEntity.classBeforeCd.toLowerCase().eq(compareClass.toLowerCase()) - : null; - } - - private BooleanExpression containsMapSheetNum( - QMapSheetAnalDataGeomEntity detectedEntity, List mapSheet) { - if (mapSheet == null || mapSheet.isEmpty()) { - return null; - } - - return detectedEntity.mapSheetNum.in(mapSheet); - } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetAnalDataInferenceGeomRepository.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetAnalDataInferenceGeomRepository.java new file mode 100644 index 00000000..8b104ec7 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetAnalDataInferenceGeomRepository.java @@ -0,0 +1,8 @@ +package com.kamco.cd.kamcoback.postgres.repository.Inference; + +import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataInferenceGeomEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MapSheetAnalDataInferenceGeomRepository + extends JpaRepository, + MapSheetAnalDataInferenceGeomRepositoryCustom {} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetAnalDataInferenceGeomRepositoryCustom.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetAnalDataInferenceGeomRepositoryCustom.java new file mode 100644 index 00000000..7ba4b827 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetAnalDataInferenceGeomRepositoryCustom.java @@ -0,0 +1,3 @@ +package com.kamco.cd.kamcoback.postgres.repository.Inference; + +public interface MapSheetAnalDataInferenceGeomRepositoryCustom {} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetAnalDataInferenceGeomRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetAnalDataInferenceGeomRepositoryImpl.java new file mode 100644 index 00000000..c357036e --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetAnalDataInferenceGeomRepositoryImpl.java @@ -0,0 +1,9 @@ +package com.kamco.cd.kamcoback.postgres.repository.Inference; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class MapSheetAnalDataInferenceGeomRepositoryImpl + implements MapSheetAnalDataInferenceGeomRepositoryCustom {} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetAnalDataInferenceRepository.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetAnalDataInferenceRepository.java new file mode 100644 index 00000000..784f8b01 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetAnalDataInferenceRepository.java @@ -0,0 +1,8 @@ +package com.kamco.cd.kamcoback.postgres.repository.Inference; + +import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataInferenceEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MapSheetAnalDataInferenceRepository + extends JpaRepository, + MapSheetAnalDataInferenceRepositoryCustom {} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetAnalDataInferenceRepositoryCustom.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetAnalDataInferenceRepositoryCustom.java new file mode 100644 index 00000000..edcb6120 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetAnalDataInferenceRepositoryCustom.java @@ -0,0 +1,3 @@ +package com.kamco.cd.kamcoback.postgres.repository.Inference; + +public interface MapSheetAnalDataInferenceRepositoryCustom {} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetAnalDataInferenceRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetAnalDataInferenceRepositoryImpl.java new file mode 100644 index 00000000..352a512b --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetAnalDataInferenceRepositoryImpl.java @@ -0,0 +1,13 @@ +package com.kamco.cd.kamcoback.postgres.repository.Inference; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class MapSheetAnalDataInferenceRepositoryImpl + implements MapSheetAnalDataInferenceRepositoryCustom { + + private final JPAQueryFactory queryFactory; +} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetAnalDataRepository.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetAnalDataRepository.java new file mode 100644 index 00000000..870315d3 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetAnalDataRepository.java @@ -0,0 +1,7 @@ +package com.kamco.cd.kamcoback.postgres.repository.Inference; + +import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MapSheetAnalDataRepository + extends JpaRepository, MapSheetAnalDataRepositoryCustom {} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetAnalDataRepositoryCustom.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetAnalDataRepositoryCustom.java new file mode 100644 index 00000000..3a416b15 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetAnalDataRepositoryCustom.java @@ -0,0 +1,32 @@ +package com.kamco.cd.kamcoback.postgres.repository.Inference; + +import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto; +import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.AnalResList; +import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.AnalResSummary; +import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.Dashboard; +import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.SearchGeoReq; +import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataEntity; +import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataGeomEntity; +import jakarta.validation.constraints.NotNull; +import java.util.List; +import java.util.Optional; +import org.springframework.data.domain.Page; + +public interface MapSheetAnalDataRepositoryCustom { + + Page getInferenceResultList(InferenceResultDto.SearchReq searchReq); + + Optional getInferenceResultSummary(Long id); + + Page getInferenceGeomList( + Long id, InferenceResultDto.SearchGeoReq searchGeoReq); + + Page listInferenceResultWithGeom( + List dataIds, SearchGeoReq searchReq); + + List getSheets(Long id); + + List getDashboard(Long id); + + List listAnalyGeom(@NotNull Long id); +} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetAnalDataRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetAnalDataRepositoryImpl.java new file mode 100644 index 00000000..05d567a0 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetAnalDataRepositoryImpl.java @@ -0,0 +1,371 @@ +package com.kamco.cd.kamcoback.postgres.repository.Inference; + +import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto; +import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.AnalResList; +import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.AnalResSummary; +import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.Dashboard; +import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.SearchGeoReq; +import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataEntity; +import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataGeomEntity; +import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataEntity; +import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataGeomEntity; +import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalEntity; +import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalSttcEntity; +import com.kamco.cd.kamcoback.postgres.entity.QModelMngBakEntity; +import com.kamco.cd.kamcoback.postgres.entity.QModelVerEntity; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.core.types.Order; +import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.jpa.JPAExpressions; +import com.querydsl.jpa.JPQLQuery; +import com.querydsl.jpa.impl.JPAQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class MapSheetAnalDataRepositoryImpl implements MapSheetAnalDataRepositoryCustom { + + private final JPAQueryFactory queryFactory; + private final QModelMngBakEntity tmm = QModelMngBakEntity.modelMngBakEntity; + private final QModelVerEntity tmv = QModelVerEntity.modelVerEntity; + private final QMapSheetAnalEntity mapSheetAnalEntity = QMapSheetAnalEntity.mapSheetAnalEntity; + private final QMapSheetAnalDataEntity mapSheetAnalDataEntity = + QMapSheetAnalDataEntity.mapSheetAnalDataEntity; + private final QMapSheetAnalDataGeomEntity mapSheetAnalDataGeomEntity = + QMapSheetAnalDataGeomEntity.mapSheetAnalDataGeomEntity; + private final QMapSheetAnalSttcEntity mapSheetAnalSttcEntity = + QMapSheetAnalSttcEntity.mapSheetAnalSttcEntity; + + /** + * 분석결과 목록 조회 + * + * @param searchReq + * @return + */ + @Override + public Page getInferenceResultList(InferenceResultDto.SearchReq searchReq) { + Pageable pageable = searchReq.toPageable(); + // "0000" 전체조회 + BooleanBuilder builder = new BooleanBuilder(); + if (searchReq.getStatCode() != null && !"0000".equals(searchReq.getStatCode())) { + builder.and(mapSheetAnalEntity.analState.eq(searchReq.getStatCode())); + } + + // 제목 + if (searchReq.getTitle() != null) { + builder.and(mapSheetAnalEntity.analTitle.like("%" + searchReq.getTitle() + "%")); + } + + List content = + queryFactory + .select( + Projections.constructor( + InferenceResultDto.AnalResList.class, + mapSheetAnalEntity.id, + mapSheetAnalEntity.analTitle, + mapSheetAnalEntity.analMapSheet, + mapSheetAnalEntity.detectingCnt, + mapSheetAnalEntity.analStrtDttm, + mapSheetAnalEntity.analEndDttm, + mapSheetAnalEntity.analSec, + mapSheetAnalEntity.analPredSec, + mapSheetAnalEntity.analState, + Expressions.stringTemplate( + "fn_code_name({0}, {1})", "0002", mapSheetAnalEntity.analState), + mapSheetAnalEntity.gukyuinUsed)) + .from(mapSheetAnalEntity) + .where(builder) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .orderBy(mapSheetAnalEntity.id.desc()) + .fetch(); + + long total = + queryFactory + .select(mapSheetAnalEntity.id) + .from(mapSheetAnalEntity) + .where(builder) + .fetchCount(); + + return new PageImpl<>(content, pageable, total); + } + + /** + * 분석결과 요약정보 + * + * @param id + * @return + */ + @Override + public Optional getInferenceResultSummary(Long id) { + + // 1. 최신 버전 UID를 가져오는 서브쿼리 + JPQLQuery latestVerUidSub = + JPAExpressions.select(tmv.id.max()).from(tmv).where(tmv.modelUid.eq(tmm.id)); + + Optional content = + Optional.ofNullable( + queryFactory + .select( + Projections.constructor( + InferenceResultDto.AnalResSummary.class, + mapSheetAnalEntity.id, + mapSheetAnalEntity.analTitle, + tmm.modelNm.concat(" ").concat(tmv.modelVer).as("modelInfo"), + mapSheetAnalEntity.targetYyyy, + mapSheetAnalEntity.compareYyyy, + mapSheetAnalEntity.analMapSheet, + mapSheetAnalEntity.analStrtDttm, + mapSheetAnalEntity.analEndDttm, + mapSheetAnalEntity.analSec, + mapSheetAnalEntity.analPredSec, + mapSheetAnalEntity.resultUrl, + mapSheetAnalEntity.detectingCnt, + mapSheetAnalEntity.accuracy, + mapSheetAnalEntity.analState, + Expressions.stringTemplate( + "fn_code_name({0}, {1})", "0002", mapSheetAnalEntity.analState))) + .from(mapSheetAnalEntity) + .leftJoin(tmm) + .on(mapSheetAnalEntity.modelUid.eq(tmm.id)) + .leftJoin(tmv) + .on(tmv.modelUid.eq(tmm.id).and(tmv.id.eq(latestVerUidSub))) + .where(mapSheetAnalEntity.id.eq(id)) + .fetchOne()); + return content; + } + + /** + * 분석결과 상세 class name별 탐지 개수 + * + * @param id + * @return + */ + @Override + public List getDashboard(Long id) { + return queryFactory + .select( + Projections.constructor( + Dashboard.class, + mapSheetAnalSttcEntity.id.classAfterCd, + mapSheetAnalSttcEntity.classAfterCnt.sum())) + .from(mapSheetAnalSttcEntity) + .where(mapSheetAnalSttcEntity.id.analUid.eq(id)) + .groupBy(mapSheetAnalSttcEntity.id.classAfterCd) + .orderBy(mapSheetAnalSttcEntity.id.classAfterCd.asc()) + .fetch(); + } + + @Override + public List listAnalyGeom(Long id) { + QMapSheetAnalDataEntity analy = QMapSheetAnalDataEntity.mapSheetAnalDataEntity; + return queryFactory.selectFrom(analy).where(analy.analUid.eq(id)).fetch(); + } + + /** + * 분석결과 상세 목록 + * + * @param searchReq + * @return + */ + @Override + public Page listInferenceResultWithGeom( + List ids, SearchGeoReq searchReq) { + + // 분석 차수 + QMapSheetAnalDataGeomEntity detectedEntity = + QMapSheetAnalDataGeomEntity.mapSheetAnalDataGeomEntity; + Pageable pageable = searchReq.toPageable(); + + // 검색조건 + JPAQuery query = + queryFactory + .selectFrom(detectedEntity) + .where( + detectedEntity.dataUid.in(ids), + eqTargetClass(detectedEntity, searchReq.getTargetClass()), + eqCompareClass(detectedEntity, searchReq.getCompareClass()), + containsMapSheetNum(detectedEntity, searchReq.getMapSheetNum())); + + // count + long total = query.fetchCount(); + + // Pageable에서 정렬 가져오기, 없으면 기본 정렬(createdDttm desc) 사용 + List> orders = getOrderSpecifiers(pageable.getSort()); + if (orders.isEmpty()) { + orders.add(detectedEntity.createdDttm.desc()); + } + + List content = + query + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .orderBy(orders.toArray(new OrderSpecifier[0])) + .fetch(); + + return new PageImpl<>(content, pageable, total); + } + + /** + * 분석결과 상세 목록 + * + * @param searchGeoReq + * @return + */ + @Override + public Page getInferenceGeomList(Long id, SearchGeoReq searchGeoReq) { + Pageable pageable = searchGeoReq.toPageable(); + BooleanBuilder builder = new BooleanBuilder(); + + // 추론결과 id + builder.and(mapSheetAnalEntity.id.eq(id)); + + // 기준년도 분류 + if (searchGeoReq.getTargetClass() != null && !searchGeoReq.getTargetClass().equals("")) { + builder.and( + mapSheetAnalDataGeomEntity + .classAfterCd + .toLowerCase() + .eq(searchGeoReq.getTargetClass().toLowerCase())); + } + + // 비교년도 분류 + if (searchGeoReq.getCompareClass() != null && !searchGeoReq.getCompareClass().equals("")) { + builder.and( + mapSheetAnalDataGeomEntity + .classBeforeCd + .toLowerCase() + .eq(searchGeoReq.getCompareClass().toLowerCase())); + } + + // 분석도엽 + if (searchGeoReq.getMapSheetNum() != null && !searchGeoReq.getMapSheetNum().isEmpty()) { + List mapSheetNum = searchGeoReq.getMapSheetNum(); + builder.and(mapSheetAnalDataGeomEntity.mapSheetNum.in(mapSheetNum)); + } + + List content = + queryFactory + .select( + Projections.constructor( + InferenceResultDto.Geom.class, + mapSheetAnalDataGeomEntity.compareYyyy, + mapSheetAnalDataGeomEntity.targetYyyy, + mapSheetAnalDataGeomEntity.classBeforeCd, + mapSheetAnalDataGeomEntity.classBeforeProb, + mapSheetAnalDataGeomEntity.classAfterCd, + mapSheetAnalDataGeomEntity.classAfterProb, + mapSheetAnalDataGeomEntity.mapSheetNum, + mapSheetAnalDataGeomEntity.geom, + mapSheetAnalDataGeomEntity.geomCenter)) + .from(mapSheetAnalEntity) + .join(mapSheetAnalDataEntity) + .on(mapSheetAnalDataEntity.analUid.eq(mapSheetAnalEntity.id)) + .join(mapSheetAnalDataGeomEntity) + .on(mapSheetAnalDataGeomEntity.dataUid.eq(mapSheetAnalDataEntity.id)) + .where(builder) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + long total = + queryFactory + .select(mapSheetAnalDataGeomEntity.id) + .from(mapSheetAnalEntity) + .join(mapSheetAnalDataEntity) + .on(mapSheetAnalDataEntity.analUid.eq(mapSheetAnalEntity.id)) + .join(mapSheetAnalDataGeomEntity) + .on(mapSheetAnalDataGeomEntity.dataUid.eq(mapSheetAnalDataEntity.id)) + .where(builder) + .fetchCount(); + + return new PageImpl<>(content, pageable, total); + } + + /** + * 추론된 5000:1 도엽 목록 + * + * @param id + * @return + */ + @Override + public List getSheets(Long id) { + return queryFactory + .select(mapSheetAnalDataEntity.mapSheetNum) + .from(mapSheetAnalEntity) + .join(mapSheetAnalDataEntity) + .on(mapSheetAnalDataEntity.analUid.eq(mapSheetAnalEntity.id)) + .where(mapSheetAnalEntity.id.eq(id)) + .groupBy(mapSheetAnalDataEntity.mapSheetNum) + .fetch(); + } + + /** Pageable의 Sort를 QueryDSL OrderSpecifier로 변환 */ + @SuppressWarnings({"unchecked", "rawtypes"}) + private List> getOrderSpecifiers(Sort sort) { + List> orders = new ArrayList<>(); + + if (sort.isSorted()) { + QMapSheetAnalDataGeomEntity entity = QMapSheetAnalDataGeomEntity.mapSheetAnalDataGeomEntity; + + for (Sort.Order order : sort) { + Order direction = order.isAscending() ? Order.ASC : Order.DESC; + String property = order.getProperty(); + + // 유효한 필드만 처리 + switch (property) { + case "classBeforeCd" -> orders.add(new OrderSpecifier(direction, entity.classBeforeCd)); + case "classBeforeProb" -> + orders.add(new OrderSpecifier(direction, entity.classBeforeProb)); + case "classAfterCd" -> orders.add(new OrderSpecifier(direction, entity.classAfterCd)); + case "classAfterProb" -> orders.add(new OrderSpecifier(direction, entity.classAfterProb)); + case "mapSheetNum" -> orders.add(new OrderSpecifier(direction, entity.mapSheetNum)); + case "compareYyyy" -> orders.add(new OrderSpecifier(direction, entity.compareYyyy)); + case "targetYyyy" -> orders.add(new OrderSpecifier(direction, entity.targetYyyy)); + case "area" -> orders.add(new OrderSpecifier(direction, entity.area)); + case "createdDttm" -> orders.add(new OrderSpecifier(direction, entity.createdDttm)); + case "updatedDttm" -> orders.add(new OrderSpecifier(direction, entity.updatedDttm)); + // 유효하지 않은 필드는 무시 + default -> {} + } + } + } + + return orders; + } + + private BooleanExpression eqTargetClass( + QMapSheetAnalDataGeomEntity detectedEntity, String targetClass) { + return targetClass != null && !targetClass.isEmpty() + ? detectedEntity.classAfterCd.toLowerCase().eq(targetClass.toLowerCase()) + : null; + } + + private BooleanExpression eqCompareClass( + QMapSheetAnalDataGeomEntity detectedEntity, String compareClass) { + return compareClass != null && !compareClass.isEmpty() + ? detectedEntity.classBeforeCd.toLowerCase().eq(compareClass.toLowerCase()) + : null; + } + + private BooleanExpression containsMapSheetNum( + QMapSheetAnalDataGeomEntity detectedEntity, List mapSheet) { + if (mapSheet == null || mapSheet.isEmpty()) { + return null; + } + + return detectedEntity.mapSheetNum.in(mapSheet); + } +} 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..f23fdd18 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 @@ -19,7 +19,7 @@ public interface MapSheetMngRepositoryCustom { Optional findMapSheetMngHstInfo(Long hstUid); - int insertMapSheetOrgDataToMapSheetMngHst(int mngYyyy); + int insertMapSheetOrgDataToMapSheetMngHst(int mngYyyy, String mngPath); List findHstUidToMapSheetFileList(Long hstUid); @@ -43,6 +43,10 @@ public interface MapSheetMngRepositoryCustom { void updateByHstUidMngFileState(Long hstUid, String fileState); + void updateByFileUidMngFileState(Long fileUid, String fileState); + + void deleteByNotInFileUidMngFile(Long hstUid, List fileUids); + void updateYearState(int yyyy, String status); Page findMapSheetErrorList( @@ -50,9 +54,11 @@ public interface MapSheetMngRepositoryCustom { MapSheetMngDto.ErrorDataDto findMapSheetError(Long hstUid); - List findIdToMapSheetFileList(Long hstUid); + List findByHstUidMapSheetFileList(Long hstUid); - MapSheetMngDto.MngFilesDto findIdToMapSheetFile(Long fileUid); + MapSheetMngDto.MngFilesDto findByFileUidMapSheetFile(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 eb512241..1226813b 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 @@ -448,10 +448,11 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport } @Override - public List findIdToMapSheetFileList(Long hstUid) { + public List findByHstUidMapSheetFileList(Long hstUid) { BooleanBuilder whereBuilder = new BooleanBuilder(); whereBuilder.and(mapSheetMngFileEntity.hstUid.eq(hstUid)); + whereBuilder.and(mapSheetMngFileEntity.fileDel.eq(false)); List foundContent = queryFactory @@ -478,6 +479,7 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport public List findHstUidToMapSheetFileList(Long hstUid) { BooleanBuilder whereBuilder = new BooleanBuilder(); whereBuilder.and(mapSheetMngFileEntity.hstUid.eq(hstUid)); + whereBuilder.and(mapSheetMngFileEntity.fileDel.eq(false)); List foundContent = queryFactory @@ -531,7 +533,7 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport } @Override - public MapSheetMngDto.MngFilesDto findIdToMapSheetFile(Long fileUid) { + public MapSheetMngDto.MngFilesDto findByFileUidMapSheetFile(Long fileUid) { MapSheetMngDto.MngFilesDto foundContent = queryFactory @@ -636,8 +638,9 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport public void deleteByHstUidMngFile(Long hstUid) { long deletedFileCount = queryFactory - .delete(mapSheetMngFileEntity) - .where(mapSheetMngFileEntity.fileUid.eq(hstUid)) + .update(mapSheetMngFileEntity) + .set(mapSheetMngFileEntity.fileDel, true) + .where(mapSheetMngFileEntity.hstUid.eq(hstUid)) .execute(); } @@ -645,7 +648,8 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport public void deleteByFileUidMngFile(Long fileUid) { long fileCount = queryFactory - .delete(mapSheetMngFileEntity) + .update(mapSheetMngFileEntity) + .set(mapSheetMngFileEntity.fileDel, true) .where(mapSheetMngFileEntity.fileUid.eq(fileUid)) .execute(); } @@ -660,6 +664,46 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport .execute(); } + @Override + public void deleteByNotInFileUidMngFile(Long hstUid, List fileUids) { + long execCount = + queryFactory + .update(mapSheetMngFileEntity) + .set(mapSheetMngFileEntity.fileDel, true) + .where( + mapSheetMngFileEntity + .hstUid + .eq(hstUid) + .and(mapSheetMngFileEntity.fileUid.notIn(fileUids))) + .execute(); + } + + @Override + public void updateByFileUidMngFileState(Long fileUid, String fileState) { + long execCount = + queryFactory + .update(mapSheetMngFileEntity) + .set(mapSheetMngFileEntity.fileState, fileState) + .where(mapSheetMngFileEntity.fileUid.eq(fileUid)) + .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 = @@ -689,13 +733,14 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport } @Override - public int insertMapSheetOrgDataToMapSheetMngHst(int mngYyyy) { + public int insertMapSheetOrgDataToMapSheetMngHst(int mngYyyy, String mngPath) { String sql = """ INSERT INTO tb_map_sheet_mng_hst ( mng_yyyy + ,map_sheet_path ,map_sheet_code ,map_sheet_num ,map_sheet_name @@ -705,14 +750,15 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport ,use_inference ) select - :mngYyyy as mng_yyyy - ,fid as map_sheet_code - ,mapidcd_no::INTEGER as map_sheet_num - ,mapid_nm as map_sheet_name - ,fid as map_sheet_code_src - ,5000 as scale_ratio - ,((mapidcd_no::INTEGER)/1000) as ref_map_sheet_num - ,use_inference + :mngYyyy as mng_yyyy, + :mngPath as map_sheet_path, + fid as map_sheet_code, + mapidcd_no as map_sheet_num, + mapid_nm as map_sheet_name, + fid as map_sheet_code_src, + 5000 as scale_ratio, + ((mapidcd_no::INTEGER)/1000) as ref_map_sheet_num, + use_inference from tb_map_inkx_5k """; @@ -720,6 +766,7 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport // Native Query 생성 및 실행 Query query = (Query) em.createNativeQuery(sql); query.setParameter("mngYyyy", mngYyyy); + query.setParameter("mngPath", mngPath); int exeCnt = query.executeUpdate(); // 실행 (영향받은 행의 개수 반환) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index d64d7d1d..a5036d99 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -66,6 +66,7 @@ management: file: + #sync-root-dir: D:/app/original-images/ sync-root-dir: /app/original-images/ sync-tmp-dir: ${file.sync-root-dir}/tmp sync-file-extention: tfw,tif diff --git a/src/main/resources/db/migration/dump-kamco_cds-202512221523.tar b/src/main/resources/db/migration/dump-kamco_cds-202512231534.tar similarity index 79% rename from src/main/resources/db/migration/dump-kamco_cds-202512221523.tar rename to src/main/resources/db/migration/dump-kamco_cds-202512231534.tar index 885deb0d..5396379a 100644 Binary files a/src/main/resources/db/migration/dump-kamco_cds-202512221523.tar and b/src/main/resources/db/migration/dump-kamco_cds-202512231534.tar differ