분류데이터셋 업로드,상세,목록 API
This commit is contained in:
@@ -0,0 +1,302 @@
|
||||
package com.kamco.cd.training.dataset;
|
||||
|
||||
import com.kamco.cd.training.config.api.ApiResponseDto;
|
||||
import com.kamco.cd.training.dataset.dto.DatasetClsObjDto;
|
||||
import com.kamco.cd.training.dataset.dto.DatasetDto;
|
||||
import com.kamco.cd.training.dataset.dto.DatasetDto.AddDeliveriesReq;
|
||||
import com.kamco.cd.training.dataset.dto.DatasetObjDto.DatasetClass;
|
||||
import com.kamco.cd.training.dataset.dto.DatasetObjDto.DatasetStorage;
|
||||
import com.kamco.cd.training.dataset.service.DatasetAsyncService;
|
||||
import com.kamco.cd.training.dataset.service.DatasetClsService;
|
||||
import com.kamco.cd.training.dataset.service.DatasetService;
|
||||
import com.kamco.cd.training.model.dto.FileDto.FoldersDto;
|
||||
import com.kamco.cd.training.model.dto.FileDto.SrchFoldersDto;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
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 jakarta.validation.Valid;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileStore;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "분류모델 학습데이터 관리", description = "어드민 홈 > 학습데이터관리 > 분류데이터셋")
|
||||
@RestController
|
||||
@RequestMapping("/api/datasets/cls")
|
||||
@RequiredArgsConstructor
|
||||
public class DatasetClsApiController {
|
||||
|
||||
private final DatasetClsService datasetClsService;
|
||||
private final DatasetAsyncService datasetAsyncService;
|
||||
|
||||
@Operation(summary = "분류 학습데이터 관리 목록 조회", description = "분류 학습데이터 목록을 조회합니다.")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "조회 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = Page.class))),
|
||||
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@GetMapping
|
||||
public ApiResponseDto<Page<DatasetDto.Basic>> searchDatasets(
|
||||
@Parameter(
|
||||
description = "구분",
|
||||
example = "",
|
||||
schema = @Schema(allowableValues = {"DELIVER", "PRODUCTION"}))
|
||||
@RequestParam(required = false)
|
||||
String dataType,
|
||||
@Parameter(description = "제목", example = "") @RequestParam(required = false) String title,
|
||||
@Parameter(description = "페이지 번호 (0부터 시작)", example = "0") @RequestParam(defaultValue = "0")
|
||||
int page,
|
||||
@Parameter(description = "페이지 크기", example = "20") @RequestParam(defaultValue = "20")
|
||||
int size) {
|
||||
DatasetDto.SearchReq searchReq = new DatasetDto.SearchReq();
|
||||
searchReq.setTitle(title);
|
||||
searchReq.setDataType(dataType);
|
||||
searchReq.setPage(page);
|
||||
searchReq.setSize(size);
|
||||
return ApiResponseDto.ok(datasetClsService.searchDatasets(searchReq));
|
||||
}
|
||||
|
||||
@Operation(summary = "학습데이터관리 상세 조회", description = "학습데이터관리 상세 정보를 조회합니다.")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "조회 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = DatasetDto.Basic.class))),
|
||||
@ApiResponse(responseCode = "404", description = "데이터셋을 찾을 수 없음", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@GetMapping("/{uuid}")
|
||||
public ApiResponseDto<DatasetDto.Basic> getDatasetDetail(@PathVariable UUID uuid) {
|
||||
return ApiResponseDto.ok(datasetClsService.getDatasetDetail(uuid));
|
||||
}
|
||||
|
||||
@Operation(summary = "학습데이터 수정", description = "학습데이터 제목, 메모 수정")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "수정 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = Long.class))),
|
||||
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
|
||||
@ApiResponse(responseCode = "404", description = "데이터셋을 찾을 수 없음", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@PutMapping("/{uuid}")
|
||||
public ApiResponseDto<UUID> updateDataset(
|
||||
@PathVariable UUID uuid, @RequestBody DatasetDto.UpdateReq updateReq) {
|
||||
datasetClsService.updateDataset(uuid, updateReq);
|
||||
return ApiResponseDto.ok(uuid);
|
||||
}
|
||||
|
||||
@Operation(summary = "학습데이터 삭제", description = "학습데이터를 삭제합니다.(납품 데이터는 삭제 불가)")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(responseCode = "201", description = "삭제 성공", content = @Content),
|
||||
@ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content),
|
||||
@ApiResponse(responseCode = "404", description = "데이터셋을 찾을 수 없음", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@DeleteMapping("/{uuid}")
|
||||
public ApiResponseDto<UUID> deleteDatasets(@PathVariable UUID uuid) {
|
||||
|
||||
datasetClsService.deleteDatasets(uuid);
|
||||
return ApiResponseDto.ok(uuid);
|
||||
}
|
||||
|
||||
@Operation(summary = "분류 학습데이터 관리 목록 조회", description = "분류 학습데이터 목록을 조회합니다.")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "조회 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = Page.class))),
|
||||
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@GetMapping("/obj-list")
|
||||
public ApiResponseDto<Page<DatasetClsObjDto.Basic>> searchDatasetObjectList(
|
||||
@Parameter(description = "회차 uuid", example = "e9a6774b-4f81-4402-b080-51d27fac1f01")
|
||||
@RequestParam(required = true)
|
||||
UUID uuid,
|
||||
@Parameter(description = "년도", example = "2023") @RequestParam(required = false) Integer yyyy,
|
||||
@Parameter(description = "분류", example = "container") @RequestParam(required = false)
|
||||
String classCd,
|
||||
@Parameter(description = "도엽번호", example = "36713060") @RequestParam(required = false)
|
||||
String mapSheetNum,
|
||||
@RequestParam(defaultValue = "0") int page,
|
||||
@RequestParam(defaultValue = "20") int size) {
|
||||
DatasetClsObjDto.ClsSearchReq searchReq = new DatasetClsObjDto.ClsSearchReq();
|
||||
searchReq.setUuid(uuid);
|
||||
searchReq.setYyyy(yyyy);
|
||||
searchReq.setClassCd(classCd);
|
||||
searchReq.setMapSheetNum(mapSheetNum);
|
||||
searchReq.setPage(page);
|
||||
searchReq.setSize(size);
|
||||
return ApiResponseDto.ok(datasetClsService.searchDatasetClsObjectList(searchReq));
|
||||
}
|
||||
|
||||
@Operation(summary = "학습데이터 관리 obj 삭제", description = "학습데이터 관리 obj 삭제 API")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "삭제 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = Page.class))),
|
||||
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@DeleteMapping("/obj/{uuid}")
|
||||
public ApiResponseDto<UUID> deleteDatasetObjByUuid(@PathVariable UUID uuid) {
|
||||
return ApiResponseDto.ok(datasetClsService.deleteDatasetObjByUuid(uuid));
|
||||
}
|
||||
|
||||
@Operation(summary = "학습데이터 결과 class 조회", description = "학습데이터 결과 class 조회 API")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "조회 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = Page.class))),
|
||||
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@GetMapping("/class/{uuid}")
|
||||
public ApiResponseDto<List<DatasetClass>> getDatasetObjByUuid(
|
||||
@Parameter(description = "dataset uuid", example = "e1416f32-769f-495c-a883-3ebfacef4bac")
|
||||
@PathVariable
|
||||
UUID uuid,
|
||||
@Parameter(description = "compare, target", example = "compare") @RequestParam String type) {
|
||||
return ApiResponseDto.ok(datasetClsService.getDatasetObjByUuid(uuid, type));
|
||||
}
|
||||
|
||||
@Operation(summary = "남은 저장공간 조회", description = "남은 저장공간 조회 API")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "조회 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = Page.class))),
|
||||
@ApiResponse(responseCode = "404", description = "저장 공간 조회 오류", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@GetMapping("/usable-bytes")
|
||||
public ApiResponseDto<DatasetStorage> getUsableBytes() throws IOException {
|
||||
FileStore store = Files.getFileStore(Path.of("."));
|
||||
|
||||
long usable = store.getUsableSpace();
|
||||
DatasetStorage storage = new DatasetStorage();
|
||||
storage.setUsableBytes(String.valueOf(usable));
|
||||
|
||||
// datasetClsService.getUsableBytes();
|
||||
return ApiResponseDto.ok(storage);
|
||||
}
|
||||
|
||||
@Operation(summary = "학습데이터 zip파일 등록", description = "학습데이터 zip파일 등록 합니다.")
|
||||
@PostMapping
|
||||
public ApiResponseDto<ApiResponseDto.ResponseObj> insertDataset(
|
||||
@RequestBody @Valid DatasetDto.AddReq addReq) {
|
||||
|
||||
return ApiResponseDto.okObject(datasetClsService.insertDataset(addReq));
|
||||
}
|
||||
|
||||
@Operation(summary = "객체별 파일 Path 조회", description = "파일 Path 조회")
|
||||
@GetMapping("/files")
|
||||
public ResponseEntity<Resource> getFile(@RequestParam UUID uuid, @RequestParam String pathType)
|
||||
throws Exception {
|
||||
|
||||
String path = datasetClsService.getFilePathByUUIDPathType(uuid, pathType);
|
||||
return datasetClsService.getFilePathByFile(path);
|
||||
}
|
||||
|
||||
@Operation(summary = "객체별 파일 Path 조회", description = "파일 Path 조회")
|
||||
@GetMapping("/files-to86")
|
||||
public ResponseEntity<Resource> getFileTo86(
|
||||
@RequestParam UUID uuid, @RequestParam String pathType) throws Exception {
|
||||
|
||||
String path = datasetClsService.getFilePathByUUIDPathType(uuid, pathType);
|
||||
return datasetClsService.getFilePathByFile(path);
|
||||
}
|
||||
|
||||
@Operation(summary = "납품 폴더 조회", description = "납품 폴더 조회 API")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "조회 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = FoldersDto.class))),
|
||||
@ApiResponse(responseCode = "404", description = "조회 오류", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@PostMapping("/folder-list")
|
||||
public ApiResponseDto<FoldersDto> getDir(@RequestBody SrchFoldersDto srchDto) throws IOException {
|
||||
|
||||
return ApiResponseDto.createOK(datasetClsService.getFolderAll(srchDto));
|
||||
}
|
||||
|
||||
@Operation(summary = "납품 학습데이터셋 등록", description = "납품 학습데이터셋 등록 API")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "등록 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = String.class))),
|
||||
@ApiResponse(responseCode = "404", description = "조회 오류", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@PostMapping("/deliveries")
|
||||
public ApiResponseDto<String> insertDeliveriesDataset(@RequestBody AddDeliveriesReq req) {
|
||||
|
||||
// 폴더 구조 검증
|
||||
DatasetService.validateTrainValTestDirs(req.getFilePath());
|
||||
// 파일 개수 검증
|
||||
DatasetService.validateDirFileCount(req.getFilePath());
|
||||
// 폴더명(uid)으로 등록한 건이 있는지 체크
|
||||
datasetClsService.validateExistsUidChk(req.getFilePath());
|
||||
|
||||
datasetAsyncService.insertDeliveriesDatasetAsync(req);
|
||||
return ApiResponseDto.createOK("ok");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package com.kamco.cd.training.dataset.dto;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.kamco.cd.training.common.enums.DetectionClassification;
|
||||
import com.kamco.cd.training.common.utils.HeaderUtil;
|
||||
import com.kamco.cd.training.common.utils.enums.CodeExpose;
|
||||
import com.kamco.cd.training.common.utils.enums.EnumType;
|
||||
import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.UUID;
|
||||
import lombok.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
|
||||
@Slf4j
|
||||
public class DatasetClsObjDto {
|
||||
|
||||
@Schema(name = "DatasetClsObj Basic", description = "분류 데이터셋 객체 Obj 기본 정보")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public static class Basic {
|
||||
|
||||
private Long objId;
|
||||
private UUID uuid;
|
||||
private Long datasetUid;
|
||||
private String type;
|
||||
private Integer yyyy;
|
||||
private String classCd;
|
||||
private String mapSheetNum;
|
||||
private String tifImgPath;
|
||||
private String fileName;
|
||||
@JsonFormatDttm private ZonedDateTime createdDttm;
|
||||
private Long createdUid;
|
||||
private Boolean deleted;
|
||||
|
||||
public Basic(
|
||||
Long objId,
|
||||
UUID uuid,
|
||||
Long datasetUid,
|
||||
String type,
|
||||
Integer yyyy,
|
||||
String classCd,
|
||||
String mapSheetNum,
|
||||
String tifImgPath,
|
||||
String fileName,
|
||||
ZonedDateTime createdDttm,
|
||||
Long createdUid,
|
||||
Boolean deleted) {
|
||||
this.objId = objId;
|
||||
this.uuid = uuid;
|
||||
this.datasetUid = datasetUid;
|
||||
this.type = type;
|
||||
this.yyyy = yyyy;
|
||||
this.classCd = classCd;
|
||||
this.mapSheetNum = mapSheetNum;
|
||||
this.tifImgPath = tifImgPath;
|
||||
this.fileName = fileName;
|
||||
this.createdDttm = createdDttm;
|
||||
this.createdUid = createdUid;
|
||||
this.deleted = deleted;
|
||||
}
|
||||
}
|
||||
|
||||
@Schema(name = "ClsSearchReq", description = "분류 데이터셋 상세 도엽목록 조회 요청")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class ClsSearchReq {
|
||||
@Schema(description = "회차 uuid", example = "e9a6774b-4f81-4402-b080-51d27fac1f01")
|
||||
private UUID uuid;
|
||||
|
||||
@Schema(description = "년도", example = "2021")
|
||||
private Integer yyyy;
|
||||
|
||||
@Schema(description = "분류", example = "waste")
|
||||
private String classCd;
|
||||
|
||||
@Schema(description = "도엽번호", example = "36713060")
|
||||
private String mapSheetNum;
|
||||
|
||||
@Schema(description = "페이지 번호 (0부터 시작)", example = "0")
|
||||
private int page = 0;
|
||||
|
||||
@Schema(description = "페이지 크기", example = "20")
|
||||
private int size = 20;
|
||||
|
||||
public Pageable toPageable() {
|
||||
return PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdDttm"));
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class DatasetClass {
|
||||
private String classCd;
|
||||
|
||||
public String getClassName() {
|
||||
|
||||
return HeaderUtil.isEnglishRequest()
|
||||
? DetectionClassification.fromString(classCd).getId()
|
||||
: DetectionClassification.fromString(classCd).getDesc();
|
||||
// fromString 메서드를 사용하여 안전하게 변환 (미정의 값은 ETC로 처리)
|
||||
// return DetectionClassification.valueOf(classCd.toUpperCase()).getDesc();
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class DatasetStorage {
|
||||
private String usableBytes;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public static class DatasetObjRegDto {
|
||||
private Long datasetUid;
|
||||
private Integer compareYyyy;
|
||||
private String compareClassCd;
|
||||
private Integer targetYyyy;
|
||||
private String targetClassCd;
|
||||
private String comparePath;
|
||||
private String targetPath;
|
||||
private String labelPath;
|
||||
private String geojsonPath;
|
||||
private String mapSheetNum;
|
||||
private JsonNode geojson;
|
||||
private String fileName;
|
||||
}
|
||||
|
||||
@CodeExpose
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum FolderType implements EnumType {
|
||||
TRAIN("train"),
|
||||
VAL("val"),
|
||||
TEST("test");
|
||||
|
||||
private String desc;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,7 @@ public class DatasetDto {
|
||||
private String statusCd;
|
||||
private Boolean deleted;
|
||||
private String dataType;
|
||||
private String cdClsType;
|
||||
|
||||
public Basic(
|
||||
Long id,
|
||||
@@ -60,7 +61,8 @@ public class DatasetDto {
|
||||
ZonedDateTime createdDttm,
|
||||
String status,
|
||||
Boolean deleted,
|
||||
String dataType) {
|
||||
String dataType,
|
||||
String cdClsType) {
|
||||
this.id = id;
|
||||
this.uuid = uuid;
|
||||
this.title = title;
|
||||
@@ -74,6 +76,7 @@ public class DatasetDto {
|
||||
this.statusCd = status;
|
||||
this.deleted = deleted;
|
||||
this.dataType = dataType;
|
||||
this.cdClsType = cdClsType;
|
||||
}
|
||||
|
||||
public String getTotalSize(Long totalSize) {
|
||||
@@ -542,6 +545,7 @@ public class DatasetDto {
|
||||
private Long totalSize;
|
||||
private Long totalObjectCount;
|
||||
private String datasetPath;
|
||||
private String cdClsType;
|
||||
}
|
||||
|
||||
@Getter
|
||||
|
||||
@@ -166,4 +166,19 @@ public class DatasetObjDto {
|
||||
private JsonNode geojson;
|
||||
private String fileName;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public static class DatasetClsObjRegDto {
|
||||
private Long datasetUid;
|
||||
private String type;
|
||||
private Integer yyyy;
|
||||
private String classCd;
|
||||
private String mapSheetNum;
|
||||
private String tifImgPath;
|
||||
private String fileName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,821 @@
|
||||
package com.kamco.cd.training.dataset.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.training.common.enums.LearnDataRegister;
|
||||
import com.kamco.cd.training.common.enums.LearnDataType;
|
||||
import com.kamco.cd.training.common.exception.CustomApiException;
|
||||
import com.kamco.cd.training.common.service.FormatStorage;
|
||||
import com.kamco.cd.training.common.utils.FIleChecker;
|
||||
import com.kamco.cd.training.config.api.ApiResponseDto.ApiResponseCode;
|
||||
import com.kamco.cd.training.config.api.ApiResponseDto.ResponseObj;
|
||||
import com.kamco.cd.training.dataset.dto.DatasetClsObjDto;
|
||||
import com.kamco.cd.training.dataset.dto.DatasetDto;
|
||||
import com.kamco.cd.training.dataset.dto.DatasetDto.AddDeliveriesReq;
|
||||
import com.kamco.cd.training.dataset.dto.DatasetDto.AddReq;
|
||||
import com.kamco.cd.training.dataset.dto.DatasetDto.DatasetMngRegDto;
|
||||
import com.kamco.cd.training.dataset.dto.DatasetObjDto;
|
||||
import com.kamco.cd.training.dataset.dto.DatasetObjDto.DatasetClass;
|
||||
import com.kamco.cd.training.dataset.dto.DatasetObjDto.DatasetObjRegDto;
|
||||
import com.kamco.cd.training.dataset.dto.DatasetObjDto.DatasetStorage;
|
||||
import com.kamco.cd.training.model.dto.FileDto.FoldersDto;
|
||||
import com.kamco.cd.training.model.dto.FileDto.SrchFoldersDto;
|
||||
import com.kamco.cd.training.postgres.core.DatasetClsCoreService;
|
||||
import jakarta.validation.Valid;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DatasetClsService {
|
||||
|
||||
private final DatasetClsCoreService datasetClsCoreService;
|
||||
private final DatasetBatchService datasetBatchService;
|
||||
|
||||
private static final List<String> LABEL_DIRS = List.of("label-json", "label", "input1", "input2");
|
||||
private static final List<String> REQUIRED_DIRS = Arrays.asList("train", "val", "test");
|
||||
private static final List<String> CHECK_DIRS = List.of("label", "input1", "input2");
|
||||
|
||||
/**
|
||||
* 데이터셋 목록 조회
|
||||
*
|
||||
* @param searchReq 검색 조건
|
||||
* @return 데이터셋 목록
|
||||
*/
|
||||
public Page<DatasetDto.Basic> searchDatasets(DatasetDto.SearchReq searchReq) {
|
||||
log.info("데이터셋 목록 조회 - 조건: {}", searchReq);
|
||||
return datasetClsCoreService.findDatasetList(searchReq);
|
||||
}
|
||||
|
||||
/**
|
||||
* 데이터셋 상세 조회
|
||||
*
|
||||
* @param id 상세 조회할 목록 Id
|
||||
* @return 데이터셋 상세 정보
|
||||
*/
|
||||
public DatasetDto.Basic getDatasetDetail(UUID id) {
|
||||
return datasetClsCoreService.getOneByUuid(id);
|
||||
}
|
||||
|
||||
// TODO 미사용시작
|
||||
/**
|
||||
* 데이터셋 등록
|
||||
*
|
||||
* @param registerReq 등록 요청
|
||||
* @return 등록된 데이터셋 ID
|
||||
*/
|
||||
@Transactional
|
||||
public Long registerDataset(DatasetDto.RegisterReq registerReq) {
|
||||
log.info("데이터셋 등록 - 요청: {}", registerReq);
|
||||
DatasetDto.Basic saved = datasetClsCoreService.save(registerReq);
|
||||
log.info("데이터셋 등록 완료 - ID: {}", saved.getId());
|
||||
return saved.getId();
|
||||
}
|
||||
|
||||
// TODO 미사용 끝
|
||||
/**
|
||||
* 데이터셋 수정
|
||||
*
|
||||
* @param updateReq 수정 요청
|
||||
* @return 수정된 데이터셋 ID
|
||||
*/
|
||||
@Transactional
|
||||
public void updateDataset(UUID uuid, DatasetDto.UpdateReq updateReq) {
|
||||
datasetClsCoreService.update(uuid, updateReq);
|
||||
}
|
||||
|
||||
/**
|
||||
* 데이터셋 삭제 (다건)
|
||||
*
|
||||
* @param uuid 삭제 요청
|
||||
*/
|
||||
@Transactional
|
||||
public void deleteDatasets(UUID uuid) {
|
||||
datasetClsCoreService.deleteDatasets(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 데이터셋 통계 요약
|
||||
*
|
||||
* @param summaryReq 요약 요청
|
||||
* @return 통계 요약
|
||||
*/
|
||||
public DatasetDto.Summary getDatasetSummary(DatasetDto.SummaryReq summaryReq) {
|
||||
log.info("데이터셋 통계 요약 - 요청: {}", summaryReq);
|
||||
return datasetClsCoreService.getDatasetSummary(summaryReq);
|
||||
}
|
||||
|
||||
public Page<DatasetClsObjDto.Basic> searchDatasetClsObjectList(
|
||||
DatasetClsObjDto.ClsSearchReq searchReq) {
|
||||
return datasetClsCoreService.searchDatasetClsObjectList(searchReq);
|
||||
}
|
||||
|
||||
public UUID deleteDatasetObjByUuid(UUID uuid) {
|
||||
return datasetClsCoreService.deleteDatasetObjByUuid(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 데이터셋 object class 조회
|
||||
*
|
||||
* @param uuid dataset uuid
|
||||
* @param type compare, target
|
||||
*/
|
||||
public List<DatasetClass> getDatasetObjByUuid(UUID uuid, String type) {
|
||||
return datasetClsCoreService.findDatasetObjClassByUuid(uuid, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용 가능 공간 조회
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public DatasetStorage getUsableBytes() {
|
||||
// 현재 실행 위치가 속한 디스크 기준
|
||||
try {
|
||||
FormatStorage.DiskUsage usage = FormatStorage.getDiskUsage(Path.of("."));
|
||||
log.debug("경로 : {}", usage.path());
|
||||
log.debug("총 저장공간 : {}", usage.totalText());
|
||||
log.debug("남은 저장공간 : {}", usage.usableText());
|
||||
log.debug("사용률 : {}", usage.usedPercent());
|
||||
|
||||
DatasetStorage datasetStorage = new DatasetStorage();
|
||||
datasetStorage.setUsableBytes(usage.usableText());
|
||||
return datasetStorage;
|
||||
} catch (Exception e) {
|
||||
throw new CustomApiException("NOT_FOUND", HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 분류모델 업로드
|
||||
*
|
||||
* @param addReq
|
||||
* @return
|
||||
*/
|
||||
@Transactional
|
||||
public ResponseObj insertDataset(@Valid AddReq addReq) {
|
||||
|
||||
Long datasetUid = null; // master id 값, 등록하면서 가져올 예정
|
||||
|
||||
try {
|
||||
// 압축 해제
|
||||
FIleChecker.unzip(addReq.getFileName(), addReq.getFilePath());
|
||||
|
||||
// 압축 해제한 폴더 하위에 train,val,test 폴더 모두 존재하는지 확인
|
||||
validateClsTrainValTestDirs(addReq.getFilePath() + addReq.getFileName().replace(".zip", ""));
|
||||
|
||||
// 해제한 폴더 읽어서 데이터 저장
|
||||
datasetUid =
|
||||
this.processAndInsertDataset(
|
||||
addReq.getFilePath() + addReq.getFileName().replace(".zip", ""), addReq);
|
||||
log.info("######testttest#### datasetUid: {}", datasetUid);
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error(e.getMessage());
|
||||
return new ResponseObj(ApiResponseCode.INTERNAL_SERVER_ERROR, e.getMessage());
|
||||
}
|
||||
|
||||
datasetClsCoreService.updateDatasetUploadStatus(datasetUid, LearnDataRegister.COMPLETED);
|
||||
return new ResponseObj(ApiResponseCode.OK, "업로드 성공하였습니다.");
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> getUnzipDatasetFiles(String unzipRootPath, String subDir) {
|
||||
|
||||
Path root = Paths.get(unzipRootPath).resolve(subDir);
|
||||
Map<String, Map<String, Object>> grouped = new HashMap<>();
|
||||
|
||||
for (String dirName : LABEL_DIRS) {
|
||||
Path dir = root.resolve(dirName);
|
||||
|
||||
if (!Files.isDirectory(dir)) {
|
||||
throw new CustomApiException(
|
||||
ApiResponseCode.NOT_FOUND_DATA.getId(),
|
||||
HttpStatus.CONFLICT,
|
||||
"폴더가 존재하지 않습니다. 업로드 된 파일을 확인하세요. : " + dir);
|
||||
}
|
||||
|
||||
try (Stream<Path> stream = Files.list(dir)) {
|
||||
stream
|
||||
.filter(Files::isRegularFile)
|
||||
.forEach(
|
||||
path -> {
|
||||
String fileName = path.getFileName().toString();
|
||||
String baseName = getBaseName(fileName);
|
||||
|
||||
// baseName 기준 Map 생성
|
||||
Map<String, Object> data =
|
||||
grouped.computeIfAbsent(baseName, k -> new HashMap<>());
|
||||
|
||||
// 공통 메타
|
||||
data.put("baseName", baseName);
|
||||
|
||||
// 폴더별 처리
|
||||
if ("label-json".equals(dirName)) {
|
||||
// json 파일이면 파싱
|
||||
try {
|
||||
data.put("label-json", readJson(path));
|
||||
} catch (Exception e) {
|
||||
log.error("파일 JSON 읽기 실패. skip. file={}", path, e);
|
||||
return; // skip
|
||||
}
|
||||
data.put("geojson_path", path.toAbsolutePath().toString());
|
||||
} else {
|
||||
data.put(dirName, path.toAbsolutePath().toString());
|
||||
}
|
||||
});
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
return new ArrayList<>(grouped.values());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Long insertTrainTestData(
|
||||
Map<String, Object> map, AddReq addReq, int idx, Long datasetUid, String subDir) {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String comparePath = (String) map.get("input1");
|
||||
String targetPath = (String) map.get("input2");
|
||||
String labelPath = (String) map.get("label");
|
||||
String geojsonPath = (String) map.get("geojson_path");
|
||||
Object labelJson = map.get("label-json");
|
||||
JsonNode json;
|
||||
|
||||
if (labelJson instanceof JsonNode jn) {
|
||||
json = jn;
|
||||
} else {
|
||||
try {
|
||||
json = mapper.readTree(labelJson.toString());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("label_json parse error", e);
|
||||
}
|
||||
}
|
||||
|
||||
String fileName = Paths.get(comparePath).getFileName().toString();
|
||||
String[] fileNameStr = fileName.split("_");
|
||||
String compareYyyy = fileNameStr[1];
|
||||
String targetYyyy = fileNameStr[2];
|
||||
String mapSheetNum = fileNameStr[3];
|
||||
|
||||
if (idx == 0 && subDir.equals("train")) {
|
||||
String title = compareYyyy + "-" + targetYyyy; // 제목 : 비교년도-기준년도
|
||||
String dataType = LearnDataType.PRODUCTION.getId(); // 만들어 넣는 건 다 제작
|
||||
Long stage =
|
||||
datasetClsCoreService.getDatasetMaxStage(
|
||||
Integer.parseInt(compareYyyy), Integer.parseInt(targetYyyy))
|
||||
+ 1;
|
||||
String uid = addReq.getFileName().replace(".zip", "");
|
||||
|
||||
DatasetMngRegDto mngRegDto =
|
||||
DatasetMngRegDto.builder()
|
||||
.uid(uid)
|
||||
.dataType(dataType)
|
||||
.compareYyyy(Integer.parseInt(compareYyyy))
|
||||
.targetYyyy(Integer.parseInt(targetYyyy))
|
||||
.title(title)
|
||||
.memo(addReq.getMemo())
|
||||
.roundNo(stage)
|
||||
.totalSize(addReq.getFileSize())
|
||||
.datasetPath(addReq.getFilePath() + uid)
|
||||
.build();
|
||||
|
||||
datasetUid = datasetClsCoreService.insertDatasetMngData(mngRegDto); // tb_dataset 에 insert
|
||||
}
|
||||
|
||||
// datasetUid 로 obj 도 등록하기
|
||||
// Json 갯수만큼 for문 돌려서 insert 해야 함, features에 빈값
|
||||
if (json != null && json.path("features") != null && !json.path("features").isEmpty()) {
|
||||
|
||||
for (JsonNode feature : json.path("features")) {
|
||||
JsonNode prop = feature.path("properties");
|
||||
String compareClassCd = prop.path("before").asText(null);
|
||||
String targetClassCd = prop.path("after").asText(null);
|
||||
|
||||
// 한 개씩 자른 geojson을 FeatureCollection 으로 만들어서 넣기
|
||||
ObjectNode root = mapper.createObjectNode();
|
||||
root.put("type", "FeatureCollection");
|
||||
ArrayNode features = mapper.createArrayNode();
|
||||
features.add(feature);
|
||||
root.set("features", features);
|
||||
|
||||
DatasetObjRegDto objRegDto =
|
||||
DatasetObjRegDto.builder()
|
||||
.datasetUid(datasetUid)
|
||||
.compareYyyy(Integer.parseInt(compareYyyy))
|
||||
.compareClassCd(compareClassCd)
|
||||
.targetYyyy(Integer.parseInt(targetYyyy))
|
||||
.targetClassCd(targetClassCd)
|
||||
.comparePath(comparePath)
|
||||
.targetPath(targetPath)
|
||||
.labelPath(labelPath)
|
||||
.mapSheetNum(mapSheetNum)
|
||||
.geojson(root)
|
||||
.geojsonPath(geojsonPath)
|
||||
.fileName(fileName)
|
||||
.build();
|
||||
|
||||
if (subDir.equals("train")) {
|
||||
datasetClsCoreService.insertDatasetObj(objRegDto);
|
||||
} else if (subDir.equals("val")) {
|
||||
datasetClsCoreService.insertDatasetValObj(objRegDto);
|
||||
} else {
|
||||
datasetClsCoreService.insertDatasetTestObj(objRegDto);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return datasetUid;
|
||||
}
|
||||
|
||||
/**
|
||||
* CLS zip file DB화
|
||||
*
|
||||
* @param unzipRootPath
|
||||
* @param addReq
|
||||
* @return
|
||||
*/
|
||||
@Transactional
|
||||
public Long processAndInsertDataset(String unzipRootPath, AddReq addReq) {
|
||||
Path datasetRoot = Paths.get(unzipRootPath).resolve("dataset");
|
||||
|
||||
log.info("datasetRoot: {}", datasetRoot);
|
||||
Long datasetUid = null;
|
||||
boolean isMasterInserted = false; // Master(Mng) 테이블에 최초 1회만 등록하기 위한 플래그
|
||||
|
||||
// 1. 순회할 상위 폴더 목록
|
||||
for (String subDir : REQUIRED_DIRS) {
|
||||
Path subDirPath = datasetRoot.resolve(subDir);
|
||||
if (!Files.exists(subDirPath) || !Files.isDirectory(subDirPath)) {
|
||||
continue; // 해당 폴더가 없으면 다음으로 패스
|
||||
}
|
||||
|
||||
try (Stream<Path> classDirs = Files.list(subDirPath)) {
|
||||
// 2. 클래스명 폴더 목록 추출 (예: building, container, water 등)
|
||||
List<Path> classDirList = classDirs.filter(Files::isDirectory).toList();
|
||||
|
||||
for (Path classDir : classDirList) {
|
||||
// DB에 넣을 classCd (폴더명) 추출
|
||||
String classCd = classDir.getFileName().toString();
|
||||
|
||||
try (Stream<Path> files = Files.list(classDir)) {
|
||||
// 3. 해당 클래스 폴더 안의 실제 파일(tif) 목록 추출
|
||||
List<Path> filePaths =
|
||||
files
|
||||
.filter(Files::isRegularFile)
|
||||
.sorted(Comparator.comparing(Path::getFileName)) // 정렬 로직 추가
|
||||
.toList();
|
||||
|
||||
if (filePaths.isEmpty()) continue; // 폴더가 비어있으면 에러 방지를 위해 패스
|
||||
|
||||
// ★ 맨 앞의 첫 번째 파일명에서 compareYyyy 추출 및 targetYyyy 계산
|
||||
String firstFileName = filePaths.getFirst().getFileName().toString();
|
||||
String compareYyyy = firstFileName.split("_")[0]; // 예: "2021"
|
||||
String targetYyyy = String.valueOf(Integer.parseInt(compareYyyy) + 1); // 예: "2022"
|
||||
|
||||
for (Path file : filePaths) {
|
||||
String fileName = file.getFileName().toString();
|
||||
String absolutePath = file.toAbsolutePath().toString();
|
||||
String yyyy = fileName.split("_")[0];
|
||||
|
||||
// 4. 파일명 파싱 (확장자 제거 후 Split)
|
||||
// ※ 주의: 첨부하신 이미지의 파일명(2021_33607077_idx93)을 기준으로 인덱스를 재조정했습니다.
|
||||
String nameWithoutExt = fileName.substring(0, fileName.lastIndexOf('.'));
|
||||
String[] fileNameStr = nameWithoutExt.split("_");
|
||||
String mapSheetNum = fileNameStr.length > 1 ? fileNameStr[1] : ""; // 33607077
|
||||
|
||||
// 5. Master(Mng) 데이터 등록 (전체 순회 중 최초 1회만 실행)
|
||||
if (!isMasterInserted) {
|
||||
String title = compareYyyy + "-" + targetYyyy;
|
||||
Long stage =
|
||||
datasetClsCoreService.getDatasetMaxStage(
|
||||
Integer.parseInt(compareYyyy), Integer.parseInt(targetYyyy))
|
||||
+ 1;
|
||||
String uid = addReq.getFileName().replace(".zip", "");
|
||||
|
||||
DatasetMngRegDto mngRegDto =
|
||||
DatasetMngRegDto.builder()
|
||||
.uid(uid)
|
||||
.dataType(LearnDataType.PRODUCTION.getId())
|
||||
.compareYyyy(Integer.parseInt(compareYyyy))
|
||||
.targetYyyy(Integer.parseInt(targetYyyy))
|
||||
.title(title)
|
||||
.memo(addReq.getMemo())
|
||||
.roundNo(stage)
|
||||
.totalSize(addReq.getFileSize())
|
||||
.datasetPath(addReq.getFilePath() + uid)
|
||||
.cdClsType("CLS")
|
||||
.build();
|
||||
|
||||
datasetUid = datasetClsCoreService.insertDatasetMngData(mngRegDto);
|
||||
isMasterInserted = true;
|
||||
}
|
||||
|
||||
// 6. Obj(상세) 데이터 등록
|
||||
DatasetObjDto.DatasetClsObjRegDto objRegDto =
|
||||
DatasetObjDto.DatasetClsObjRegDto.builder()
|
||||
.datasetUid(datasetUid)
|
||||
.type(subDir)
|
||||
.yyyy(Integer.parseInt(yyyy))
|
||||
.classCd(classCd)
|
||||
.mapSheetNum(mapSheetNum)
|
||||
.tifImgPath(absolutePath) // 실제 파일의 절대 경로
|
||||
.fileName(fileName)
|
||||
.build();
|
||||
|
||||
// 7. 폴더 위치(train/val/test)에 맞는 테이블에 Insert
|
||||
datasetClsCoreService.insertDatasetClsObj(objRegDto);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("디렉토리 읽기 중 오류가 발생했습니다: " + subDirPath, e);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("$$$$$$$$$$datasetUid: {}", datasetUid);
|
||||
return datasetUid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cls File 목록
|
||||
*
|
||||
* @param unzipRootPath
|
||||
* @param subDir
|
||||
* @return
|
||||
*/
|
||||
private List<String> getUnzipDatasetClsFiles(String unzipRootPath, String subDir) {
|
||||
|
||||
Path root = Paths.get(unzipRootPath).resolve("dataset");
|
||||
Map<String, Map<String, Object>> grouped = new HashMap<>();
|
||||
|
||||
Path dir = root.resolve(subDir);
|
||||
|
||||
if (!Files.isDirectory(dir)) {
|
||||
throw new CustomApiException(
|
||||
ApiResponseCode.NOT_FOUND_DATA.getId(),
|
||||
HttpStatus.CONFLICT,
|
||||
"폴더가 존재하지 않습니다. 업로드 된 파일을 확인하세요. : " + dir);
|
||||
}
|
||||
|
||||
try (Stream<Path> stream = Files.list(dir)) {
|
||||
return stream
|
||||
.filter(Files::isRegularFile) // 파일만 필터링 (하위 폴더 제외)
|
||||
.map(path -> path.toAbsolutePath().toString()) // 절대 경로 문자열로 변환
|
||||
.toList(); // List로 수집 (Java 16 미만인 경우 .collect(Collectors.toList()) 사용)
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String getBaseName(String fileName) {
|
||||
int idx = fileName.lastIndexOf('.');
|
||||
return (idx > 0) ? fileName.substring(0, idx) : fileName;
|
||||
}
|
||||
|
||||
private JsonNode readJson(Path jsonPath) {
|
||||
try {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
return mapper.readTree(jsonPath.toFile());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("JSON 읽기 실패: " + jsonPath, e);
|
||||
}
|
||||
}
|
||||
|
||||
public String getFilePathByUUIDPathType(UUID uuid, String pathType) {
|
||||
return datasetClsCoreService.getFilePathByUUIDPathType(uuid, pathType);
|
||||
}
|
||||
|
||||
private String escape(String path) {
|
||||
// 쉘 커맨드에서 안전하게 사용할 수 있도록 문자열을 작은따옴표로 감싸면서, 내부의 작은따옴표를 이스케이프 처리
|
||||
return "'" + path.replace("'", "'\"'\"'") + "'";
|
||||
}
|
||||
|
||||
private static String normalizeLinuxPath(String path) {
|
||||
return path.replace("\\", "/");
|
||||
}
|
||||
|
||||
public ResponseEntity<Resource> getFilePathByFile(String remoteFilePath) {
|
||||
|
||||
try {
|
||||
Path path = Paths.get(remoteFilePath);
|
||||
InputStream inputStream = Files.newInputStream(path);
|
||||
|
||||
InputStreamResource resource =
|
||||
new InputStreamResource(inputStream) {
|
||||
@Override
|
||||
public long contentLength() {
|
||||
return -1; // 알 수 없으면 -1
|
||||
}
|
||||
};
|
||||
|
||||
String fileName = Paths.get(remoteFilePath.replace("\\", "/")).getFileName().toString();
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"")
|
||||
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
.body(resource);
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/** unzipRootDir: 압축 해제된 폴더 경로 (ex: /data/xxx/myzipname) */
|
||||
public static void validateTrainValTestDirs(String unzipRootDir) {
|
||||
Path root = Paths.get(unzipRootDir);
|
||||
|
||||
// 루트 폴더 자체 존재 확인
|
||||
if (!Files.exists(root) || !Files.isDirectory(root)) {
|
||||
throw new CustomApiException(
|
||||
ApiResponseCode.NOT_FOUND_DATA.getId(),
|
||||
HttpStatus.CONFLICT,
|
||||
"압축 해제 폴더가 존재하지 않습니다: " + unzipRootDir);
|
||||
}
|
||||
|
||||
// 필요한 폴더들 존재/디렉토리 여부 확인
|
||||
List<String> missing =
|
||||
REQUIRED_DIRS.stream()
|
||||
.filter(d -> !Files.isDirectory(root.resolve(d)))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (!missing.isEmpty()) {
|
||||
throw new CustomApiException(
|
||||
ApiResponseCode.NOT_FOUND_DATA.getId(),
|
||||
HttpStatus.CONFLICT,
|
||||
"데이터 폴더 구조가 올바르지 않습니다. 누락된 폴더: "
|
||||
+ String.join(", ", missing)
|
||||
+ " (필수: train, val, test)");
|
||||
}
|
||||
}
|
||||
|
||||
/** unzipRootDir: 압축 해제된 폴더 경로 (ex: /zip파일폴더/dataset/train,val,test) */
|
||||
public static void validateClsTrainValTestDirs(String unzipRootDir) {
|
||||
Path root = Paths.get(unzipRootDir).resolve("dataset");
|
||||
|
||||
// 루트 폴더 자체 존재 확인
|
||||
if (!Files.exists(root) || !Files.isDirectory(root)) {
|
||||
throw new CustomApiException(
|
||||
ApiResponseCode.NOT_FOUND_DATA.getId(),
|
||||
HttpStatus.CONFLICT,
|
||||
"압축 해제 폴더가 존재하지 않습니다: " + root.toAbsolutePath().toString());
|
||||
}
|
||||
|
||||
// 필요한 폴더들 존재/디렉토리 여부 확인
|
||||
List<String> missing =
|
||||
REQUIRED_DIRS.stream()
|
||||
.filter(d -> !Files.isDirectory(root.resolve(d)))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (!missing.isEmpty()) {
|
||||
throw new CustomApiException(
|
||||
ApiResponseCode.NOT_FOUND_DATA.getId(),
|
||||
HttpStatus.CONFLICT,
|
||||
"데이터 폴더 구조가 올바르지 않습니다. 누락된 폴더: "
|
||||
+ String.join(", ", missing)
|
||||
+ " (필수: train, val, test)");
|
||||
}
|
||||
}
|
||||
|
||||
public static void validateDirFileCount(String unzipRootDir) {
|
||||
Path root = Paths.get(unzipRootDir);
|
||||
|
||||
for (String split : REQUIRED_DIRS) {
|
||||
|
||||
Path splitPath = root.resolve(split);
|
||||
|
||||
Map<String, Long> fileCountMap = new HashMap<>();
|
||||
|
||||
for (String subDir : CHECK_DIRS) { // input1, input2, label 폴더만 수행하기
|
||||
|
||||
Path subDirPath = splitPath.resolve(subDir);
|
||||
|
||||
if (!Files.isDirectory(subDirPath)) {
|
||||
throw new CustomApiException(
|
||||
ApiResponseCode.NOT_FOUND_DATA.getId(),
|
||||
HttpStatus.CONFLICT,
|
||||
split + " 폴더 하위에 " + subDir + " 폴더가 존재하지 않습니다.");
|
||||
}
|
||||
|
||||
long count;
|
||||
try (Stream<Path> files = Files.list(subDirPath)) {
|
||||
count = files.filter(Files::isRegularFile).count();
|
||||
log.info("dir: " + subDirPath + ", count: " + count);
|
||||
} catch (IOException e) {
|
||||
throw new CustomApiException(
|
||||
ApiResponseCode.NOT_FOUND_DATA.getId(),
|
||||
HttpStatus.CONFLICT,
|
||||
split + "/" + subDir + " 파일 개수 확인 중 오류 발생");
|
||||
}
|
||||
|
||||
fileCountMap.put(subDir, count);
|
||||
}
|
||||
|
||||
// 모든 폴더 파일 개수가 동일한지 확인
|
||||
Set<Long> uniqueCounts = new HashSet<>(fileCountMap.values());
|
||||
|
||||
if (uniqueCounts.size() != 1) {
|
||||
throw new CustomApiException(
|
||||
ApiResponseCode.NOT_FOUND_DATA.getId(),
|
||||
HttpStatus.CONFLICT,
|
||||
split + " 데이터 파일 개수가 일치하지 않습니다. " + fileCountMap.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 폴더 조회
|
||||
*
|
||||
* @param srchDto 폴더 경로
|
||||
* @return 폴더 리스트
|
||||
* @throws IOException
|
||||
*/
|
||||
public FoldersDto getFolderAll(SrchFoldersDto srchDto) throws IOException {
|
||||
|
||||
File dir = new File(srchDto.getDirPath() == null ? "/" : srchDto.getDirPath());
|
||||
|
||||
// 존재 + 디렉토리 체크
|
||||
if (!dir.exists() || !dir.isDirectory()) {
|
||||
throw new CustomApiException("BAD_REQUEST", HttpStatus.BAD_REQUEST, "잘못된 경로입니다.");
|
||||
}
|
||||
|
||||
// 권한 없을때
|
||||
if (!dir.canRead()) {
|
||||
throw new CustomApiException(
|
||||
ApiResponseCode.FORBIDDEN.getId(), HttpStatus.FORBIDDEN, "디렉토리에 접근할 권한이 없습니다.");
|
||||
}
|
||||
|
||||
String canonicalPath = dir.getCanonicalPath();
|
||||
|
||||
File[] files = dir.listFiles();
|
||||
|
||||
if (files == null) {
|
||||
return new FoldersDto(canonicalPath, 0, 0, Collections.emptyList());
|
||||
}
|
||||
|
||||
List<FIleChecker.Folder> folders = new ArrayList<>();
|
||||
|
||||
int folderTotCnt = 0;
|
||||
int folderErrTotCnt = 0;
|
||||
|
||||
for (File f : files) {
|
||||
|
||||
// 숨김 제외
|
||||
if (f.isHidden()) continue;
|
||||
|
||||
if (f.isDirectory()) {
|
||||
|
||||
// 폴더 개수 증가
|
||||
folderTotCnt++;
|
||||
|
||||
// 폴더 유효성 여부 (기본 true, 이후 검증 로직으로 변경 가능)
|
||||
boolean isValid = true;
|
||||
|
||||
// 유효하지 않은 폴더 카운트 증가
|
||||
if (!isValid) folderErrTotCnt++;
|
||||
|
||||
// 현재 폴더 이름 (ex: train, images 등)
|
||||
String folderNm = f.getName();
|
||||
|
||||
// 부모 경로 (ex: /data/datasets)
|
||||
String parentPath = f.getParent();
|
||||
|
||||
// 부모 폴더 이름 (ex: datasets)
|
||||
String parentFolderNm = new File(parentPath).getName();
|
||||
|
||||
// 전체 절대 경로 (ex: /data/datasets/train)
|
||||
String fullPath = f.getAbsolutePath();
|
||||
|
||||
// 폴더 깊이 (경로 기준 depth)
|
||||
// ex: /a/b/c → depth = 3
|
||||
int depth = f.toPath().getNameCount();
|
||||
|
||||
// 하위 폴더 개수
|
||||
long childCnt = FIleChecker.getChildFolderCount(f);
|
||||
|
||||
// 마지막 수정 시간 (문자열 포맷)
|
||||
String lastModified = FIleChecker.getLastModified(f);
|
||||
|
||||
// Folder DTO 생성 및 리스트에 추가
|
||||
folders.add(
|
||||
new FIleChecker.Folder(
|
||||
folderNm, // 폴더명
|
||||
parentFolderNm, // 부모 폴더명
|
||||
parentPath, // 부모 경로
|
||||
fullPath, // 전체 경로
|
||||
depth, // 깊이
|
||||
childCnt, // 하위 폴더 개수
|
||||
lastModified, // 수정일시
|
||||
isValid // 유효성 여부
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// 폴더 정렬
|
||||
folders.sort(
|
||||
Comparator.comparing(FIleChecker.Folder::getFolderNm, String.CASE_INSENSITIVE_ORDER));
|
||||
|
||||
return new FoldersDto(canonicalPath, folderTotCnt, folderErrTotCnt, folders);
|
||||
}
|
||||
|
||||
/**
|
||||
* 납품 데이터 등록
|
||||
*
|
||||
* @param req 폴더경로, 메모
|
||||
* @return 성공/실패 여부0
|
||||
*/
|
||||
public void insertDeliveriesDataset(AddDeliveriesReq req, Long datasetUid) {
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
// 처리
|
||||
processType(req.getFilePath(), datasetUid, "train");
|
||||
processType(req.getFilePath(), datasetUid, "val");
|
||||
processType(req.getFilePath(), datasetUid, "test");
|
||||
|
||||
log.info("========== 전체 완료. 총 소요시간: {} ms ==========", System.currentTimeMillis() - startTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 납품 데이터 등록 처리
|
||||
*
|
||||
* @param path
|
||||
* @param datasetUid
|
||||
* @param type
|
||||
*/
|
||||
private void processType(String path, Long datasetUid, String type) {
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
log.info("[납품 데이터 등록 처리][{}] 시작", type.toUpperCase());
|
||||
|
||||
List<Map<String, Object>> list = getUnzipDatasetFiles(path, type);
|
||||
|
||||
int batchSize = 1000;
|
||||
int total = list.size();
|
||||
int processed = 0;
|
||||
|
||||
for (int i = 0; i < total; i += batchSize) {
|
||||
|
||||
List<Map<String, Object>> batch = list.subList(i, Math.min(i + batchSize, total));
|
||||
|
||||
try {
|
||||
log.info("[납품 데이터 등록 처리][{}] batch 시작: {} ~ {}", type, i, i + batch.size());
|
||||
|
||||
datasetBatchService.saveBatch(batch, datasetUid, type);
|
||||
|
||||
processed += batch.size();
|
||||
} catch (Exception e) {
|
||||
log.error("batch 실패 row 데이터: {}", batch);
|
||||
log.error(
|
||||
"[납품 데이터 등록 처리][{}] batch 실패. range: {} ~ {}, datasetUid={}",
|
||||
type,
|
||||
i,
|
||||
i + batch.size(),
|
||||
datasetUid,
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
log.info(
|
||||
"[납품 데이터 등록 처리][{}] 완료. 총 {}건, 소요시간: {} ms",
|
||||
type,
|
||||
total,
|
||||
System.currentTimeMillis() - start);
|
||||
}
|
||||
|
||||
public void validateExistsUidChk(String filePath) {
|
||||
Path selectedPath = Paths.get(filePath);
|
||||
String uid = selectedPath.getFileName().toString();
|
||||
|
||||
// 같은 uid 로 등록한 파일이 있는지 확인
|
||||
Long existsCnt = datasetClsCoreService.findDatasetByUidExistsCnt(uid);
|
||||
if (existsCnt > 0) {
|
||||
throw new CustomApiException(
|
||||
ApiResponseCode.DUPLICATE_DATA.getId(),
|
||||
HttpStatus.CONFLICT,
|
||||
"이미 등록된 회차 데이터 파일입니다. 확인 부탁드립니다.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
package com.kamco.cd.training.postgres.core;
|
||||
|
||||
import com.kamco.cd.training.common.enums.LearnDataRegister;
|
||||
import com.kamco.cd.training.common.enums.LearnDataType;
|
||||
import com.kamco.cd.training.common.exception.NotFoundException;
|
||||
import com.kamco.cd.training.common.service.BaseCoreService;
|
||||
import com.kamco.cd.training.dataset.dto.DatasetClsObjDto;
|
||||
import com.kamco.cd.training.dataset.dto.DatasetDto;
|
||||
import com.kamco.cd.training.dataset.dto.DatasetDto.DatasetMngRegDto;
|
||||
import com.kamco.cd.training.dataset.dto.DatasetObjDto;
|
||||
import com.kamco.cd.training.dataset.dto.DatasetObjDto.DatasetClass;
|
||||
import com.kamco.cd.training.dataset.dto.DatasetObjDto.DatasetObjRegDto;
|
||||
import com.kamco.cd.training.postgres.entity.DatasetClsObjEntity;
|
||||
import com.kamco.cd.training.postgres.entity.DatasetEntity;
|
||||
import com.kamco.cd.training.postgres.entity.DatasetObjEntity;
|
||||
import com.kamco.cd.training.postgres.repository.dataset.DatasetClsRepository;
|
||||
import com.kamco.cd.training.postgres.repository.dataset.DatasetObjRepository;
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class DatasetClsCoreService
|
||||
implements BaseCoreService<DatasetDto.Basic, Long, DatasetDto.SearchReq> {
|
||||
|
||||
private final DatasetClsRepository datasetClsRepository;
|
||||
private final DatasetObjRepository datasetObjRepository;
|
||||
|
||||
/**
|
||||
* 학습 데이터 삭제
|
||||
*
|
||||
* @param id 데이터셋 ID
|
||||
*/
|
||||
@Override
|
||||
public void remove(Long id) {
|
||||
DatasetEntity entity =
|
||||
datasetClsRepository
|
||||
.findById(id)
|
||||
.orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. ID: " + id));
|
||||
entity.setDeleted(true);
|
||||
datasetClsRepository.save(entity);
|
||||
}
|
||||
|
||||
public void remove(UUID id) {
|
||||
DatasetEntity entity =
|
||||
datasetClsRepository
|
||||
.findByUuid(id)
|
||||
.orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. ID: " + id));
|
||||
entity.setDeleted(true);
|
||||
datasetClsRepository.save(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DatasetDto.Basic getOneById(Long id) {
|
||||
DatasetEntity entity =
|
||||
datasetClsRepository
|
||||
.findById(id)
|
||||
.orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. ID: " + id));
|
||||
|
||||
if (entity.getDeleted()) {
|
||||
throw new NotFoundException("삭제된 데이터셋입니다. ID: " + id);
|
||||
}
|
||||
|
||||
return entity.toDto();
|
||||
}
|
||||
|
||||
public DatasetDto.Basic getOneByUuid(UUID id) {
|
||||
DatasetEntity entity =
|
||||
datasetClsRepository
|
||||
.findByUuid(id)
|
||||
.orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. uuid: " + id));
|
||||
return entity.toDto();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<DatasetDto.Basic> search(DatasetDto.SearchReq searchReq) {
|
||||
Page<DatasetEntity> entityPage = datasetClsRepository.findDatasetList(searchReq);
|
||||
return entityPage.map(DatasetEntity::toDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 학습데이터 조회
|
||||
*
|
||||
* @param searchReq 검색 조건
|
||||
* @return 페이징 처리된 데이터셋 목록
|
||||
*/
|
||||
public Page<DatasetDto.Basic> findDatasetList(DatasetDto.SearchReq searchReq) {
|
||||
return search(searchReq);
|
||||
}
|
||||
|
||||
// TODO 미사용시작
|
||||
/**
|
||||
* 학습데이터 등록
|
||||
*
|
||||
* @param registerReq 등록 요청 데이터
|
||||
* @return 등록된 데이터셋 정보
|
||||
*/
|
||||
public DatasetDto.Basic save(DatasetDto.RegisterReq registerReq) {
|
||||
// 먼저 id1 필드를 임시값(0)으로 설정하여 저장
|
||||
DatasetEntity entity = new DatasetEntity();
|
||||
entity.setTitle(registerReq.getTitle());
|
||||
// entity.setYear(registerReq.getYear());
|
||||
entity.setDataType(LearnDataType.PRODUCTION.getId());
|
||||
entity.setCompareYyyy(registerReq.getCompareYear());
|
||||
entity.setTargetYyyy(registerReq.getTargetYyyy());
|
||||
entity.setRoundNo(registerReq.getRoundNo() != null ? registerReq.getRoundNo() : 1L);
|
||||
entity.setMemo(registerReq.getMemo());
|
||||
entity.setStatus(LearnDataRegister.READY.getId());
|
||||
entity.setDataType("CREATE");
|
||||
entity.setTotalItems(0L);
|
||||
entity.setTotalSize(0L);
|
||||
entity.setItemCount(0L);
|
||||
entity.setDeleted(false);
|
||||
entity.setCreatedDttm(ZonedDateTime.now());
|
||||
// entity.setId1(0L); // 임시값
|
||||
|
||||
DatasetEntity savedEntity = datasetClsRepository.save(entity);
|
||||
|
||||
// 저장 후 id1을 dataset_uid와 동일하게 업데이트
|
||||
// savedEntity.setId1(savedEntity.getId());
|
||||
// savedEntity = datasetClsRepository.save(savedEntity);
|
||||
|
||||
return savedEntity.toDto();
|
||||
}
|
||||
|
||||
// TODO 미사용 끝
|
||||
/**
|
||||
* 학습 데이터 수정
|
||||
*
|
||||
* @param updateReq 수정 요청 데이터
|
||||
* @return 수정된 데이터셋 정보
|
||||
*/
|
||||
public void update(UUID uuid, DatasetDto.UpdateReq updateReq) {
|
||||
DatasetEntity entity =
|
||||
datasetClsRepository
|
||||
.findByUuid(uuid)
|
||||
.orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. uuid: " + uuid));
|
||||
|
||||
if (StringUtils.isNotBlank(updateReq.getTitle())) {
|
||||
entity.setTitle(updateReq.getTitle());
|
||||
}
|
||||
if (StringUtils.isNotBlank(updateReq.getMemo())) {
|
||||
entity.setMemo(updateReq.getMemo());
|
||||
}
|
||||
|
||||
datasetClsRepository.save(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 학습데이터 삭제
|
||||
*
|
||||
* @param uuid 삭제 요청 (데이터셋 ID 목록)
|
||||
*/
|
||||
public void deleteDatasets(UUID uuid) {
|
||||
remove(uuid);
|
||||
}
|
||||
|
||||
public DatasetDto.Summary getDatasetSummary(DatasetDto.SummaryReq summaryReq) {
|
||||
long totalMapSheets = 0;
|
||||
long totalFileSize = 0;
|
||||
|
||||
for (Long datasetId : summaryReq.getDatasetIds()) {
|
||||
DatasetEntity entity =
|
||||
datasetClsRepository
|
||||
.findById(datasetId)
|
||||
.orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. ID: " + datasetId));
|
||||
|
||||
if (!entity.getDeleted()) {
|
||||
totalMapSheets += entity.getTotalItems() != null ? entity.getTotalItems() : 0;
|
||||
totalFileSize += entity.getTotalSize() != null ? entity.getTotalSize() : 0;
|
||||
}
|
||||
}
|
||||
|
||||
double averageMapSheets =
|
||||
!summaryReq.getDatasetIds().isEmpty()
|
||||
? (double) totalMapSheets / summaryReq.getDatasetIds().size()
|
||||
: 0;
|
||||
|
||||
return new DatasetDto.Summary(
|
||||
summaryReq.getDatasetIds().size(), totalMapSheets, totalFileSize, averageMapSheets);
|
||||
}
|
||||
|
||||
public Page<DatasetClsObjDto.Basic> searchDatasetClsObjectList(
|
||||
DatasetClsObjDto.ClsSearchReq searchReq) {
|
||||
Page<DatasetClsObjEntity> entityPage =
|
||||
datasetClsRepository.searchDatasetClsObjectList(searchReq);
|
||||
return entityPage.map(DatasetClsObjEntity::toDto);
|
||||
}
|
||||
|
||||
public UUID deleteDatasetObjByUuid(UUID uuid) {
|
||||
DatasetObjEntity entity =
|
||||
datasetObjRepository
|
||||
.findByUuid(uuid)
|
||||
.orElseThrow(() -> new NotFoundException("데이터셋 obj를 찾을 수 없습니다. ID: " + uuid));
|
||||
entity.setDeleted(true);
|
||||
datasetObjRepository.save(entity);
|
||||
return entity.getUuid();
|
||||
}
|
||||
|
||||
/**
|
||||
* 데이터셋 object class 조회
|
||||
*
|
||||
* @param uuid dataset uuid
|
||||
* @param type compare, target
|
||||
* @return
|
||||
*/
|
||||
public List<DatasetClass> findDatasetObjClassByUuid(UUID uuid, String type) {
|
||||
return datasetObjRepository.findDatasetObjClassByUuid(uuid, type);
|
||||
}
|
||||
|
||||
public Long getDatasetMaxStage(int compareYyyy, int targetYyyy) {
|
||||
return datasetClsRepository.getDatasetMaxStage(compareYyyy, targetYyyy);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Long insertDatasetMngData(DatasetMngRegDto mngRegDto) {
|
||||
return datasetClsRepository.insertDatasetMngData(mngRegDto);
|
||||
}
|
||||
|
||||
public void insertDatasetObj(DatasetObjRegDto objRegDto) {
|
||||
datasetObjRepository.insertDatasetObj(objRegDto);
|
||||
}
|
||||
|
||||
public String getFilePathByUUIDPathType(UUID uuid, String pathType) {
|
||||
return datasetObjRepository.getFilePathByUUIDPathType(uuid, pathType);
|
||||
}
|
||||
|
||||
public void insertDatasetTestObj(DatasetObjRegDto objRegDto) {
|
||||
datasetObjRepository.insertDatasetTestObj(objRegDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 학습데이터셋 마스터 상태 변경
|
||||
*
|
||||
* @param datasetUid 학습데이터셋 마스터 id
|
||||
* @param register 상태
|
||||
*/
|
||||
@Transactional
|
||||
public void updateDatasetUploadStatus(Long datasetUid, LearnDataRegister register) {
|
||||
DatasetEntity entity =
|
||||
datasetClsRepository
|
||||
.findById(datasetUid)
|
||||
.orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. ID: " + datasetUid));
|
||||
|
||||
entity.setStatus(register.getId());
|
||||
}
|
||||
|
||||
public void insertDatasetValObj(DatasetObjRegDto objRegDto) {
|
||||
datasetObjRepository.insertDatasetValObj(objRegDto);
|
||||
}
|
||||
|
||||
public Long findDatasetByUidExistsCnt(String uid) {
|
||||
return datasetClsRepository.findDatasetByUidExistsCnt(uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 데이터셋 등록 실패시 Obj 데이터 정리
|
||||
*
|
||||
* @param datasetUid 모델 마스터 id
|
||||
*/
|
||||
@Transactional
|
||||
public void deleteAllDatasetObj(Long datasetUid) {
|
||||
int cnt = datasetObjRepository.deleteAllDatasetObj(datasetUid);
|
||||
log.info("datasetUid={} 데이터셋 실패 - 전체 삭제 완료. 총 {}건", datasetUid, cnt);
|
||||
}
|
||||
|
||||
public void insertDatasetClsObj(DatasetObjDto.DatasetClsObjRegDto objRegDto) {
|
||||
datasetClsRepository.insertDatasetClsObj(objRegDto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.kamco.cd.training.postgres.entity;
|
||||
|
||||
import com.kamco.cd.training.dataset.dto.DatasetClsObjDto.Basic;
|
||||
import jakarta.persistence.*;
|
||||
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;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "tb_dataset_cls_obj")
|
||||
public class DatasetClsObjEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "obj_id", nullable = false)
|
||||
private Long objId;
|
||||
|
||||
@Column(name = "uuid")
|
||||
private UUID uuid;
|
||||
|
||||
@NotNull
|
||||
@Column(name = "dataset_uid", nullable = false)
|
||||
private Long datasetUid;
|
||||
|
||||
@Column(name = "type")
|
||||
private String type;
|
||||
|
||||
@Column(name = "yyyy")
|
||||
private Integer yyyy;
|
||||
|
||||
@Size(max = 255)
|
||||
@Column(name = "class_cd")
|
||||
private String classCd;
|
||||
|
||||
@Size(max = 255)
|
||||
@Column(name = "map_sheet_num")
|
||||
private String mapSheetNum;
|
||||
|
||||
@Column(name = "tif_img_path")
|
||||
private String tifImgPath;
|
||||
|
||||
@Column(name = "file_name")
|
||||
private String fileName;
|
||||
|
||||
@ColumnDefault("now()")
|
||||
@Column(name = "created_dttm")
|
||||
private ZonedDateTime createdDttm;
|
||||
|
||||
@Column(name = "created_uid")
|
||||
private Long createdUid;
|
||||
|
||||
@ColumnDefault("false")
|
||||
@Column(name = "deleted")
|
||||
private Boolean deleted;
|
||||
|
||||
public Basic toDto() {
|
||||
return new Basic(
|
||||
this.objId,
|
||||
this.uuid,
|
||||
this.datasetUid,
|
||||
this.type,
|
||||
this.yyyy,
|
||||
this.classCd,
|
||||
this.mapSheetNum,
|
||||
this.tifImgPath,
|
||||
this.fileName,
|
||||
this.createdDttm,
|
||||
this.createdUid,
|
||||
this.deleted);
|
||||
}
|
||||
}
|
||||
@@ -127,6 +127,9 @@ public class DatasetEntity {
|
||||
@Column(name = "uid")
|
||||
private String uid;
|
||||
|
||||
@Column(name = "cd_cls_type")
|
||||
private String cdClsType;
|
||||
|
||||
public DatasetDto.Basic toDto() {
|
||||
return new DatasetDto.Basic(
|
||||
this.id,
|
||||
@@ -140,6 +143,7 @@ public class DatasetEntity {
|
||||
this.createdDttm,
|
||||
this.status,
|
||||
this.deleted,
|
||||
this.dataType);
|
||||
this.dataType,
|
||||
this.cdClsType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.kamco.cd.training.postgres.repository.dataset;
|
||||
|
||||
import com.kamco.cd.training.postgres.entity.DatasetEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface DatasetClsRepository
|
||||
extends JpaRepository<DatasetEntity, Long>, DatasetClsRepositoryCustom {}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.kamco.cd.training.postgres.repository.dataset;
|
||||
|
||||
import com.kamco.cd.training.dataset.dto.DatasetClsObjDto;
|
||||
import com.kamco.cd.training.dataset.dto.DatasetDto;
|
||||
import com.kamco.cd.training.dataset.dto.DatasetDto.DatasetMngRegDto;
|
||||
import com.kamco.cd.training.dataset.dto.DatasetDto.DatasetReq;
|
||||
import com.kamco.cd.training.dataset.dto.DatasetDto.SelectDataSet;
|
||||
import com.kamco.cd.training.dataset.dto.DatasetDto.SelectTransferDataSet;
|
||||
import com.kamco.cd.training.dataset.dto.DatasetObjDto;
|
||||
import com.kamco.cd.training.postgres.entity.DatasetClsObjEntity;
|
||||
import com.kamco.cd.training.postgres.entity.DatasetEntity;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import org.springframework.data.domain.Page;
|
||||
|
||||
public interface DatasetClsRepositoryCustom {
|
||||
Page<DatasetEntity> findDatasetList(DatasetDto.SearchReq searchReq);
|
||||
|
||||
Optional<DatasetEntity> findByUuid(UUID id);
|
||||
|
||||
List<SelectDataSet> getDatasetSelectG1List(DatasetReq req);
|
||||
|
||||
// TODO 미사용시작
|
||||
public List<SelectTransferDataSet> getDatasetTransferSelectG1List(Long modelId);
|
||||
|
||||
public List<SelectTransferDataSet> getDatasetTransferSelectG2G3List(Long modelId, String modelNo);
|
||||
|
||||
// TODO 미사용 끝
|
||||
|
||||
List<SelectDataSet> getDatasetSelectG2G3List(DatasetReq req);
|
||||
|
||||
List<SelectDataSet> getDatasetSelectG4List(DatasetReq req);
|
||||
|
||||
Long getDatasetMaxStage(int compareYyyy, int targetYyyy);
|
||||
|
||||
Long insertDatasetMngData(DatasetMngRegDto mngRegDto);
|
||||
|
||||
List<String> findDatasetUid(List<Long> datasetIds);
|
||||
|
||||
Long findDatasetByUidExistsCnt(String uid);
|
||||
|
||||
Page<DatasetClsObjEntity> searchDatasetClsObjectList(DatasetClsObjDto.ClsSearchReq searchReq);
|
||||
|
||||
void insertDatasetClsObj(DatasetObjDto.DatasetClsObjRegDto objRegDto);
|
||||
}
|
||||
@@ -0,0 +1,659 @@
|
||||
package com.kamco.cd.training.postgres.repository.dataset;
|
||||
|
||||
import static com.kamco.cd.training.postgres.entity.QDatasetClsObjEntity.datasetClsObjEntity;
|
||||
import static com.kamco.cd.training.postgres.entity.QDatasetEntity.datasetEntity;
|
||||
import static com.kamco.cd.training.postgres.entity.QDatasetObjEntity.datasetObjEntity;
|
||||
import static com.kamco.cd.training.postgres.entity.QModelDatasetMappEntity.modelDatasetMappEntity;
|
||||
import static com.kamco.cd.training.postgres.entity.QModelMasterEntity.modelMasterEntity;
|
||||
|
||||
import com.kamco.cd.training.common.enums.DetectionClassification;
|
||||
import com.kamco.cd.training.common.enums.ModelType;
|
||||
import com.kamco.cd.training.dataset.dto.DatasetClsObjDto;
|
||||
import com.kamco.cd.training.dataset.dto.DatasetDto.*;
|
||||
import com.kamco.cd.training.dataset.dto.DatasetObjDto;
|
||||
import com.kamco.cd.training.postgres.entity.*;
|
||||
import com.querydsl.core.BooleanBuilder;
|
||||
import com.querydsl.core.types.Projections;
|
||||
import com.querydsl.core.types.dsl.CaseBuilder;
|
||||
import com.querydsl.core.types.dsl.Expressions;
|
||||
import com.querydsl.core.types.dsl.NumberExpression;
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
import jakarta.persistence.PersistenceContext;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
@RequiredArgsConstructor
|
||||
public class DatasetClsRepositoryImpl implements DatasetClsRepositoryCustom {
|
||||
|
||||
private final JPAQueryFactory queryFactory;
|
||||
private final QDatasetEntity dataset = QDatasetEntity.datasetEntity;
|
||||
|
||||
@PersistenceContext private final EntityManager em;
|
||||
|
||||
/**
|
||||
* 데이터셋 목록 조회
|
||||
*
|
||||
* @param searchReq 검색 조건
|
||||
* @return 페이징 처리된 데이터셋 Entity 목록
|
||||
*/
|
||||
@Override
|
||||
public Page<DatasetEntity> findDatasetList(SearchReq searchReq) {
|
||||
Pageable pageable = searchReq.toPageable();
|
||||
BooleanBuilder builder = new BooleanBuilder();
|
||||
|
||||
// 제목
|
||||
if (StringUtils.isNotBlank(searchReq.getTitle())) {
|
||||
String contains = "%" + searchReq.getTitle() + "%";
|
||||
builder.and(dataset.title.likeIgnoreCase(contains));
|
||||
}
|
||||
|
||||
// 구분
|
||||
if (StringUtils.isNotBlank(searchReq.getDataType())) {
|
||||
builder.and(dataset.dataType.eq(searchReq.getDataType()));
|
||||
}
|
||||
|
||||
// Entity 직접 조회 (Projections 사용 지양)
|
||||
List<DatasetEntity> content =
|
||||
queryFactory
|
||||
.selectFrom(dataset)
|
||||
.where(builder.and(dataset.deleted.isFalse()).and(dataset.cdClsType.eq("CLS")))
|
||||
.offset(pageable.getOffset())
|
||||
.limit(pageable.getPageSize())
|
||||
.orderBy(dataset.createdDttm.desc())
|
||||
.fetch();
|
||||
|
||||
// Count 쿼리 별도 실행 (null safe handling)
|
||||
long total =
|
||||
Optional.ofNullable(
|
||||
queryFactory
|
||||
.select(dataset.count())
|
||||
.from(dataset)
|
||||
.where(builder.and(dataset.deleted.isFalse()).and(dataset.cdClsType.eq("CLS")))
|
||||
.fetchOne())
|
||||
.orElse(0L);
|
||||
|
||||
return new PageImpl<>(content, pageable, total);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<DatasetEntity> findByUuid(UUID id) {
|
||||
return Optional.ofNullable(
|
||||
queryFactory
|
||||
.select(dataset)
|
||||
.from(dataset)
|
||||
.where(dataset.uuid.eq(id), dataset.deleted.isFalse())
|
||||
.fetchOne());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SelectDataSet> getDatasetSelectG1List(DatasetReq req) {
|
||||
|
||||
BooleanBuilder builder = new BooleanBuilder();
|
||||
|
||||
builder.and(dataset.deleted.isFalse());
|
||||
|
||||
if (StringUtils.isNotBlank(req.getDataType()) && !"CURRENT".equals(req.getDataType())) {
|
||||
builder.and(dataset.dataType.eq(req.getDataType()));
|
||||
}
|
||||
|
||||
if (req.getIds() != null) {
|
||||
builder.and(dataset.id.in(req.getIds()));
|
||||
}
|
||||
|
||||
return queryFactory
|
||||
.select(
|
||||
Projections.constructor(
|
||||
SelectDataSet.class,
|
||||
Expressions.constant(req.getModelNo()),
|
||||
dataset.id,
|
||||
dataset.uuid,
|
||||
dataset.dataType,
|
||||
dataset.title,
|
||||
dataset.roundNo,
|
||||
dataset.compareYyyy,
|
||||
dataset.targetYyyy,
|
||||
dataset.memo,
|
||||
new CaseBuilder()
|
||||
.when(
|
||||
datasetObjEntity.targetClassCd.eq(DetectionClassification.BUILDING.getId()))
|
||||
.then(1L)
|
||||
.otherwise(0L)
|
||||
.sum(),
|
||||
new CaseBuilder()
|
||||
.when(
|
||||
datasetObjEntity.targetClassCd.eq(
|
||||
DetectionClassification.CONTAINER.getId()))
|
||||
.then(1L)
|
||||
.otherwise(0L)
|
||||
.sum()))
|
||||
.from(dataset)
|
||||
.leftJoin(datasetObjEntity)
|
||||
.on(dataset.id.eq(datasetObjEntity.datasetUid))
|
||||
.where(builder) // datasetObjEntity.targetClassCd.in("building", "container").and(
|
||||
.groupBy(
|
||||
dataset.id,
|
||||
dataset.uuid,
|
||||
dataset.dataType,
|
||||
dataset.title,
|
||||
dataset.roundNo,
|
||||
dataset.memo)
|
||||
.orderBy(dataset.createdDttm.desc())
|
||||
.fetch();
|
||||
}
|
||||
|
||||
// TODO 미사용시작
|
||||
@Override
|
||||
public List<SelectTransferDataSet> getDatasetTransferSelectG1List(Long modelId) {
|
||||
|
||||
QModelMasterEntity beforeMaster = new QModelMasterEntity("beforeMaster");
|
||||
QModelDatasetMappEntity beforeMapp = new QModelDatasetMappEntity("beforeMapp");
|
||||
QDatasetEntity beforeDataset = new QDatasetEntity("beforeDataset");
|
||||
QDatasetObjEntity beforeObj = new QDatasetObjEntity("beforeObj");
|
||||
|
||||
return queryFactory
|
||||
.select(
|
||||
Projections.constructor(
|
||||
SelectTransferDataSet.class,
|
||||
|
||||
// ===== 현재 =====
|
||||
modelMasterEntity.modelNo,
|
||||
dataset.id,
|
||||
dataset.uuid,
|
||||
dataset.dataType,
|
||||
dataset.title,
|
||||
dataset.roundNo,
|
||||
dataset.compareYyyy,
|
||||
dataset.targetYyyy,
|
||||
dataset.memo,
|
||||
new CaseBuilder()
|
||||
.when(datasetObjEntity.targetClassCd.eq("building"))
|
||||
.then(1)
|
||||
.otherwise(0)
|
||||
.sum(),
|
||||
new CaseBuilder()
|
||||
.when(datasetObjEntity.targetClassCd.eq("container"))
|
||||
.then(1)
|
||||
.otherwise(0)
|
||||
.sum(),
|
||||
|
||||
// ===== before (join으로) =====
|
||||
beforeMaster.modelNo,
|
||||
beforeDataset.id,
|
||||
beforeDataset.uuid,
|
||||
beforeDataset.dataType,
|
||||
beforeDataset.title,
|
||||
beforeDataset.roundNo,
|
||||
beforeDataset.compareYyyy,
|
||||
beforeDataset.targetYyyy,
|
||||
beforeDataset.memo,
|
||||
new CaseBuilder()
|
||||
.when(beforeObj.targetClassCd.eq("building"))
|
||||
.then(1)
|
||||
.otherwise(0)
|
||||
.sum(),
|
||||
new CaseBuilder()
|
||||
.when(beforeObj.targetClassCd.eq("container"))
|
||||
.then(1)
|
||||
.otherwise(0)
|
||||
.sum()))
|
||||
.from(modelMasterEntity)
|
||||
|
||||
// ===== 현재 dataset join =====
|
||||
.leftJoin(modelDatasetMappEntity)
|
||||
.on(modelDatasetMappEntity.modelUid.eq(modelMasterEntity.id))
|
||||
.leftJoin(dataset)
|
||||
.on(modelDatasetMappEntity.datasetUid.eq(dataset.id))
|
||||
.leftJoin(datasetObjEntity)
|
||||
.on(dataset.id.eq(datasetObjEntity.datasetUid))
|
||||
|
||||
// ===== before 모델 join =====
|
||||
.leftJoin(beforeMaster)
|
||||
.on(beforeMaster.id.eq(modelMasterEntity.beforeModelId))
|
||||
.leftJoin(beforeMapp)
|
||||
.on(beforeMapp.modelUid.eq(beforeMaster.id))
|
||||
.leftJoin(beforeDataset)
|
||||
.on(beforeMapp.datasetUid.eq(beforeDataset.id))
|
||||
.leftJoin(beforeObj)
|
||||
.on(beforeDataset.id.eq(beforeObj.datasetUid))
|
||||
.where(modelMasterEntity.id.eq(modelId))
|
||||
.groupBy(
|
||||
modelMasterEntity.modelNo,
|
||||
dataset.id,
|
||||
dataset.uuid,
|
||||
dataset.dataType,
|
||||
dataset.title,
|
||||
dataset.roundNo,
|
||||
dataset.compareYyyy,
|
||||
dataset.targetYyyy,
|
||||
dataset.memo,
|
||||
beforeMaster.modelNo,
|
||||
beforeDataset.id,
|
||||
beforeDataset.uuid,
|
||||
beforeDataset.dataType,
|
||||
beforeDataset.title,
|
||||
beforeDataset.roundNo,
|
||||
beforeDataset.compareYyyy,
|
||||
beforeDataset.targetYyyy,
|
||||
beforeDataset.memo)
|
||||
.orderBy(dataset.createdDttm.desc())
|
||||
.fetch();
|
||||
}
|
||||
|
||||
// TODO 미사용 끝
|
||||
|
||||
@Override
|
||||
public List<SelectDataSet> getDatasetSelectG2G3List(DatasetReq req) {
|
||||
|
||||
String building = DetectionClassification.BUILDING.getId();
|
||||
String container = DetectionClassification.CONTAINER.getId();
|
||||
String waste = DetectionClassification.WASTE.getId();
|
||||
String solar = DetectionClassification.SOLAR.getId();
|
||||
|
||||
BooleanBuilder builder = new BooleanBuilder();
|
||||
builder.and(dataset.deleted.isFalse());
|
||||
|
||||
NumberExpression<Long> selectedCnt = null;
|
||||
// G2
|
||||
NumberExpression<Long> wasteCnt =
|
||||
datasetObjEntity
|
||||
.targetClassCd
|
||||
.when(DetectionClassification.WASTE.getId())
|
||||
.then(1L)
|
||||
.otherwise(0L)
|
||||
.sum();
|
||||
|
||||
// G3 (G1, G2, G4 제외)
|
||||
NumberExpression<Long> elseCnt =
|
||||
new CaseBuilder()
|
||||
.when(datasetObjEntity.targetClassCd.notIn(building, container, waste, solar))
|
||||
.then(1L)
|
||||
.otherwise(0L)
|
||||
.sum();
|
||||
|
||||
if (req.getModelNo().equals(ModelType.G2.getId())) {
|
||||
selectedCnt = wasteCnt;
|
||||
} else {
|
||||
selectedCnt = elseCnt;
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(req.getDataType())) {
|
||||
if (!"CURRENT".equals(req.getDataType())) {
|
||||
builder.and(dataset.dataType.eq(req.getDataType()));
|
||||
}
|
||||
}
|
||||
|
||||
if (req.getIds() != null) {
|
||||
builder.and(dataset.id.in(req.getIds()));
|
||||
}
|
||||
|
||||
return queryFactory
|
||||
.select(
|
||||
Projections.constructor(
|
||||
SelectDataSet.class,
|
||||
Expressions.constant(req.getModelNo()),
|
||||
dataset.id,
|
||||
dataset.uuid,
|
||||
dataset.dataType,
|
||||
dataset.title,
|
||||
dataset.roundNo,
|
||||
dataset.compareYyyy,
|
||||
dataset.targetYyyy,
|
||||
dataset.memo,
|
||||
selectedCnt.as("cnt")))
|
||||
.from(dataset)
|
||||
.leftJoin(datasetObjEntity)
|
||||
.on(dataset.id.eq(datasetObjEntity.datasetUid))
|
||||
.where(builder)
|
||||
.groupBy(
|
||||
dataset.id,
|
||||
dataset.uuid,
|
||||
dataset.dataType,
|
||||
dataset.title,
|
||||
dataset.roundNo,
|
||||
dataset.memo)
|
||||
.orderBy(dataset.createdDttm.desc())
|
||||
.fetch();
|
||||
}
|
||||
|
||||
// TODO 미사용시작
|
||||
@Override
|
||||
public List<SelectTransferDataSet> getDatasetTransferSelectG2G3List(
|
||||
Long modelId, String modelNo) {
|
||||
|
||||
// before join용
|
||||
QModelMasterEntity beforeMaster = new QModelMasterEntity("beforeMaster");
|
||||
QModelDatasetMappEntity beforeMapp = new QModelDatasetMappEntity("beforeMapp");
|
||||
QDatasetEntity beforeDataset = new QDatasetEntity("beforeDataset");
|
||||
QDatasetObjEntity beforeObj = new QDatasetObjEntity("beforeObj");
|
||||
|
||||
BooleanBuilder builder = new BooleanBuilder();
|
||||
|
||||
NumberExpression<Long> wasteCnt =
|
||||
datasetObjEntity.targetClassCd.when("waste").then(1L).otherwise(0L).sum();
|
||||
|
||||
NumberExpression<Long> elseCnt =
|
||||
new CaseBuilder()
|
||||
.when(datasetObjEntity.targetClassCd.notIn("building", "container", "waste"))
|
||||
.then(1L)
|
||||
.otherwise(0L)
|
||||
.sum();
|
||||
|
||||
NumberExpression<Long> selectedCnt = ModelType.G2.getId().equals(modelNo) ? wasteCnt : elseCnt;
|
||||
|
||||
// before도 동일 로직으로 cnt 계산
|
||||
NumberExpression<Long> beforeWasteCnt =
|
||||
beforeObj.targetClassCd.when("waste").then(1L).otherwise(0L).sum();
|
||||
|
||||
NumberExpression<Long> beforeElseCnt =
|
||||
new CaseBuilder()
|
||||
.when(beforeObj.targetClassCd.notIn("building", "container", "waste"))
|
||||
.then(1L)
|
||||
.otherwise(0L)
|
||||
.sum();
|
||||
|
||||
NumberExpression<Long> beforeSelectedCnt =
|
||||
ModelType.G2.getId().equals(modelNo) ? beforeWasteCnt : beforeElseCnt;
|
||||
|
||||
return queryFactory
|
||||
.select(
|
||||
Projections.constructor(
|
||||
SelectTransferDataSet.class,
|
||||
|
||||
// ===== 현재 =====
|
||||
modelMasterEntity.modelNo, // modelNo 파라미터 사용 (req.getModelNo() 제거)
|
||||
dataset.id,
|
||||
dataset.uuid,
|
||||
dataset.dataType,
|
||||
dataset.title,
|
||||
dataset.roundNo,
|
||||
dataset.compareYyyy,
|
||||
dataset.targetYyyy,
|
||||
dataset.memo,
|
||||
selectedCnt, // classCount 자리에 들어가는 cnt (Long)
|
||||
|
||||
// ===== before =====
|
||||
beforeMaster.modelNo,
|
||||
beforeDataset.id,
|
||||
beforeDataset.uuid,
|
||||
beforeDataset.dataType,
|
||||
beforeDataset.title,
|
||||
beforeDataset.roundNo,
|
||||
beforeDataset.compareYyyy,
|
||||
beforeDataset.targetYyyy,
|
||||
beforeDataset.memo,
|
||||
beforeSelectedCnt))
|
||||
.from(modelMasterEntity)
|
||||
|
||||
// ===== 현재 dataset =====
|
||||
.leftJoin(modelDatasetMappEntity)
|
||||
.on(modelDatasetMappEntity.modelUid.eq(modelMasterEntity.id))
|
||||
.leftJoin(dataset)
|
||||
.on(modelDatasetMappEntity.datasetUid.eq(dataset.id))
|
||||
.leftJoin(datasetObjEntity)
|
||||
.on(dataset.id.eq(datasetObjEntity.datasetUid))
|
||||
|
||||
// ===== before dataset =====
|
||||
.leftJoin(beforeMaster)
|
||||
.on(beforeMaster.id.eq(modelMasterEntity.beforeModelId))
|
||||
.leftJoin(beforeMapp)
|
||||
.on(beforeMapp.modelUid.eq(beforeMaster.id))
|
||||
.leftJoin(beforeDataset)
|
||||
.on(beforeMapp.datasetUid.eq(beforeDataset.id))
|
||||
.leftJoin(beforeObj)
|
||||
.on(beforeDataset.id.eq(beforeObj.datasetUid))
|
||||
.where(modelMasterEntity.id.eq(modelId).and(builder))
|
||||
|
||||
// sum() 때문에 groupBy 필요
|
||||
.groupBy(
|
||||
dataset.id,
|
||||
dataset.uuid,
|
||||
dataset.dataType,
|
||||
dataset.title,
|
||||
dataset.roundNo,
|
||||
dataset.compareYyyy,
|
||||
dataset.targetYyyy,
|
||||
dataset.memo,
|
||||
beforeMaster.modelNo,
|
||||
beforeDataset.id,
|
||||
beforeDataset.uuid,
|
||||
beforeDataset.dataType,
|
||||
beforeDataset.title,
|
||||
beforeDataset.roundNo,
|
||||
beforeDataset.compareYyyy,
|
||||
beforeDataset.targetYyyy,
|
||||
beforeDataset.memo)
|
||||
.orderBy(dataset.createdDttm.desc())
|
||||
.fetch();
|
||||
}
|
||||
|
||||
// TODO 미사용 끝
|
||||
|
||||
@Override
|
||||
public Long getDatasetMaxStage(int compareYyyy, int targetYyyy) {
|
||||
return queryFactory
|
||||
.select(dataset.roundNo.max().coalesce(0L))
|
||||
.from(dataset)
|
||||
.where(dataset.compareYyyy.eq(compareYyyy), dataset.targetYyyy.eq(targetYyyy))
|
||||
.fetchOne();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long insertDatasetMngData(DatasetMngRegDto mngRegDto) {
|
||||
|
||||
UUID uuid = UUID.randomUUID();
|
||||
// 1. 기본 생성자로 빈 엔티티 객체 생성
|
||||
// DatasetEntity datasetEntity = new DatasetEntity();
|
||||
//
|
||||
// // 2. Setter를 이용해 데이터 채우기
|
||||
// datasetEntity.setUid(mngRegDto.getUid());
|
||||
// datasetEntity.setDataType(mngRegDto.getDataType());
|
||||
// datasetEntity.setCompareYyyy(mngRegDto.getCompareYyyy());
|
||||
// datasetEntity.setTargetYyyy(mngRegDto.getTargetYyyy());
|
||||
// datasetEntity.setRoundNo(mngRegDto.getRoundNo());
|
||||
// datasetEntity.setTotalSize(mngRegDto.getTotalSize());
|
||||
// datasetEntity.setTitle(mngRegDto.getTitle());
|
||||
// datasetEntity.setMemo(mngRegDto.getMemo());
|
||||
// datasetEntity.setDatasetPath(mngRegDto.getDatasetPath());
|
||||
// datasetEntity.setCdClsType(mngRegDto.getCdClsType());
|
||||
//
|
||||
// datasetEntity.setUuid(UUID.randomUUID());
|
||||
//
|
||||
//
|
||||
// // 2. JPA 영속성 컨텍스트에 저장 (이 시점에 DB에 들어가거나 ID가 생성됨)
|
||||
// em.persist(datasetEntity);
|
||||
|
||||
queryFactory
|
||||
.insert(dataset)
|
||||
.columns(
|
||||
dataset.uid,
|
||||
dataset.uuid,
|
||||
dataset.dataType,
|
||||
dataset.compareYyyy,
|
||||
dataset.targetYyyy,
|
||||
dataset.roundNo,
|
||||
dataset.totalSize,
|
||||
dataset.title,
|
||||
dataset.memo,
|
||||
dataset.datasetPath,
|
||||
dataset.cdClsType)
|
||||
.values(
|
||||
mngRegDto.getUid(),
|
||||
uuid,
|
||||
mngRegDto.getDataType(),
|
||||
mngRegDto.getCompareYyyy(),
|
||||
mngRegDto.getTargetYyyy(),
|
||||
mngRegDto.getRoundNo(),
|
||||
mngRegDto.getTotalSize(),
|
||||
mngRegDto.getTitle(),
|
||||
mngRegDto.getMemo(),
|
||||
mngRegDto.getDatasetPath(),
|
||||
mngRegDto.getCdClsType())
|
||||
.execute();
|
||||
|
||||
// 3. uid 조회 필요 없이, 생성된 Key를 안전하게 바로 리턴
|
||||
// return datasetEntity.getId();
|
||||
return queryFactory.select(dataset.id).from(dataset).where(dataset.uuid.eq(uuid)).fetchOne();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> findDatasetUid(List<Long> datasetIds) {
|
||||
return queryFactory.select(dataset.uid).from(dataset).where(dataset.id.in(datasetIds)).fetch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long findDatasetByUidExistsCnt(String uid) {
|
||||
return queryFactory
|
||||
.select(dataset.id.count())
|
||||
.from(dataset)
|
||||
.where(dataset.uid.eq(uid), dataset.deleted.isFalse())
|
||||
.fetchOne();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SelectDataSet> getDatasetSelectG4List(DatasetReq req) {
|
||||
BooleanBuilder builder = new BooleanBuilder();
|
||||
|
||||
builder.and(dataset.deleted.isFalse());
|
||||
|
||||
if (StringUtils.isNotBlank(req.getDataType()) && !"CURRENT".equals(req.getDataType())) {
|
||||
builder.and(dataset.dataType.eq(req.getDataType()));
|
||||
}
|
||||
|
||||
if (req.getIds() != null) {
|
||||
builder.and(dataset.id.in(req.getIds()));
|
||||
}
|
||||
|
||||
return queryFactory
|
||||
.select(
|
||||
Projections.constructor(
|
||||
SelectDataSet.class,
|
||||
Expressions.constant(req.getModelNo()),
|
||||
dataset.id,
|
||||
dataset.uuid,
|
||||
dataset.dataType,
|
||||
dataset.title,
|
||||
dataset.roundNo,
|
||||
dataset.compareYyyy,
|
||||
dataset.targetYyyy,
|
||||
dataset.memo,
|
||||
new CaseBuilder()
|
||||
.when(datasetObjEntity.targetClassCd.eq(DetectionClassification.SOLAR.getId()))
|
||||
.then(1L)
|
||||
.otherwise(0L)
|
||||
.sum()))
|
||||
.from(dataset)
|
||||
.leftJoin(datasetObjEntity)
|
||||
.on(dataset.id.eq(datasetObjEntity.datasetUid))
|
||||
.where(builder)
|
||||
.groupBy(
|
||||
dataset.id,
|
||||
dataset.uuid,
|
||||
dataset.dataType,
|
||||
dataset.title,
|
||||
dataset.roundNo,
|
||||
dataset.memo)
|
||||
.orderBy(dataset.createdDttm.desc())
|
||||
.fetch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<DatasetClsObjEntity> searchDatasetClsObjectList(
|
||||
DatasetClsObjDto.ClsSearchReq searchReq) {
|
||||
{
|
||||
Pageable pageable = searchReq.toPageable();
|
||||
BooleanBuilder builder = new BooleanBuilder();
|
||||
|
||||
if (StringUtils.isNotBlank(searchReq.getClassCd())) {
|
||||
builder.and(datasetClsObjEntity.classCd.eq(searchReq.getClassCd()));
|
||||
}
|
||||
if (searchReq.getYyyy() != null) {
|
||||
builder.and(datasetClsObjEntity.yyyy.eq(searchReq.getYyyy()));
|
||||
}
|
||||
if (StringUtils.isNotBlank(searchReq.getMapSheetNum())) {
|
||||
builder.and(datasetClsObjEntity.mapSheetNum.eq(searchReq.getMapSheetNum()));
|
||||
}
|
||||
|
||||
DatasetEntity entity =
|
||||
queryFactory
|
||||
.selectFrom(datasetEntity)
|
||||
.where(datasetEntity.uuid.eq(searchReq.getUuid()))
|
||||
.fetchOne();
|
||||
|
||||
if (Objects.isNull(entity)) {
|
||||
throw new EntityNotFoundException(
|
||||
"DatasetEntity not found for uuid: " + searchReq.getUuid());
|
||||
}
|
||||
|
||||
List<DatasetClsObjEntity> content =
|
||||
queryFactory
|
||||
.selectFrom(datasetClsObjEntity)
|
||||
.where(
|
||||
datasetClsObjEntity
|
||||
.deleted
|
||||
.isFalse()
|
||||
.and(datasetClsObjEntity.datasetUid.eq(entity.getId()))
|
||||
.and(builder)
|
||||
.and(
|
||||
datasetClsObjEntity.type.eq(DatasetClsObjDto.FolderType.TRAIN.getText())))
|
||||
.offset(pageable.getOffset())
|
||||
.limit(pageable.getPageSize())
|
||||
.fetch();
|
||||
|
||||
// Count 쿼리 별도 실행 (null safe handling)
|
||||
long total =
|
||||
Optional.ofNullable(
|
||||
queryFactory
|
||||
.select(datasetClsObjEntity.count())
|
||||
.from(datasetClsObjEntity)
|
||||
.where(
|
||||
datasetClsObjEntity
|
||||
.deleted
|
||||
.isFalse()
|
||||
.and(datasetClsObjEntity.datasetUid.eq(entity.getId()))
|
||||
.and(builder)
|
||||
.and(
|
||||
datasetClsObjEntity.type.eq(
|
||||
DatasetClsObjDto.FolderType.TRAIN.getText())))
|
||||
.fetchOne())
|
||||
.orElse(0L);
|
||||
|
||||
return new PageImpl<>(content, pageable, total);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertDatasetClsObj(DatasetObjDto.DatasetClsObjRegDto objRegDto) {
|
||||
try {
|
||||
em.createNativeQuery(
|
||||
"""
|
||||
insert into tb_dataset_cls_obj
|
||||
(uuid, dataset_uid, type, yyyy, class_cd,
|
||||
map_sheet_num, tif_img_path, file_name)
|
||||
values
|
||||
(?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""")
|
||||
.setParameter(1, UUID.randomUUID())
|
||||
.setParameter(2, objRegDto.getDatasetUid())
|
||||
.setParameter(3, objRegDto.getType())
|
||||
.setParameter(4, objRegDto.getYyyy())
|
||||
.setParameter(5, objRegDto.getClassCd())
|
||||
.setParameter(6, objRegDto.getMapSheetNum())
|
||||
.setParameter(7, objRegDto.getTifImgPath())
|
||||
.setParameter(8, objRegDto.getFileName())
|
||||
.executeUpdate();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,7 +65,7 @@ public class DatasetRepositoryImpl implements DatasetRepositoryCustom {
|
||||
List<DatasetEntity> content =
|
||||
queryFactory
|
||||
.selectFrom(dataset)
|
||||
.where(builder.and(dataset.deleted.isFalse()))
|
||||
.where(builder.and(dataset.deleted.isFalse()).and(dataset.cdClsType.isNull()))
|
||||
.offset(pageable.getOffset())
|
||||
.limit(pageable.getPageSize())
|
||||
.orderBy(dataset.createdDttm.desc())
|
||||
@@ -77,7 +77,7 @@ public class DatasetRepositoryImpl implements DatasetRepositoryCustom {
|
||||
queryFactory
|
||||
.select(dataset.count())
|
||||
.from(dataset)
|
||||
.where(builder.and(dataset.deleted.isFalse()))
|
||||
.where(builder.and(dataset.deleted.isFalse()).and(dataset.cdClsType.isNull()))
|
||||
.fetchOne())
|
||||
.orElse(0L);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user