label-to-review 커밋
This commit is contained in:
@@ -87,7 +87,7 @@ public class CommonCodeDto {
|
||||
private Integer order;
|
||||
private Boolean used;
|
||||
private Boolean deleted;
|
||||
private List<CommonCodeDto.Basic> children;
|
||||
private List<Basic> children;
|
||||
|
||||
@JsonFormatDttm private ZonedDateTime createdDttm;
|
||||
|
||||
@@ -112,7 +112,7 @@ public class CommonCodeDto {
|
||||
Integer order,
|
||||
Boolean used,
|
||||
Boolean deleted,
|
||||
List<CommonCodeDto.Basic> children,
|
||||
List<Basic> children,
|
||||
ZonedDateTime createdDttm,
|
||||
ZonedDateTime updatedDttm,
|
||||
String props1,
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
### TODO
|
||||
## 환경분리
|
||||
- local
|
||||
- dev
|
||||
- prod
|
||||
|
||||
## codestyle 세팅 - google 고쳐주세요. tab 간격 2자리
|
||||
|
||||
## objectmepper 빈으로 세팅
|
||||
## 라이브러리 체크
|
||||
## querydsl 세팅 custom, impl
|
||||
## 공통코드 관리
|
||||
## 나중 레디스랑 캐시
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.kamco.cd.kamcoback.enums;
|
||||
package com.kamco.cd.kamcoback.common.enums;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
@@ -1,7 +1,8 @@
|
||||
package com.kamco.cd.kamcoback.enums;
|
||||
package com.kamco.cd.kamcoback.common.enums;
|
||||
|
||||
import com.kamco.cd.kamcoback.enums.ApiConfigEnum.EnumDto;
|
||||
import com.kamco.cd.kamcoback.inferface.EnumType;
|
||||
import com.kamco.cd.kamcoback.common.enums.ApiConfigEnum.EnumDto;
|
||||
import com.kamco.cd.kamcoback.common.utils.enums.CodeExpose;
|
||||
import com.kamco.cd.kamcoback.common.utils.enums.EnumType;
|
||||
import java.util.Arrays;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
@@ -12,6 +13,7 @@ import lombok.Getter;
|
||||
* <p>This enum represents whether a resource is active, excluded from processing, or inactive. It
|
||||
* is commonly used for filtering, business rules, and status management.
|
||||
*/
|
||||
@CodeExpose
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum CommonUseStatus implements EnumType {
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.kamco.cd.kamcoback.common.enums;
|
||||
|
||||
import com.kamco.cd.kamcoback.common.utils.enums.CodeExpose;
|
||||
import com.kamco.cd.kamcoback.common.utils.enums.EnumType;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@CodeExpose
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum CrsType implements EnumType {
|
||||
EPSG_3857("Web Mercator, 웹지도 미터(EPSG:900913 동일)"),
|
||||
EPSG_4326("WGS84 위경도, GeoJSON/OSM 기본"),
|
||||
EPSG_5186("Korea 2000 중부 TM, 한국 SHP");
|
||||
|
||||
private final String desc;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.kamco.cd.kamcoback.inference.dto;
|
||||
package com.kamco.cd.kamcoback.common.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.kamco.cd.kamcoback.common.enums;
|
||||
|
||||
import com.kamco.cd.kamcoback.common.utils.enums.CodeExpose;
|
||||
import com.kamco.cd.kamcoback.common.utils.enums.EnumType;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@CodeExpose
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum FileUploadStatus implements EnumType {
|
||||
INIT("초기화"),
|
||||
UPLOADING("업로드중"),
|
||||
DONE("업로드완료"),
|
||||
MERGED("병합완료"),
|
||||
MERGE_FAIL("병합 실패");
|
||||
|
||||
private final String desc;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.kamco.cd.kamcoback.common.utils.enums;
|
||||
package com.kamco.cd.kamcoback.common.enums;
|
||||
|
||||
import com.kamco.cd.kamcoback.common.utils.enums.CodeExpose;
|
||||
import com.kamco.cd.kamcoback.common.utils.enums.EnumType;
|
||||
import java.util.Arrays;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.kamco.cd.kamcoback.common.enums;
|
||||
|
||||
import com.kamco.cd.kamcoback.common.utils.enums.CodeExpose;
|
||||
import com.kamco.cd.kamcoback.common.utils.enums.EnumType;
|
||||
import java.util.Optional;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@CodeExpose
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum LayerType implements EnumType {
|
||||
TILE("배경지도"),
|
||||
GEOJSON("객체데이터"),
|
||||
WMTS("타일레이어"),
|
||||
WMS("지적도");
|
||||
|
||||
private final String desc;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return desc;
|
||||
}
|
||||
|
||||
public static Optional<LayerType> from(String type) {
|
||||
try {
|
||||
return Optional.of(LayerType.valueOf(type));
|
||||
} catch (Exception e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
package com.kamco.cd.kamcoback.enums;
|
||||
package com.kamco.cd.kamcoback.common.enums;
|
||||
|
||||
import com.kamco.cd.kamcoback.inferface.EnumType;
|
||||
import com.kamco.cd.kamcoback.common.utils.enums.CodeExpose;
|
||||
import com.kamco.cd.kamcoback.common.utils.enums.EnumType;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@CodeExpose
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum MngStateType implements EnumType {
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.kamco.cd.kamcoback.common.enums;
|
||||
|
||||
import com.kamco.cd.kamcoback.common.utils.enums.CodeExpose;
|
||||
import com.kamco.cd.kamcoback.common.utils.enums.EnumType;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@CodeExpose
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum RoleType implements EnumType {
|
||||
ADMIN("관리자"),
|
||||
LABELER("라벨러"),
|
||||
REVIEWER("검수자");
|
||||
|
||||
private final String desc;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.kamco.cd.kamcoback.common.enums;
|
||||
|
||||
import com.kamco.cd.kamcoback.common.utils.enums.CodeExpose;
|
||||
import com.kamco.cd.kamcoback.common.utils.enums.EnumType;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@CodeExpose
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum StatusType implements EnumType {
|
||||
ACTIVE("사용"),
|
||||
INACTIVE("사용중지"),
|
||||
PENDING("계정등록");
|
||||
|
||||
private final String desc;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.kamco.cd.kamcoback.common.enums;
|
||||
|
||||
import com.kamco.cd.kamcoback.common.utils.enums.CodeExpose;
|
||||
import com.kamco.cd.kamcoback.common.utils.enums.EnumType;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@CodeExpose
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum SyncCheckStateType implements EnumType {
|
||||
NOTYET("미처리"),
|
||||
DONE("완료");
|
||||
|
||||
private final String desc;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.kamco.cd.kamcoback.enums;
|
||||
package com.kamco.cd.kamcoback.common.enums;
|
||||
|
||||
import com.kamco.cd.kamcoback.inferface.CodeExpose;
|
||||
import com.kamco.cd.kamcoback.inferface.CodeHidden;
|
||||
import com.kamco.cd.kamcoback.inferface.EnumType;
|
||||
import com.kamco.cd.kamcoback.common.utils.enums.CodeExpose;
|
||||
import com.kamco.cd.kamcoback.common.utils.enums.CodeHidden;
|
||||
import com.kamco.cd.kamcoback.common.utils.enums.EnumType;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.kamco.cd.kamcoback.common.enums.error;
|
||||
|
||||
import com.kamco.cd.kamcoback.common.utils.ErrorCode;
|
||||
import lombok.Getter;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
@Getter
|
||||
public enum AuthErrorCode implements ErrorCode {
|
||||
LOGIN_ID_NOT_FOUND("LOGIN_ID_NOT_FOUND", HttpStatus.UNAUTHORIZED),
|
||||
|
||||
LOGIN_PASSWORD_MISMATCH("LOGIN_PASSWORD_MISMATCH", HttpStatus.UNAUTHORIZED),
|
||||
|
||||
LOGIN_PASSWORD_EXCEEDED("LOGIN_PASSWORD_EXCEEDED", HttpStatus.UNAUTHORIZED),
|
||||
|
||||
INACTIVE_ID("INACTIVE_ID", HttpStatus.UNAUTHORIZED);
|
||||
|
||||
private final String code;
|
||||
private final HttpStatus status;
|
||||
|
||||
AuthErrorCode(String code, HttpStatus status) {
|
||||
this.code = code;
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.kamco.cd.kamcoback.common.exception;
|
||||
|
||||
import com.kamco.cd.kamcoback.common.utils.ErrorCode;
|
||||
import lombok.Getter;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
@Getter
|
||||
public class CustomApiException extends RuntimeException {
|
||||
|
||||
private final String codeName; // ApiResponseCode enum name과 맞추는 용도 (예: "UNPROCESSABLE_ENTITY")
|
||||
private final HttpStatus status; // 응답으로 내려줄 HttpStatus
|
||||
|
||||
public CustomApiException(String codeName, HttpStatus status, String message) {
|
||||
super(message);
|
||||
this.codeName = codeName;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public CustomApiException(String codeName, HttpStatus status) {
|
||||
super(codeName);
|
||||
this.codeName = codeName;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public CustomApiException(ErrorCode errorCode) {
|
||||
super(errorCode.getCode()); // 또는 errorCode.getMessage()
|
||||
this.codeName = errorCode.getCode();
|
||||
this.status = errorCode.getStatus();
|
||||
}
|
||||
|
||||
public CustomApiException(String codeName, HttpStatus status, Throwable cause) {
|
||||
super(codeName, cause);
|
||||
this.codeName = codeName;
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.kamco.cd.kamcoback.common.exception;
|
||||
|
||||
public class DuplicateFileException extends RuntimeException {
|
||||
public DuplicateFileException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.kamco.cd.kamcoback.common.exception;
|
||||
|
||||
public class ValidationException extends RuntimeException {
|
||||
public ValidationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
package com.kamco.cd.kamcoback.common.geometry;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.locationtech.jts.geom.Geometry;
|
||||
|
||||
/** GeoJSON 파일 생성 유틸리티 */
|
||||
public class GeoJsonFileWriter {
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public GeoJsonFileWriter() {
|
||||
this.objectMapper = new ObjectMapper();
|
||||
}
|
||||
|
||||
public GeoJsonFileWriter(ObjectMapper objectMapper) {
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* GeoJSON 문자열 생성
|
||||
*
|
||||
* @param features Feature 목록
|
||||
* @param name GeoJSON name 속성
|
||||
* @param srid CRS EPSG 코드
|
||||
* @return GeoJSON 문자열
|
||||
*/
|
||||
public String buildGeoJson(List<ImageFeature> features, String name, int srid) {
|
||||
try {
|
||||
ObjectNode root = objectMapper.createObjectNode();
|
||||
root.put("type", "FeatureCollection");
|
||||
root.put("name", name);
|
||||
|
||||
// CRS 정보
|
||||
ObjectNode crs = objectMapper.createObjectNode();
|
||||
crs.put("type", "name");
|
||||
ObjectNode crsProps = objectMapper.createObjectNode();
|
||||
crsProps.put("name", "urn:ogc:def:crs:EPSG::" + srid);
|
||||
crs.set("properties", crsProps);
|
||||
root.set("crs", crs);
|
||||
|
||||
// Features 배열
|
||||
ArrayNode featuresArray = objectMapper.createArrayNode();
|
||||
for (ImageFeature f : features) {
|
||||
featuresArray.add(buildFeature(f));
|
||||
}
|
||||
root.set("features", featuresArray);
|
||||
|
||||
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(root);
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("GeoJSON 생성 실패", e);
|
||||
}
|
||||
}
|
||||
|
||||
/** 단일 Feature 객체 생성 */
|
||||
private ObjectNode buildFeature(ImageFeature f) throws Exception {
|
||||
ObjectNode feature = objectMapper.createObjectNode();
|
||||
feature.put("type", "Feature");
|
||||
|
||||
// Properties
|
||||
ObjectNode properties = objectMapper.createObjectNode();
|
||||
properties.put("scene_id", f.getSceneId());
|
||||
properties.put("abs_path", f.getAbsPath());
|
||||
properties.put("basename", f.getFileName());
|
||||
properties.put("georef_source", "internal");
|
||||
properties.put("crs_source", "transformed");
|
||||
feature.set("properties", properties);
|
||||
|
||||
// Geometry (CRS 제거)
|
||||
ObjectNode geometry = (ObjectNode) objectMapper.readTree(f.getGeomJson());
|
||||
geometry.remove("crs");
|
||||
feature.set("geometry", geometry);
|
||||
|
||||
return feature;
|
||||
}
|
||||
|
||||
/**
|
||||
* 파일로 저장
|
||||
*
|
||||
* @param geojson GeoJSON 문자열
|
||||
* @param filePath 저장 경로
|
||||
*/
|
||||
public void writeToFile(String geojson, String filePath) throws IOException {
|
||||
try (FileWriter writer = new FileWriter(filePath)) {
|
||||
writer.write(geojson);
|
||||
}
|
||||
}
|
||||
|
||||
/** Feature 목록을 바로 파일로 저장 */
|
||||
public void exportToFile(List<ImageFeature> features, String name, int srid, String filePath)
|
||||
throws IOException {
|
||||
String geojson = buildGeoJson(features, name, srid);
|
||||
writeToFile(geojson, filePath);
|
||||
}
|
||||
|
||||
/** Feature 데이터 클래스 */
|
||||
public static class ImageFeature {
|
||||
|
||||
private String sceneId;
|
||||
private String filePath;
|
||||
private String fileName;
|
||||
private String geomJson;
|
||||
|
||||
public ImageFeature() {}
|
||||
|
||||
public ImageFeature(String sceneId, String filePath, String fileName, Geometry geomJson) {
|
||||
this.sceneId = sceneId;
|
||||
this.filePath = filePath;
|
||||
this.fileName = fileName;
|
||||
if (geomJson != null) {
|
||||
|
||||
this.geomJson = GeometryUtils.toGeoJson(geomJson);
|
||||
}
|
||||
}
|
||||
|
||||
public String getSceneId() {
|
||||
return sceneId;
|
||||
}
|
||||
|
||||
public void setSceneId(String sceneId) {
|
||||
this.sceneId = sceneId;
|
||||
}
|
||||
|
||||
public String getFilePath() {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
public void setFilePath(String filePath) {
|
||||
this.filePath = filePath;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public void setFileName(String fileName) {
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
public String getGeomJson() {
|
||||
return geomJson;
|
||||
}
|
||||
|
||||
public void setGeomJson(String geomJson) {
|
||||
this.geomJson = geomJson;
|
||||
}
|
||||
|
||||
public String getAbsPath() {
|
||||
return filePath + "/" + fileName;
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class Scene {
|
||||
|
||||
List<ImageFeature> features;
|
||||
String filePath;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.kamco.cd.kamcoback.common.geometry;
|
||||
|
||||
import org.locationtech.jts.geom.Geometry;
|
||||
import org.locationtech.jts.io.geojson.GeoJsonWriter;
|
||||
|
||||
public class GeometryUtils {
|
||||
|
||||
private static final GeoJsonWriter GEOJSON_WRITER = new GeoJsonWriter(8);
|
||||
|
||||
/** JTS Geometry를 GeoJSON 문자열로 변환 */
|
||||
public static String toGeoJson(Geometry geometry) {
|
||||
if (geometry == null) {
|
||||
return null;
|
||||
}
|
||||
return GEOJSON_WRITER.write(geometry);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.kamco.cd.kamcoback.common.service;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
|
||||
/**
|
||||
* Base Core Service Interface
|
||||
*
|
||||
* <p>CRUD operations를 정의하는 기본 서비스 인터페이스
|
||||
*
|
||||
* @param <T> Entity 타입
|
||||
* @param <ID> Entity의 ID 타입
|
||||
* @param <S> Search Request 타입
|
||||
*/
|
||||
public interface BaseCoreService<T, ID, S> {
|
||||
|
||||
/**
|
||||
* ID로 엔티티를 삭제합니다.
|
||||
*
|
||||
* @param id 삭제할 엔티티의 ID
|
||||
*/
|
||||
void remove(ID id);
|
||||
|
||||
/**
|
||||
* ID로 단건 조회합니다.
|
||||
*
|
||||
* @param id 조회할 엔티티의 ID
|
||||
* @return 조회된 엔티티
|
||||
*/
|
||||
T getOneById(ID id);
|
||||
|
||||
/**
|
||||
* 검색 조건과 페이징으로 조회합니다.
|
||||
*
|
||||
* @param searchReq 검색 조건
|
||||
* @return 페이징 처리된 검색 결과
|
||||
*/
|
||||
Page<T> search(S searchReq);
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package com.kamco.cd.kamcoback.common.service;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Log4j2
|
||||
@Component
|
||||
public class ExternalJarRunner {
|
||||
@Value("${spring.profiles.active}")
|
||||
private String profile;
|
||||
|
||||
private static final long TIMEOUT_MINUTES = TimeUnit.DAYS.toMinutes(3);
|
||||
|
||||
/**
|
||||
* shp 파일 생성
|
||||
*
|
||||
* @param jarPath jar 경로
|
||||
* @param batchIds 배치 아이디
|
||||
* @param inferenceId uid
|
||||
* @param mapIds 추론 실행한 도엽 ids
|
||||
* @param mode
|
||||
* <p>MERGED - batch-ids 에 해당하는 **모든 데이터를 하나의 Shapefile로 병합 생성,
|
||||
* <p>MAP_IDS - 명시적으로 전달한 map-ids만 대상으로 Shapefile 생성,
|
||||
* <p>RESOLVE - batch-ids 기준으로 **JAR 내부에서 map_ids를 조회**한 뒤 Shapefile 생성
|
||||
*/
|
||||
public void run(String jarPath, String batchIds, String inferenceId, String mapIds, String mode) {
|
||||
List<String> args = new ArrayList<>();
|
||||
|
||||
addArg(args, "converter.inference-id", inferenceId);
|
||||
addArg(args, "converter.batch-ids", batchIds);
|
||||
|
||||
if (mapIds != null && !mapIds.isEmpty()) {
|
||||
addArg(args, "converter.map-ids", mapIds);
|
||||
}
|
||||
|
||||
if (mode != null && !mode.isEmpty()) {
|
||||
addArg(args, "converter.mode", mode);
|
||||
}
|
||||
addArg(args, "spring.profiles.active", profile);
|
||||
execJar(jarPath, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* geoserver 등록
|
||||
*
|
||||
* @param jarPath jar 파일경로
|
||||
* @param register shp 경로
|
||||
* @param layer geoserver에 등록될 레이어 이름
|
||||
*/
|
||||
public void run(String jarPath, String register, String layer) {
|
||||
List<String> args = new ArrayList<>();
|
||||
|
||||
addArg(args, "upload-shp", register);
|
||||
// addArg(args, "layer", layer);
|
||||
|
||||
addArg(args, "spring.profiles.active", profile);
|
||||
execJar(jarPath, args);
|
||||
}
|
||||
|
||||
private void execJar(String jarPath, List<String> args) {
|
||||
StringBuilder out = new StringBuilder();
|
||||
|
||||
try {
|
||||
List<String> cmd = new ArrayList<>();
|
||||
cmd.add("java");
|
||||
cmd.add("-jar");
|
||||
cmd.add(jarPath);
|
||||
cmd.addAll(args);
|
||||
|
||||
ProcessBuilder pb = new ProcessBuilder(cmd);
|
||||
pb.redirectErrorStream(true);
|
||||
|
||||
Process p = pb.start();
|
||||
|
||||
try (BufferedReader br =
|
||||
new BufferedReader(new InputStreamReader(p.getInputStream(), StandardCharsets.UTF_8))) {
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
out.append(line).append('\n');
|
||||
log.info("[jar] {}", line);
|
||||
}
|
||||
}
|
||||
|
||||
boolean finished = p.waitFor(TIMEOUT_MINUTES, TimeUnit.MINUTES);
|
||||
if (!finished) {
|
||||
p.destroyForcibly();
|
||||
throw new RuntimeException("jar timeout\n" + out);
|
||||
}
|
||||
|
||||
int exit = p.exitValue();
|
||||
if (exit != 0) {
|
||||
throw new RuntimeException("jar failed. exitCode=" + exit + "\n" + out);
|
||||
}
|
||||
|
||||
log.info("jar finished successfully");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("jar execution error. output=\n{}", out, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void addArg(List<String> args, String key, String value) {
|
||||
value = normalizeCliValue(value);
|
||||
|
||||
if (value != null && !value.isBlank()) {
|
||||
log.info("addArg key={}, normalizedValue=[{}], length={}", key, value, value.length());
|
||||
args.add("--" + key + "=" + value);
|
||||
}
|
||||
}
|
||||
|
||||
private String normalizeCliValue(String v) {
|
||||
if (v == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
v = v.trim();
|
||||
|
||||
// 양끝 따옴표 제거
|
||||
if (v.length() >= 2 && v.startsWith("\"") && v.endsWith("\"")) {
|
||||
v = v.substring(1, v.length() - 1);
|
||||
}
|
||||
|
||||
// 남아있는 따옴표 제거
|
||||
v = v.replace("\"", "");
|
||||
|
||||
return v;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.kamco.cd.kamcoback.common.utils;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
public class DateRange {
|
||||
|
||||
private static final ZoneId KST = ZoneId.of("Asia/Seoul");
|
||||
|
||||
private DateRange() {}
|
||||
|
||||
public static ZonedDateTime start(LocalDate date) {
|
||||
return date == null ? null : date.atStartOfDay(KST);
|
||||
}
|
||||
|
||||
public static ZonedDateTime end(LocalDate date) {
|
||||
return date == null ? null : date.plusDays(1).atStartOfDay(KST);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.kamco.cd.kamcoback.common.utils;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
public interface ErrorCode {
|
||||
|
||||
String getCode();
|
||||
|
||||
HttpStatus getStatus();
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.kamco.cd.kamcoback.common.utils;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
public final class HeaderUtil {
|
||||
|
||||
private HeaderUtil() {}
|
||||
|
||||
/** 특정 Header 값 조회 */
|
||||
public static String get(HttpServletRequest request, String headerName) {
|
||||
if (request == null || headerName == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String value = request.getHeader(headerName);
|
||||
return (value != null && !value.isBlank()) ? value : null;
|
||||
}
|
||||
|
||||
/** 필수 Header 조회 (없으면 null) */
|
||||
public static String getRequired(HttpServletRequest request, String headerName) {
|
||||
return get(request, headerName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.kamco.cd.kamcoback.common.utils;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class NameValidator {
|
||||
|
||||
private static final String HANGUL_REGEX = ".*\\p{IsHangul}.*";
|
||||
private static final Pattern HANGUL_PATTERN = Pattern.compile(HANGUL_REGEX);
|
||||
|
||||
private static final String WHITESPACE_REGEX = ".*\\s.*";
|
||||
private static final Pattern WHITESPACE_PATTERN = Pattern.compile(WHITESPACE_REGEX);
|
||||
|
||||
public static boolean containsKorean(String str) {
|
||||
if (str == null || str.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
Matcher matcher = HANGUL_PATTERN.matcher(str);
|
||||
return matcher.matches();
|
||||
}
|
||||
|
||||
public static boolean containsWhitespaceRegex(String str) {
|
||||
if (str == null || str.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Matcher matcher = WHITESPACE_PATTERN.matcher(str);
|
||||
// find()를 사용하여 문자열 내에서 패턴이 일치하는 부분이 있는지 확인
|
||||
return matcher.find();
|
||||
}
|
||||
|
||||
public static boolean isNullOrEmpty(String str) {
|
||||
if (str == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (str.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.kamco.cd.kamcoback.common.utils;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URLEncoder;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
|
||||
public class NetUtils {
|
||||
|
||||
public String getLocalIP() {
|
||||
|
||||
String ip;
|
||||
{
|
||||
try {
|
||||
InetAddress local = InetAddress.getLocalHost();
|
||||
ip = local.getHostAddress();
|
||||
} catch (UnknownHostException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
return ip;
|
||||
}
|
||||
|
||||
public String dtoToQueryString(Object dto, String queryString) {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
Map<String, Object> map = objectMapper.convertValue(dto, Map.class);
|
||||
|
||||
String qStr =
|
||||
map.entrySet().stream()
|
||||
.filter(entry -> entry.getValue() != null) // null 제외
|
||||
.map(
|
||||
entry ->
|
||||
String.format(
|
||||
"%s=%s",
|
||||
entry.getKey(),
|
||||
URLEncoder.encode(entry.getValue().toString(), StandardCharsets.UTF_8)))
|
||||
.collect(Collectors.joining("&"));
|
||||
|
||||
if (queryString == null || queryString.isEmpty()) {
|
||||
queryString = "?" + qStr;
|
||||
} else {
|
||||
queryString = queryString + "&" + qStr;
|
||||
}
|
||||
|
||||
// 2. Map을 쿼리 스트링 문자열로 변환
|
||||
return queryString;
|
||||
}
|
||||
|
||||
public HttpHeaders jsonHeaders() {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set(HttpHeaders.ACCEPT, "application/json;charset=UTF-8");
|
||||
headers.set(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8");
|
||||
return headers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.kamco.cd.kamcoback.common.utils;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
import org.mindrot.jbcrypt.BCrypt;
|
||||
|
||||
public class StringUtils {
|
||||
|
||||
private static final int BCRYPT_COST = 10;
|
||||
|
||||
/**
|
||||
* 영문, 숫자, 특수문자를 모두 포함하여 8~20자 이내의 비밀번호
|
||||
*
|
||||
* @param password 벨리데이션 필요한 패스워드
|
||||
* @return
|
||||
*/
|
||||
public static boolean isValidPassword(String password) {
|
||||
String passwordPattern =
|
||||
"^(?=.*[A-Za-z])(?=.*\\d)(?=.*[!@#$%^&*()_+\\-\\[\\]{};':\"\\\\|,.<>/?=]).{8,20}$";
|
||||
return Pattern.matches(passwordPattern, password);
|
||||
}
|
||||
|
||||
/**
|
||||
* 패스워드 암호화
|
||||
*
|
||||
* @param password 암호화 필요한 패스워드
|
||||
* @return
|
||||
*/
|
||||
public static String hashPassword(String password) {
|
||||
if (password == null) {
|
||||
throw new IllegalArgumentException("password must not be null");
|
||||
}
|
||||
return BCrypt.hashpw(password.trim(), BCrypt.gensalt(BCRYPT_COST));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.kamco.cd.kamcoback.common.utils.geometry;
|
||||
|
||||
import com.fasterxml.jackson.core.JacksonException;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
|
||||
import java.io.IOException;
|
||||
import org.locationtech.jts.geom.Geometry;
|
||||
import org.locationtech.jts.io.geojson.GeoJsonReader;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
public class GeometryDeserializer<T extends Geometry> extends StdDeserializer<T> {
|
||||
|
||||
public GeometryDeserializer() {
|
||||
super(Geometry.class);
|
||||
}
|
||||
|
||||
public GeometryDeserializer(Class<T> targetType) {
|
||||
super(targetType);
|
||||
}
|
||||
|
||||
// TODO: test code
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public T deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
|
||||
throws IOException, JacksonException {
|
||||
String json = jsonParser.readValueAsTree().toString();
|
||||
|
||||
if (!StringUtils.hasText(json)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
GeoJsonReader reader = new GeoJsonReader();
|
||||
Geometry geometry = reader.read(json);
|
||||
geometry.setSRID(5186);
|
||||
return (T) geometry;
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("Failed to deserialize GeoJSON into Geometry", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.kamco.cd.kamcoback.common.utils.geometry;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import org.locationtech.jts.geom.Geometry;
|
||||
import org.locationtech.jts.io.geojson.GeoJsonWriter;
|
||||
|
||||
public class GeometrySerializer<T extends Geometry> extends StdSerializer<T> {
|
||||
|
||||
// TODO: test code
|
||||
public GeometrySerializer(Class<T> targetType) {
|
||||
super(targetType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(
|
||||
T geometry, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
|
||||
throws IOException {
|
||||
if (Objects.nonNull(geometry)) {
|
||||
// default: 8자리 강제로 반올림시킴. 16자리로 늘려줌
|
||||
GeoJsonWriter writer = new GeoJsonWriter(16);
|
||||
String json = writer.write(geometry);
|
||||
jsonGenerator.writeRawValue(json);
|
||||
} else {
|
||||
jsonGenerator.writeNull();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.kamco.cd.kamcoback.common.utils.zip;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class CsvFileProcessor implements ZipEntryProcessor {
|
||||
|
||||
@Override
|
||||
public boolean supports(String fileName) {
|
||||
return fileName.toLowerCase().endsWith(".csv");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(String fileName, InputStream is) throws IOException {
|
||||
try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) {
|
||||
br.lines()
|
||||
.forEach(
|
||||
line -> {
|
||||
String[] cols = line.split(",");
|
||||
// CSV 처리
|
||||
for (String col : cols) {
|
||||
log.info(col); // TODO : 추후에 csv 파일 읽어서 작업 필요할 때 정의하기
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.kamco.cd.kamcoback.common.utils.zip;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonFactory;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class JsonStreamingFileProcessor implements ZipEntryProcessor {
|
||||
|
||||
private final JsonFactory jsonFactory;
|
||||
|
||||
public JsonStreamingFileProcessor(ObjectMapper objectMapper) {
|
||||
// ZipInputStream 보호용 설정
|
||||
objectMapper.configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, false);
|
||||
this.jsonFactory = objectMapper.getFactory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(String fileName) {
|
||||
return fileName.toLowerCase().endsWith(".json");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(String fileName, InputStream is) throws IOException {
|
||||
|
||||
log.info("JSON process start: {}", fileName);
|
||||
|
||||
JsonParser parser = jsonFactory.createParser(is);
|
||||
|
||||
// JSON 구조에 상관없이 token 단위로 순회
|
||||
while (parser.nextToken() != null) {
|
||||
handleToken(parser);
|
||||
}
|
||||
|
||||
log.info("JSON process end: {}", fileName);
|
||||
}
|
||||
|
||||
private void handleToken(JsonParser parser) throws IOException {
|
||||
JsonToken token = parser.currentToken();
|
||||
|
||||
if (token == JsonToken.FIELD_NAME) {
|
||||
String fieldName = parser.getCurrentName();
|
||||
// TODO: json 파일 읽어야 할 내용 정의되면 항목 확정하기
|
||||
switch (fieldName) {
|
||||
case "type" -> {
|
||||
parser.nextToken();
|
||||
String type = parser.getValueAsString();
|
||||
log.info("type: {}", type);
|
||||
}
|
||||
case "name" -> {
|
||||
parser.nextToken();
|
||||
String name = parser.getValueAsString();
|
||||
log.info("Name: {}", name);
|
||||
}
|
||||
case "features" -> {
|
||||
parser.nextToken();
|
||||
String features = parser.readValueAsTree().toString();
|
||||
log.info("features: {}", features);
|
||||
}
|
||||
default -> {
|
||||
parser.nextToken();
|
||||
parser.skipChildren();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.kamco.cd.kamcoback.common.utils.zip;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class TextFileProcessor implements ZipEntryProcessor {
|
||||
|
||||
@Override
|
||||
public boolean supports(String fileName) {
|
||||
return fileName.toLowerCase().endsWith(".txt");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(String fileName, InputStream is) throws IOException {
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(is));
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
log.info(line); // TODO : 추후 txt 파일 읽어서 작업할 때 정의하기
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.kamco.cd.kamcoback.common.utils.zip;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public interface ZipEntryProcessor {
|
||||
|
||||
boolean supports(String fileName);
|
||||
|
||||
void process(String fileName, InputStream is) throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.kamco.cd.kamcoback.common.utils.zip;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ZipUtils {
|
||||
|
||||
private final List<ZipEntryProcessor> processors;
|
||||
|
||||
public ZipUtils(List<ZipEntryProcessor> processors) {
|
||||
this.processors = processors;
|
||||
}
|
||||
|
||||
public void processZip(InputStream zipStream) throws IOException {
|
||||
try (ZipInputStream zis = new ZipInputStream(zipStream)) {
|
||||
|
||||
ZipEntry entry;
|
||||
while ((entry = zis.getNextEntry()) != null) {
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String fileName = entry.getName();
|
||||
processors.stream()
|
||||
.filter(p -> p.supports(fileName))
|
||||
.findFirst()
|
||||
.ifPresent(
|
||||
processor -> {
|
||||
try {
|
||||
processor.process(fileName, zis);
|
||||
} catch (IOException ioe) {
|
||||
throw new UncheckedIOException(ioe);
|
||||
}
|
||||
});
|
||||
|
||||
zis.closeEntry();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.kamco.cd.kamcoback.config;
|
||||
|
||||
import io.swagger.v3.oas.models.Components;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||
import io.swagger.v3.oas.models.servers.Server;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class OpenApiConfig {
|
||||
|
||||
@Value("${server.port}")
|
||||
private String serverPort;
|
||||
|
||||
@Value("${spring.profiles.active:local}")
|
||||
private String profile;
|
||||
|
||||
@Value("${swagger.dev-url:https://kamco.dev-api.gs.dabeeo.com}")
|
||||
private String devUrl;
|
||||
|
||||
@Value("${swagger.prod-url:https://api.kamco.com}")
|
||||
private String prodUrl;
|
||||
|
||||
@Bean
|
||||
public OpenAPI kamcoOpenAPI() {
|
||||
// 1) SecurityScheme 정의 (Bearer JWT)
|
||||
SecurityScheme bearerAuth =
|
||||
new SecurityScheme()
|
||||
.type(SecurityScheme.Type.HTTP)
|
||||
.scheme("bearer")
|
||||
.bearerFormat("JWT")
|
||||
.in(SecurityScheme.In.HEADER)
|
||||
.name("Authorization");
|
||||
|
||||
// 2) SecurityRequirement (기본으로 BearerAuth 사용)
|
||||
SecurityRequirement securityRequirement = new SecurityRequirement().addList("BearerAuth");
|
||||
|
||||
// 3) Components 에 SecurityScheme 등록
|
||||
Components components = new Components().addSecuritySchemes("BearerAuth", bearerAuth);
|
||||
|
||||
// profile 별 server url 분기
|
||||
List<Server> servers = new ArrayList<>();
|
||||
if ("dev".equals(profile)) {
|
||||
servers.add(new Server().url(devUrl).description("개발 서버"));
|
||||
servers.add(new Server().url("http://localhost:" + serverPort).description("로컬 서버"));
|
||||
// servers.add(new Server().url(prodUrl).description("운영 서버"));
|
||||
} else if ("prod".equals(profile)) {
|
||||
// servers.add(new Server().url(prodUrl).description("운영 서버"));
|
||||
servers.add(new Server().url("http://localhost:" + serverPort).description("로컬 서버"));
|
||||
servers.add(new Server().url(devUrl).description("개발 서버"));
|
||||
} else {
|
||||
servers.add(new Server().url("http://localhost:" + serverPort).description("로컬 서버"));
|
||||
servers.add(new Server().url(devUrl).description("개발 서버"));
|
||||
// servers.add(new Server().url(prodUrl).description("운영 서버"));
|
||||
}
|
||||
|
||||
return new OpenAPI()
|
||||
.info(
|
||||
new Info()
|
||||
.title("KAMCO Change Detection API")
|
||||
.description(
|
||||
"KAMCO 변화 탐지 시스템 API 문서\n\n"
|
||||
+ "이 API는 지리공간 데이터를 활용한 변화 탐지 시스템을 제공합니다.\n"
|
||||
+ "GeoJSON 형식의 공간 데이터를 처리하며, PostgreSQL/PostGIS 기반으로 동작합니다.")
|
||||
.version("v1.0.0"))
|
||||
.servers(servers)
|
||||
// 만들어둔 components를 넣어야 함
|
||||
.components(components)
|
||||
.addSecurityItem(securityRequirement);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package com.kamco.cd.kamcoback.config;
|
||||
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import javax.sql.DataSource;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class StartupLogger {
|
||||
|
||||
private final Environment environment;
|
||||
private final DataSource dataSource;
|
||||
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void logStartupInfo() {
|
||||
String[] activeProfiles = environment.getActiveProfiles();
|
||||
String profileInfo = activeProfiles.length > 0 ? String.join(", ", activeProfiles) : "default";
|
||||
|
||||
// Database connection information
|
||||
String dbUrl = environment.getProperty("spring.datasource.url");
|
||||
String dbUsername = environment.getProperty("spring.datasource.username");
|
||||
String dbDriver = environment.getProperty("spring.datasource.driver-class-name");
|
||||
|
||||
// HikariCP pool settings
|
||||
String poolInfo = "";
|
||||
if (dataSource instanceof HikariDataSource hikariDs) {
|
||||
poolInfo =
|
||||
String.format(
|
||||
"""
|
||||
│ Pool Size : min=%d, max=%d
|
||||
│ Connection Timeout: %dms
|
||||
│ Idle Timeout : %dms
|
||||
│ Max Lifetime : %dms""",
|
||||
hikariDs.getMinimumIdle(),
|
||||
hikariDs.getMaximumPoolSize(),
|
||||
hikariDs.getConnectionTimeout(),
|
||||
hikariDs.getIdleTimeout(),
|
||||
hikariDs.getMaxLifetime());
|
||||
}
|
||||
|
||||
// JPA/Hibernate settings
|
||||
String showSql = environment.getProperty("spring.jpa.show-sql", "false");
|
||||
String ddlAuto = environment.getProperty("spring.jpa.hibernate.ddl-auto", "none");
|
||||
String batchSize =
|
||||
environment.getProperty("spring.jpa.properties.hibernate.jdbc.batch_size", "N/A");
|
||||
String batchFetchSize =
|
||||
environment.getProperty("spring.jpa.properties.hibernate.default_batch_fetch_size", "N/A");
|
||||
|
||||
String startupMessage =
|
||||
String.format(
|
||||
"""
|
||||
|
||||
╔════════════════════════════════════════════════════════════════════════════════╗
|
||||
║ 🚀 APPLICATION STARTUP INFORMATION ║
|
||||
╠════════════════════════════════════════════════════════════════════════════════╣
|
||||
║ PROFILE CONFIGURATION ║
|
||||
╠────────────────────────────────────────────────────────────────────────────────╣
|
||||
│ Active Profile(s): %s
|
||||
╠════════════════════════════════════════════════════════════════════════════════╣
|
||||
║ DATABASE CONFIGURATION ║
|
||||
╠────────────────────────────────────────────────────────────────────────────────╣
|
||||
│ Database URL : %s
|
||||
│ Username : %s
|
||||
│ Driver : %s
|
||||
╠════════════════════════════════════════════════════════════════════════════════╣
|
||||
║ HIKARICP CONNECTION POOL ║
|
||||
╠────────────────────────────────────────────────────────────────────────────────╣
|
||||
%s
|
||||
╠════════════════════════════════════════════════════════════════════════════════╣
|
||||
║ JPA/HIBERNATE CONFIGURATION ║
|
||||
╠────────────────────────────────────────────────────────────────────────────────╣
|
||||
│ Show SQL : %s
|
||||
│ DDL Auto : %s
|
||||
│ JDBC Batch Size : %s
|
||||
│ Fetch Batch Size : %s
|
||||
╚════════════════════════════════════════════════════════════════════════════════╝
|
||||
""",
|
||||
profileInfo,
|
||||
dbUrl != null ? dbUrl : "N/A",
|
||||
dbUsername != null ? dbUsername : "N/A",
|
||||
dbDriver != null ? dbDriver : "PostgreSQL JDBC Driver (auto-detected)",
|
||||
poolInfo,
|
||||
showSql,
|
||||
ddlAuto,
|
||||
batchSize,
|
||||
batchFetchSize);
|
||||
|
||||
log.info(startupMessage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.kamco.cd.kamcoback.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.kamco.cd.kamcoback.common.utils.geometry.GeometryDeserializer;
|
||||
import com.kamco.cd.kamcoback.common.utils.geometry.GeometrySerializer;
|
||||
import org.locationtech.jts.geom.Geometry;
|
||||
import org.locationtech.jts.geom.Point;
|
||||
import org.locationtech.jts.geom.Polygon;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Bean
|
||||
public ObjectMapper objectMapper() {
|
||||
SimpleModule module = new SimpleModule();
|
||||
module.addSerializer(Geometry.class, new GeometrySerializer<>(Geometry.class));
|
||||
module.addDeserializer(Geometry.class, new GeometryDeserializer<>(Geometry.class));
|
||||
|
||||
module.addSerializer(Polygon.class, new GeometrySerializer<>(Polygon.class));
|
||||
module.addDeserializer(Polygon.class, new GeometryDeserializer<>(Polygon.class));
|
||||
|
||||
module.addSerializer(Point.class, new GeometrySerializer<>(Point.class));
|
||||
module.addDeserializer(Point.class, new GeometryDeserializer<>(Point.class));
|
||||
|
||||
return Jackson2ObjectMapperBuilder.json().modulesToInstall(module).build();
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.kamco.cd.kamcoback.dto;
|
||||
package com.kamco.cd.kamcoback.config.api;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.kamco.cd.kamcoback.inferface.EnumType;
|
||||
import com.kamco.cd.kamcoback.common.utils.enums.EnumType;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
@@ -0,0 +1,120 @@
|
||||
package com.kamco.cd.kamcoback.config.resttemplate;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.HttpStatusCodeException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
@Log4j2
|
||||
public class ExternalHttpClient {
|
||||
|
||||
private final RestTemplate restTemplate;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public <T> ExternalCallResult<T> call(
|
||||
String url, HttpMethod method, Object body, HttpHeaders headers, Class<T> responseType) {
|
||||
|
||||
// responseType 기반으로 Accept 동적 세팅
|
||||
HttpHeaders resolvedHeaders = resolveHeaders(headers, responseType);
|
||||
logRequestBody(body);
|
||||
|
||||
HttpEntity<Object> entity = new HttpEntity<>(body, resolvedHeaders);
|
||||
|
||||
try {
|
||||
// String: raw bytes -> UTF-8 string
|
||||
if (responseType == String.class) {
|
||||
ResponseEntity<byte[]> res = restTemplate.exchange(url, method, entity, byte[].class);
|
||||
String raw =
|
||||
(res.getBody() == null) ? null : new String(res.getBody(), StandardCharsets.UTF_8);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
T casted = (T) raw;
|
||||
return new ExternalCallResult<>(res.getStatusCodeValue(), true, casted, null);
|
||||
}
|
||||
|
||||
// byte[]: raw bytes로 받고, JSON이면 에러로 처리
|
||||
if (responseType == byte[].class) {
|
||||
ResponseEntity<byte[]> res = restTemplate.exchange(url, method, entity, byte[].class);
|
||||
|
||||
MediaType ct = res.getHeaders().getContentType();
|
||||
byte[] bytes = res.getBody();
|
||||
|
||||
if (isJsonLike(ct)) {
|
||||
String err = (bytes == null) ? null : new String(bytes, StandardCharsets.UTF_8);
|
||||
return new ExternalCallResult<>(res.getStatusCodeValue(), false, null, err);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
T casted = (T) bytes;
|
||||
return new ExternalCallResult<>(res.getStatusCodeValue(), true, casted, null);
|
||||
}
|
||||
|
||||
// DTO 등: 일반 역직렬화
|
||||
ResponseEntity<T> res = restTemplate.exchange(url, method, entity, responseType);
|
||||
return new ExternalCallResult<>(res.getStatusCodeValue(), true, res.getBody(), null);
|
||||
|
||||
} catch (HttpStatusCodeException e) {
|
||||
return new ExternalCallResult<>(
|
||||
e.getStatusCode().value(), false, null, e.getResponseBodyAsString());
|
||||
}
|
||||
}
|
||||
|
||||
// 기존 resolveJsonHeaders를 "동적"으로 교체
|
||||
private HttpHeaders resolveHeaders(HttpHeaders headers, Class<?> responseType) {
|
||||
// 원본 headers를 그대로 쓰면 외부에서 재사용할 때 사이드이펙트 날 수 있어서 복사 권장
|
||||
HttpHeaders h = (headers == null) ? new HttpHeaders() : new HttpHeaders(headers);
|
||||
|
||||
// 요청 바디 기본은 JSON이라고 가정 (필요하면 호출부에서 덮어쓰기)
|
||||
if (h.getContentType() == null) {
|
||||
h.setContentType(MediaType.APPLICATION_JSON);
|
||||
}
|
||||
|
||||
// 호출부에서 Accept를 명시했으면 존중
|
||||
if (h.getAccept() != null && !h.getAccept().isEmpty()) {
|
||||
return h;
|
||||
}
|
||||
|
||||
// responseType 기반 Accept 자동 지정
|
||||
if (responseType == byte[].class) {
|
||||
h.setAccept(
|
||||
List.of(
|
||||
MediaType.APPLICATION_OCTET_STREAM,
|
||||
MediaType.valueOf("application/zip"),
|
||||
MediaType.APPLICATION_JSON // 실패(JSON 에러 바디) 대비
|
||||
));
|
||||
} else {
|
||||
h.setAccept(List.of(MediaType.APPLICATION_JSON));
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
private boolean isJsonLike(MediaType ct) {
|
||||
if (ct == null) return false;
|
||||
return ct.includes(MediaType.APPLICATION_JSON)
|
||||
|| "application/problem+json".equalsIgnoreCase(ct.toString());
|
||||
}
|
||||
|
||||
private void logRequestBody(Object body) {
|
||||
try {
|
||||
if (body != null) {
|
||||
log.info("[HTTP-REQ-BODY-JSON] {}", objectMapper.writeValueAsString(body));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("[HTTP-REQ-BODY-JSON] serialize failed: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public record ExternalCallResult<T>(int statusCode, boolean success, T body, String errBody) {}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.kamco.cd.kamcoback.config.resttemplate;
|
||||
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.client.BufferingClientHttpRequestFactory;
|
||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@Log4j2
|
||||
@Configuration
|
||||
public class RestTemplateConfig {
|
||||
|
||||
@Bean
|
||||
public RestTemplate restTemplate(RestTemplateBuilder builder) {
|
||||
SimpleClientHttpRequestFactory baseFactory = new SimpleClientHttpRequestFactory();
|
||||
baseFactory.setConnectTimeout(2000);
|
||||
baseFactory.setReadTimeout(3000);
|
||||
|
||||
RestTemplate rt =
|
||||
builder
|
||||
.requestFactory(() -> new BufferingClientHttpRequestFactory(baseFactory))
|
||||
.additionalInterceptors(new RetryInterceptor())
|
||||
.build();
|
||||
|
||||
// byte[] 응답은 무조건 raw로 읽게 강제 (Jackson이 끼어들 여지 제거)
|
||||
rt.getMessageConverters()
|
||||
.add(0, new org.springframework.http.converter.ByteArrayHttpMessageConverter());
|
||||
|
||||
return rt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.kamco.cd.kamcoback.config.resttemplate;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestExecution;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
|
||||
@Log4j2
|
||||
public class RetryInterceptor implements ClientHttpRequestInterceptor {
|
||||
|
||||
private static final int MAX_RETRY = 3;
|
||||
private static final long WAIT_MILLIS = 3000;
|
||||
|
||||
@Override
|
||||
public ClientHttpResponse intercept(
|
||||
HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
|
||||
|
||||
IOException lastException = null;
|
||||
|
||||
for (int attempt = 1; attempt <= MAX_RETRY; attempt++) {
|
||||
try {
|
||||
log.info("[WIRE-REQ] {} {}", request.getMethod(), request.getURI());
|
||||
log.info("[WIRE-REQ-HEADERS] {}", request.getHeaders());
|
||||
log.info("[WIRE-REQ-BODY] {}", new String(body, StandardCharsets.UTF_8));
|
||||
|
||||
ClientHttpResponse response = execution.execute(request, body);
|
||||
|
||||
log.info("[WIRE-RES-STATUS] {}", response.getStatusCode());
|
||||
return response;
|
||||
|
||||
} catch (IOException e) {
|
||||
lastException = e;
|
||||
log.error("[WIRE-IO-ERR] attempt={} msg={}", attempt, e.getMessage(), e);
|
||||
}
|
||||
|
||||
if (attempt < MAX_RETRY) {
|
||||
sleep();
|
||||
}
|
||||
}
|
||||
|
||||
throw lastException;
|
||||
}
|
||||
|
||||
private void sleep() throws IOException {
|
||||
try {
|
||||
TimeUnit.MILLISECONDS.sleep(WAIT_MILLIS);
|
||||
} catch (InterruptedException ie) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IOException("Retry interrupted", ie);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.kamco.cd.kamcoback.config.swagger;
|
||||
|
||||
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityScheme;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@SecurityScheme(
|
||||
name = "BearerAuth",
|
||||
type = SecuritySchemeType.HTTP,
|
||||
scheme = "bearer",
|
||||
bearerFormat = "JWT")
|
||||
public class SwaggerConfig {}
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.kamco.cd.kamcoback.config.swagger;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import org.springdoc.core.properties.SwaggerUiConfigProperties;
|
||||
import org.springdoc.core.properties.SwaggerUiOAuthProperties;
|
||||
import org.springdoc.core.providers.ObjectMapperProvider;
|
||||
import org.springdoc.webmvc.ui.SwaggerIndexPageTransformer;
|
||||
import org.springdoc.webmvc.ui.SwaggerIndexTransformer;
|
||||
import org.springdoc.webmvc.ui.SwaggerWelcomeCommon;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.web.servlet.resource.ResourceTransformerChain;
|
||||
import org.springframework.web.servlet.resource.TransformedResource;
|
||||
|
||||
@Profile({"local", "dev"})
|
||||
@Configuration
|
||||
public class SwaggerUiAutoAuthConfig {
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
public SwaggerIndexTransformer swaggerIndexTransformer(
|
||||
SwaggerUiConfigProperties swaggerUiConfigProperties,
|
||||
SwaggerUiOAuthProperties swaggerUiOAuthProperties,
|
||||
SwaggerWelcomeCommon swaggerWelcomeCommon,
|
||||
ObjectMapperProvider objectMapperProvider) {
|
||||
|
||||
SwaggerIndexPageTransformer delegate =
|
||||
new SwaggerIndexPageTransformer(
|
||||
swaggerUiConfigProperties,
|
||||
swaggerUiOAuthProperties,
|
||||
swaggerWelcomeCommon,
|
||||
objectMapperProvider);
|
||||
|
||||
return new SwaggerIndexTransformer() {
|
||||
private static final String TOKEN_KEY = "SWAGGER_ACCESS_TOKEN";
|
||||
|
||||
@Override
|
||||
public Resource transform(
|
||||
HttpServletRequest request, Resource resource, ResourceTransformerChain chain) {
|
||||
try {
|
||||
// 1) springdoc 기본 변환 먼저 적용
|
||||
Resource transformed = delegate.transform(request, resource, chain);
|
||||
|
||||
String html =
|
||||
new String(transformed.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
|
||||
|
||||
String loginPathContains = "/api/auth/signin";
|
||||
|
||||
String inject =
|
||||
"""
|
||||
tagsSorter: (a, b) => {
|
||||
const TOP = '인증(Auth)';
|
||||
if (a === TOP && b !== TOP) return -1;
|
||||
if (b === TOP && a !== TOP) return 1;
|
||||
return a.localeCompare(b);
|
||||
},
|
||||
requestInterceptor: (req) => {
|
||||
const token = localStorage.getItem('%s');
|
||||
if (token) {
|
||||
req.headers = req.headers || {};
|
||||
req.headers['Authorization'] = 'Bearer ' + token;
|
||||
}
|
||||
return req;
|
||||
},
|
||||
responseInterceptor: async (res) => {
|
||||
try {
|
||||
const isLogin = (res?.url?.includes('%s') && res?.status === 200);
|
||||
if (isLogin) {
|
||||
const text = (typeof res.data === 'string') ? res.data : JSON.stringify(res.data);
|
||||
const json = JSON.parse(text);
|
||||
const token = json?.data?.accessToken;
|
||||
|
||||
if (token) {
|
||||
localStorage.setItem('%s', token);
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
return res;
|
||||
},
|
||||
"""
|
||||
.formatted(TOKEN_KEY, loginPathContains, TOKEN_KEY);
|
||||
|
||||
html = html.replace("SwaggerUIBundle({", "SwaggerUIBundle({\n" + inject);
|
||||
|
||||
return new TransformedResource(transformed, html.getBytes(StandardCharsets.UTF_8));
|
||||
} catch (Exception e) {
|
||||
// 실패 시 원본 반환(문서 깨짐 방지)
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package com.kamco.cd.kamcoback.enums;
|
||||
|
||||
public class CodeDto {
|
||||
|
||||
private String code;
|
||||
private String name;
|
||||
|
||||
public CodeDto(String code, String name) {
|
||||
this.code = code;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
package com.kamco.cd.kamcoback.enums;
|
||||
|
||||
import com.kamco.cd.kamcoback.inferface.CodeExpose;
|
||||
import com.kamco.cd.kamcoback.inferface.CodeHidden;
|
||||
import com.kamco.cd.kamcoback.inferface.EnumType;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.reflections.Reflections;
|
||||
|
||||
public class Enums {
|
||||
|
||||
private static final String BASE_PACKAGE = "com.kamco.cd.kamcoback";
|
||||
|
||||
/** 노출 가능한 enum만 모아둔 맵 key: enum simpleName (예: RoleType) value: enum Class */
|
||||
private static final Map<String, Class<? extends Enum<?>>> exposedEnumMap = scanExposedEnumMap();
|
||||
|
||||
// code로 enum 찾기
|
||||
public static <E extends Enum<E> & EnumType> E fromId(Class<E> enumClass, String id) {
|
||||
if (id == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (E e : enumClass.getEnumConstants()) {
|
||||
if (id.equalsIgnoreCase(e.getId())) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// enum -> CodeDto list
|
||||
public static List<CodeDto> toList(Class<? extends Enum<?>> enumClass) {
|
||||
Object[] enums = enumClass.getEnumConstants();
|
||||
|
||||
return Arrays.stream(enums)
|
||||
.map(e -> (EnumType) e)
|
||||
.filter(e -> !isHidden(enumClass, (Enum<?>) e))
|
||||
.map(e -> new CodeDto(e.getId(), e.getText()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private static boolean isHidden(Class<? extends Enum<?>> enumClass, Enum<?> e) {
|
||||
try {
|
||||
return enumClass.getField(e.name()).isAnnotationPresent(CodeHidden.class);
|
||||
} catch (NoSuchFieldException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 특정 타입(enum)만 조회 /codes/{type} -> type = RoleType 같은 값 */
|
||||
public static List<CodeDto> getCodes(String type) {
|
||||
Class<? extends Enum<?>> enumClass = exposedEnumMap.get(type);
|
||||
if (enumClass == null) {
|
||||
throw new IllegalArgumentException("지원하지 않는 코드 타입: " + type);
|
||||
}
|
||||
return toList(enumClass);
|
||||
}
|
||||
|
||||
/** 전체 enum 코드 조회 */
|
||||
public static Map<String, List<CodeDto>> getAllCodes() {
|
||||
Map<String, List<CodeDto>> result = new HashMap<>();
|
||||
for (Map.Entry<String, Class<? extends Enum<?>>> e : exposedEnumMap.entrySet()) {
|
||||
result.put(e.getKey(), toList(e.getValue()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** CodeExpose + EnumType 인 enum만 스캔해서 Map 구성 */
|
||||
private static Map<String, Class<? extends Enum<?>>> scanExposedEnumMap() {
|
||||
Reflections reflections = new Reflections(BASE_PACKAGE);
|
||||
|
||||
Set<Class<?>> types = reflections.getTypesAnnotatedWith(CodeExpose.class);
|
||||
|
||||
Map<String, Class<? extends Enum<?>>> result = new HashMap<>();
|
||||
|
||||
for (Class<?> clazz : types) {
|
||||
if (clazz.isEnum() && EnumType.class.isAssignableFrom(clazz)) {
|
||||
result.put(clazz.getSimpleName(), (Class<? extends Enum<?>>) clazz);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,8 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.kamco.cd.kamcoback.common.utils.enums.ImageryFitStatus;
|
||||
import com.kamco.cd.kamcoback.common.enums.DetectionClassification;
|
||||
import com.kamco.cd.kamcoback.common.enums.ImageryFitStatus;
|
||||
import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm;
|
||||
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.DetectOption;
|
||||
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.MapSheetScope;
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.kamco.cd.kamcoback.inference.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class InferenceProgressDto {
|
||||
|
||||
private pred_requests_areas pred_requests_areas;
|
||||
private String modelVersion;
|
||||
private String cdModelPath;
|
||||
private String cdModelFileName;
|
||||
private String cdModelConfigPath;
|
||||
private String cdModelConfigFileName;
|
||||
private String cdModelClsPath;
|
||||
private String cdModelClsFileName;
|
||||
private String clsModelVersion;
|
||||
private Double priority;
|
||||
|
||||
public InferenceProgressDto(
|
||||
pred_requests_areas pred_requests_areas,
|
||||
String modelVersion,
|
||||
String cdModelPath,
|
||||
String cdModelFileName,
|
||||
String cdModelConfigPath,
|
||||
String cdModelConfigFileName,
|
||||
String cdModelClsPath,
|
||||
String cdModelClsFileName,
|
||||
String clsModelVersion,
|
||||
Double priority) {
|
||||
this.pred_requests_areas = pred_requests_areas;
|
||||
this.modelVersion = modelVersion;
|
||||
this.cdModelPath = cdModelPath;
|
||||
this.cdModelFileName = cdModelFileName;
|
||||
this.cdModelConfigPath = cdModelConfigPath;
|
||||
this.cdModelConfigFileName = cdModelConfigFileName;
|
||||
this.cdModelClsPath = cdModelClsPath;
|
||||
this.cdModelClsFileName = cdModelClsFileName;
|
||||
this.clsModelVersion = clsModelVersion;
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class pred_requests_areas {
|
||||
|
||||
private Integer input1_year;
|
||||
private Integer input2_year;
|
||||
private String input1_scene_path;
|
||||
private String input2_scene_path;
|
||||
}
|
||||
}
|
||||
@@ -246,15 +246,15 @@ public class InferenceResultDto {
|
||||
@NotBlank
|
||||
private String title;
|
||||
|
||||
@Schema(description = "M1", example = "b40e0f68-c1d8-49fc-93f9-a36270093861")
|
||||
@Schema(description = "G1", example = "b40e0f68-c1d8-49fc-93f9-a36270093861")
|
||||
@NotNull
|
||||
private UUID model1Uuid;
|
||||
|
||||
@Schema(description = "M2", example = "ec92b7d2-b5a3-4915-9bdf-35fb3ca8ad27")
|
||||
@Schema(description = "G2", example = "ec92b7d2-b5a3-4915-9bdf-35fb3ca8ad27")
|
||||
@NotNull
|
||||
private UUID model2Uuid;
|
||||
|
||||
@Schema(description = "M3", example = "37f45782-8ccf-4cf6-911c-a055a1510d39")
|
||||
@Schema(description = "G3", example = "37f45782-8ccf-4cf6-911c-a055a1510d39")
|
||||
@NotNull
|
||||
private UUID model3Uuid;
|
||||
|
||||
@@ -297,6 +297,30 @@ public class InferenceResultDto {
|
||||
@Schema(name = "InferenceStatusDetailDto", description = "추론(변화탐지) 진행상태")
|
||||
public static class InferenceStatusDetailDto {
|
||||
|
||||
@Schema(description = "모델1 사용시간 시작일시")
|
||||
@JsonFormatDttm
|
||||
ZonedDateTime m1ModelStartDttm;
|
||||
|
||||
@Schema(description = "모델2 사용시간 시작일시")
|
||||
@JsonFormatDttm
|
||||
ZonedDateTime m2ModelStartDttm;
|
||||
|
||||
@Schema(description = "모델3 사용시간 시작일시")
|
||||
@JsonFormatDttm
|
||||
ZonedDateTime m3ModelStartDttm;
|
||||
|
||||
@Schema(description = "모델1 사용시간 종료일시")
|
||||
@JsonFormatDttm
|
||||
ZonedDateTime m1ModelEndDttm;
|
||||
|
||||
@Schema(description = "모델2 사용시간 종료일시")
|
||||
@JsonFormatDttm
|
||||
ZonedDateTime m2ModelEndDttm;
|
||||
|
||||
@Schema(description = "모델3 사용시간 종료일시")
|
||||
@JsonFormatDttm
|
||||
ZonedDateTime m3ModelEndDttm;
|
||||
|
||||
@Schema(description = "탐지대상 도엽수")
|
||||
private Long detectingCnt;
|
||||
|
||||
@@ -336,30 +360,6 @@ public class InferenceResultDto {
|
||||
@Schema(description = "모델3 분석 실패")
|
||||
private Integer m3FailedJobs;
|
||||
|
||||
@Schema(description = "모델1 사용시간 시작일시")
|
||||
@JsonFormatDttm
|
||||
ZonedDateTime m1ModelStartDttm;
|
||||
|
||||
@Schema(description = "모델2 사용시간 시작일시")
|
||||
@JsonFormatDttm
|
||||
ZonedDateTime m2ModelStartDttm;
|
||||
|
||||
@Schema(description = "모델3 사용시간 시작일시")
|
||||
@JsonFormatDttm
|
||||
ZonedDateTime m3ModelStartDttm;
|
||||
|
||||
@Schema(description = "모델1 사용시간 종료일시")
|
||||
@JsonFormatDttm
|
||||
ZonedDateTime m1ModelEndDttm;
|
||||
|
||||
@Schema(description = "모델2 사용시간 종료일시")
|
||||
@JsonFormatDttm
|
||||
ZonedDateTime m2ModelEndDttm;
|
||||
|
||||
@Schema(description = "모델3 사용시간 종료일시")
|
||||
@JsonFormatDttm
|
||||
ZonedDateTime m3ModelEndDttm;
|
||||
|
||||
@Schema(description = "변화탐지 제목")
|
||||
private String title;
|
||||
|
||||
@@ -496,19 +496,19 @@ public class InferenceResultDto {
|
||||
return MapSheetScope.getDescByCode(this.mapSheetScope);
|
||||
}
|
||||
|
||||
@Schema(description = "M1 사용시간")
|
||||
@Schema(description = "G1 사용시간")
|
||||
@JsonProperty("m1ElapsedTim")
|
||||
public String getM1ElapsedTime() {
|
||||
return formatElapsedTime(this.m1ModelStartDttm, this.m1ModelEndDttm);
|
||||
}
|
||||
|
||||
@Schema(description = "M2 사용시간")
|
||||
@Schema(description = "G2 사용시간")
|
||||
@JsonProperty("m2ElapsedTim")
|
||||
public String getM2ElapsedTime() {
|
||||
return formatElapsedTime(this.m2ModelStartDttm, this.m2ModelEndDttm);
|
||||
}
|
||||
|
||||
@Schema(description = "M3 사용시간")
|
||||
@Schema(description = "G3 사용시간")
|
||||
@JsonProperty("m3ElapsedTim")
|
||||
public String getM3ElapsedTime() {
|
||||
return formatElapsedTime(this.m3ModelStartDttm, this.m3ModelEndDttm);
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
package com.kamco.cd.kamcoback.inference.dto;
|
||||
|
||||
import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataInferenceGeomEntity;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.util.UUID;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
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 Double cdProb;
|
||||
|
||||
private String beforeClass;
|
||||
private Double beforeProbability;
|
||||
|
||||
private String afterClass;
|
||||
private Double afterProbability;
|
||||
|
||||
// ===== 공간 정보 =====
|
||||
private Geometry geometry;
|
||||
private Double 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;
|
||||
}
|
||||
}
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class InferenceCntDto {
|
||||
|
||||
@Schema(description = "추론 결과(inference_results)를 기준으로 신규 목록 저장 터이터 건수", example = "120")
|
||||
int sheetAnalDataCnt;
|
||||
|
||||
@Schema(description = "추론 결과(inference_results)를 기준으로 신규 저장 데이터 건수", example = "120")
|
||||
int inferenceCnt;
|
||||
|
||||
@Schema(description = "추론 결과(inference_results)를 기준으로 신규 저장 Geom 데이터 건수", example = "120")
|
||||
int inferenceGeomCnt;
|
||||
}
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class FileCntDto {
|
||||
|
||||
@Schema(description = "shp 파일 생성 수 (덮어쓰기 포함)", example = "120")
|
||||
private int shp;
|
||||
|
||||
@Schema(description = "shx 파일 생성 수 (덮어쓰기 포함)", example = "120")
|
||||
private int shx;
|
||||
|
||||
@Schema(description = "dbf 파일 생성 수 (덮어쓰기 포함)", example = "120")
|
||||
private int dbf;
|
||||
|
||||
@Schema(description = "prj 파일 생성 수 (덮어쓰기 포함)", example = "120")
|
||||
private int prj;
|
||||
|
||||
@Schema(description = "geojson 파일 생성 수 (덮어쓰기 포함)", example = "120")
|
||||
private int geojson;
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class CreateShpRequest {
|
||||
|
||||
private Long m1BatchId;
|
||||
private Long m2BatchId;
|
||||
private Long m3BatchId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.kamco.cd.kamcoback.inference.dto;
|
||||
|
||||
import com.kamco.cd.kamcoback.postgres.entity.InferenceResultsTestingEntity;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
public class InferenceResultsTestingDto {
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class ShpDto {
|
||||
|
||||
private Long batchId;
|
||||
private String uid;
|
||||
private String mapId;
|
||||
|
||||
public static ShpDto fromEntity(InferenceResultsTestingEntity e) {
|
||||
return new ShpDto(e.getBatchId(), e.getUid(), e.getMapId());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.kamco.cd.kamcoback.inference.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
/** AI API 추론 실행 DTO */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@ToString
|
||||
public class InferenceSendDto {
|
||||
|
||||
private pred_requests_areas pred_requests_areas;
|
||||
private String model_version;
|
||||
private String cd_model_path;
|
||||
private String cd_model_config;
|
||||
private String cls_model_path;
|
||||
private String cls_model_version;
|
||||
private String cd_model_type;
|
||||
private Double priority;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@ToString
|
||||
public static class pred_requests_areas {
|
||||
|
||||
private Integer input1_year;
|
||||
private Integer input2_year;
|
||||
private String input1_scene_path;
|
||||
private String input2_scene_path;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
package com.kamco.cd.kamcoback.inference.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.util.List;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/** DTO classes for learning model result processing */
|
||||
public class LearningModelResultDto {
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Schema(description = "학습모델 결과 처리 요청")
|
||||
public static class ProcessRequest {
|
||||
|
||||
@Schema(
|
||||
description = "GeoJSON 파일 경로",
|
||||
example =
|
||||
"src/main/resources/db/migration/sample-results_updated/캠코_2021_2022_35813023.geojson")
|
||||
private String filePath;
|
||||
}
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Schema(description = "학습모델 결과 처리 응답")
|
||||
public static class ProcessResponse {
|
||||
|
||||
@Schema(description = "처리 성공 여부")
|
||||
private boolean success;
|
||||
|
||||
@Schema(description = "처리 결과 메시지")
|
||||
private String message;
|
||||
|
||||
@Schema(description = "처리된 feature 개수")
|
||||
private int processedFeatures;
|
||||
|
||||
@Schema(description = "처리된 파일 경로")
|
||||
private String filePath;
|
||||
}
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Schema(description = "학습모델 결과 일괄 처리 요청")
|
||||
public static class BatchProcessRequest {
|
||||
|
||||
@Schema(description = "GeoJSON 파일 경로 목록")
|
||||
private List<String> filePaths;
|
||||
}
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Schema(description = "학습모델 결과 일괄 처리 응답")
|
||||
public static class BatchProcessResponse {
|
||||
|
||||
@Schema(description = "처리 성공 여부")
|
||||
private boolean success;
|
||||
|
||||
@Schema(description = "처리 결과 메시지")
|
||||
private String message;
|
||||
|
||||
@Schema(description = "전체 처리된 feature 개수")
|
||||
private int totalProcessedFeatures;
|
||||
|
||||
@Schema(description = "처리된 파일 개수")
|
||||
private int processedFileCount;
|
||||
|
||||
@Schema(description = "처리된 파일 경로 목록")
|
||||
private List<String> filePaths;
|
||||
}
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Schema(description = "학습모델 처리 상태")
|
||||
public static class ProcessingStatus {
|
||||
|
||||
@Schema(description = "처리 ID")
|
||||
private String processingId;
|
||||
|
||||
@Schema(description = "처리 상태 (PENDING, PROCESSING, COMPLETED, FAILED)")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "진행률 (0-100)")
|
||||
private int progressPercentage;
|
||||
|
||||
@Schema(description = "현재 처리 중인 파일")
|
||||
private String currentFile;
|
||||
|
||||
@Schema(description = "전체 파일 개수")
|
||||
private int totalFiles;
|
||||
|
||||
@Schema(description = "처리 완료된 파일 개수")
|
||||
private int completedFiles;
|
||||
|
||||
@Schema(description = "시작 시간")
|
||||
private String startTime;
|
||||
|
||||
@Schema(description = "예상 완료 시간")
|
||||
private String estimatedEndTime;
|
||||
}
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Schema(description = "학습모델 데이터 요약")
|
||||
public static class DataSummary {
|
||||
|
||||
@Schema(description = "전체 데이터 개수")
|
||||
private long totalRecords;
|
||||
|
||||
@Schema(description = "연도별 데이터 개수")
|
||||
private List<YearDataCount> yearDataCounts;
|
||||
|
||||
@Schema(description = "분류별 데이터 개수")
|
||||
private List<ClassDataCount> classDataCounts;
|
||||
|
||||
@Schema(description = "지도 영역별 데이터 개수")
|
||||
private List<MapSheetDataCount> mapSheetDataCounts;
|
||||
}
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Schema(description = "연도별 데이터 개수")
|
||||
public static class YearDataCount {
|
||||
|
||||
@Schema(description = "비교 연도 (예: 2021_2022)")
|
||||
private String compareYear;
|
||||
|
||||
@Schema(description = "데이터 개수")
|
||||
private long count;
|
||||
}
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Schema(description = "분류별 데이터 개수")
|
||||
public static class ClassDataCount {
|
||||
|
||||
@Schema(description = "분류명")
|
||||
private String className;
|
||||
|
||||
@Schema(description = "변화 전 개수")
|
||||
private long beforeCount;
|
||||
|
||||
@Schema(description = "변화 후 개수")
|
||||
private long afterCount;
|
||||
}
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Schema(description = "지도 영역별 데이터 개수")
|
||||
public static class MapSheetDataCount {
|
||||
|
||||
@Schema(description = "지도 영역 번호")
|
||||
private String mapSheetNum;
|
||||
|
||||
@Schema(description = "데이터 개수")
|
||||
private long count;
|
||||
|
||||
@Schema(description = "평균 변화 탐지 확률")
|
||||
private double avgChangeDetectionProb;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.kamco.cd.kamcoback.inference.dto;
|
||||
|
||||
public record WriteCnt(int shp, int shx, int dbf, int prj, int geojson) {
|
||||
|
||||
public static WriteCnt zero() {
|
||||
return new WriteCnt(0, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
public WriteCnt plus(WriteCnt o) {
|
||||
return new WriteCnt(
|
||||
this.shp + o.shp,
|
||||
this.shx + o.shx,
|
||||
this.dbf + o.dbf,
|
||||
this.prj + o.prj,
|
||||
this.geojson + o.geojson);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package com.kamco.cd.kamcoback.inferface;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface CodeExpose {}
|
||||
@@ -1,10 +0,0 @@
|
||||
package com.kamco.cd.kamcoback.inferface;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface CodeHidden {}
|
||||
@@ -1,8 +0,0 @@
|
||||
package com.kamco.cd.kamcoback.inferface;
|
||||
|
||||
public interface EnumType {
|
||||
|
||||
String getId();
|
||||
|
||||
String getText();
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.kamco.cd.kamcoback.inferface;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target({ElementType.FIELD, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@JacksonAnnotationsInside
|
||||
@JsonFormat(
|
||||
shape = JsonFormat.Shape.STRING,
|
||||
pattern = "yyyy-MM-dd'T'HH:mm:ssXXX",
|
||||
timezone = "Asia/Seoul")
|
||||
public @interface JsonFormatDttm {}
|
||||
@@ -1,8 +1,9 @@
|
||||
package com.kamco.cd.kamcoback.dto;
|
||||
package com.kamco.cd.kamcoback.label.dto;
|
||||
|
||||
import com.kamco.cd.kamcoback.common.utils.enums.CodeExpose;
|
||||
import com.kamco.cd.kamcoback.common.utils.enums.EnumType;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
@@ -359,4 +360,41 @@ public class LabelAllocateDto {
|
||||
@Schema(description = "작업기간 종료일")
|
||||
private ZonedDateTime projectCloseDttm;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class InferenceLearnDto {
|
||||
|
||||
private UUID analUuid;
|
||||
private String learnUid;
|
||||
private String analState;
|
||||
private Long analId;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
public static class AllocateAddStbltDto {
|
||||
|
||||
@Schema(description = "총 잔여 건수", example = "179")
|
||||
private Integer totalCnt;
|
||||
|
||||
@Schema(
|
||||
description = "추가할당할 라벨러",
|
||||
example =
|
||||
"""
|
||||
[
|
||||
"123454", "654321", "222233", "777222"
|
||||
]
|
||||
""")
|
||||
private List<String> labelers;
|
||||
|
||||
@Schema(description = "회차 마스터 key", example = "c0e77cc7-8c28-46ba-9ca4-11e90246ab44")
|
||||
private UUID uuid;
|
||||
|
||||
@Schema(description = "기준일자", example = "2026-02-20")
|
||||
private LocalDate baseDate;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.kamco.cd.kamcoback.dto;
|
||||
package com.kamco.cd.kamcoback.label.dto;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.UUID;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.kamco.cd.kamcoback.dto;
|
||||
package com.kamco.cd.kamcoback.label.dto;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.UUID;
|
||||
@@ -0,0 +1,263 @@
|
||||
package com.kamco.cd.kamcoback.label.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.kamco.cd.kamcoback.common.utils.enums.Enums;
|
||||
import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm;
|
||||
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelMngState;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.UUID;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
|
||||
public class LabelWorkDto {
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
public static class ChangeDetectYear {
|
||||
|
||||
private String code;
|
||||
private String name;
|
||||
}
|
||||
|
||||
@Schema(name = "LabelWorkMng", description = "라벨작업관리")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class LabelWorkMng {
|
||||
|
||||
private UUID uuid;
|
||||
private Integer compareYyyy;
|
||||
private Integer targetYyyy;
|
||||
private Integer stage;
|
||||
@JsonFormatDttm private ZonedDateTime gukyuinApplyDttm;
|
||||
private Long detectionTotCnt;
|
||||
private Long labelTotCnt;
|
||||
private Long labelAssignCnt;
|
||||
private Long labelSkipTotCnt;
|
||||
private Long labelCompleteTotCnt;
|
||||
@JsonFormatDttm private ZonedDateTime labelStartDttm;
|
||||
|
||||
// tb_map_sheet_anal_inference.anal_state 컬럼 값
|
||||
private String analState;
|
||||
|
||||
// tb_labeling_assignment 테이블에서 stagnation_yn = 'N'인 정상 진행 건수
|
||||
private Long normalProgressCnt;
|
||||
|
||||
// tb_labeling_assignment 테이블에서 총 배정 건수
|
||||
private Long totalAssignmentCnt;
|
||||
|
||||
private String labelingClosedYn;
|
||||
private String inspectionClosedYn;
|
||||
|
||||
private Long inspectorCompleteTotCnt;
|
||||
private Long inspectorRemainCnt;
|
||||
private ZonedDateTime projectCloseDttm;
|
||||
|
||||
private String resultUid;
|
||||
private String subUid;
|
||||
private UUID learnUuid;
|
||||
|
||||
@JsonProperty("detectYear")
|
||||
public String getDetectYear() {
|
||||
if (compareYyyy == null || targetYyyy == null) {
|
||||
return null;
|
||||
}
|
||||
return compareYyyy + "-" + targetYyyy;
|
||||
}
|
||||
|
||||
/** 라벨링 상태 반환 (tb_map_sheet_anal_inference.anal_state 기준) */
|
||||
public String getLabelState() {
|
||||
// anal_state 값이 있으면 해당 값 사용 -> 우선은 미사용
|
||||
if (this.analState != null && !this.analState.isEmpty()) {
|
||||
return this.analState;
|
||||
}
|
||||
|
||||
// anal_state 값이 없으면 기존 로직으로 폴백
|
||||
String mngState = LabelMngState.PENDING.getId();
|
||||
|
||||
if (this.labelTotCnt == 0) {
|
||||
mngState = LabelMngState.PENDING.getId();
|
||||
} else if (this.labelTotCnt > 0 && this.labelAssignCnt > 0 && this.labelCompleteTotCnt == 0) {
|
||||
mngState = LabelMngState.ASSIGNED.getId();
|
||||
} else if (this.labelingClosedYn.equals("Y") && this.inspectionClosedYn.equals("Y")) {
|
||||
mngState = LabelMngState.FINISH.getId();
|
||||
} else if (this.labelCompleteTotCnt > 0) {
|
||||
mngState = LabelMngState.ING.getId();
|
||||
}
|
||||
|
||||
return mngState;
|
||||
}
|
||||
|
||||
public String getLabelStateName() {
|
||||
String enumId = this.getLabelState();
|
||||
if (enumId == null || enumId.isEmpty()) {
|
||||
enumId = "PENDING";
|
||||
}
|
||||
|
||||
LabelMngState type = Enums.fromId(LabelMngState.class, enumId);
|
||||
// type이 null인 경우 (enum에 정의되지 않은 상태값) 상태값 자체를 반환
|
||||
if (type == null) {
|
||||
return enumId;
|
||||
}
|
||||
return type.getText();
|
||||
}
|
||||
|
||||
/**
|
||||
* 작업 진행률 반환 (tb_labeling_assignment.stagnation_yn = 'N'인 정상 진행율 기준) 계산식: (정상 진행 건수 / 총 배정 건수) *
|
||||
* 100
|
||||
*/
|
||||
public double getLabelRate() {
|
||||
if (this.totalAssignmentCnt == null || this.totalAssignmentCnt == 0) {
|
||||
return 0.0;
|
||||
}
|
||||
if (this.labelCompleteTotCnt == null) {
|
||||
return 0.0;
|
||||
}
|
||||
return Math.round(((double) this.labelCompleteTotCnt / this.totalAssignmentCnt * 100.0) * 100)
|
||||
/ 100.0;
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class LabelWorkMngDetail {
|
||||
|
||||
private String detectionYear;
|
||||
private Integer stage;
|
||||
@JsonFormatDttm private ZonedDateTime createdDttm;
|
||||
private Long labelTotCnt;
|
||||
private Long labeler;
|
||||
private Long reviewer;
|
||||
}
|
||||
|
||||
@Schema(name = "LabelWorkMngSearchReq", description = "라벨작업관리 검색 요청")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class LabelWorkMngSearchReq {
|
||||
|
||||
// 페이징 파라미터
|
||||
@Schema(description = "페이지 번호 (0부터 시작) ", example = "0")
|
||||
private int page = 0;
|
||||
|
||||
@Schema(description = "페이지 크기", example = "20")
|
||||
private int size = 20;
|
||||
|
||||
@Schema(description = "변화탐지년도", example = "2024")
|
||||
private String detectYear;
|
||||
|
||||
@Schema(description = "시작일", example = "2026-01-01")
|
||||
private LocalDate strtDttm;
|
||||
|
||||
@Schema(description = "종료일", example = "2026-12-01")
|
||||
private LocalDate endDttm;
|
||||
|
||||
public Pageable toPageable() {
|
||||
|
||||
return PageRequest.of(page, size);
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "작업자 통계 응답")
|
||||
public static class WorkerState {
|
||||
|
||||
@Schema(description = "작업자 유형 (LABELER/INSPECTOR)")
|
||||
private String userRole;
|
||||
|
||||
@Schema(description = "작업자 ID (사번)")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "작업자 이름")
|
||||
private String userId;
|
||||
|
||||
@Schema(description = "배정개수")
|
||||
private Long assignedCnt;
|
||||
|
||||
@Schema(description = "완료개수")
|
||||
private Long doneCnt;
|
||||
|
||||
@Schema(description = "Skip개수")
|
||||
private Long skipCnt;
|
||||
|
||||
@Schema(description = "3일전처리개수")
|
||||
private Long day3AgoDoneCnt;
|
||||
|
||||
@Schema(description = "2일전처리개수")
|
||||
private Long day2AgoDoneCnt;
|
||||
|
||||
@Schema(description = "1일전처리개수")
|
||||
private Long day1AgoDoneCnt;
|
||||
|
||||
@Schema(description = "계정 상태")
|
||||
private String memberStatus;
|
||||
|
||||
public Long getRemainCnt() {
|
||||
return this.assignedCnt - this.doneCnt;
|
||||
}
|
||||
|
||||
public double getDoneRate() {
|
||||
long dayDoneCnt = this.day3AgoDoneCnt + this.day2AgoDoneCnt + this.day1AgoDoneCnt;
|
||||
|
||||
if (dayDoneCnt == 0) {
|
||||
return 0.0;
|
||||
}
|
||||
return (double) dayDoneCnt / 3;
|
||||
}
|
||||
|
||||
public String getUserRoleName() {
|
||||
if (this.userRole.equals("LABELER")) {
|
||||
return "라벨러";
|
||||
}
|
||||
return "검수자";
|
||||
}
|
||||
}
|
||||
|
||||
@Schema(name = "WorkerStateSearchReq", description = "라벨작업관리 검색 요청")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class WorkerStateSearchReq {
|
||||
|
||||
// 페이징 파라미터
|
||||
@Schema(description = "페이지 번호 (0부터 시작) ", example = "0")
|
||||
private int page = 0;
|
||||
|
||||
@Schema(description = "페이지 크기", example = "20")
|
||||
private int size = 20;
|
||||
|
||||
@Schema(description = "유형", example = "LABELER")
|
||||
private String userRole;
|
||||
|
||||
@Schema(description = "종료일", example = "20261201")
|
||||
private String searchVal;
|
||||
|
||||
@Schema(description = "종료일", example = "20261201")
|
||||
private String uuid;
|
||||
|
||||
@Schema(description = "정렬(remindCnt desc, doneCnt desc)", example = "remindCnt desc")
|
||||
private String sort;
|
||||
|
||||
public Pageable toPageable() {
|
||||
|
||||
return PageRequest.of(page, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
package com.kamco.cd.kamcoback.label.dto;
|
||||
|
||||
import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import java.time.ZonedDateTime;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
public class WorkerStatsDto {
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "프로젝트 기본 정보 (상단 표시용)")
|
||||
public static class ProjectInfo {
|
||||
|
||||
@Schema(description = "변화탐지년도 (예: 2026-2025)")
|
||||
private String detectionYear;
|
||||
|
||||
@Schema(description = "회차 (예: 8)")
|
||||
private String stage;
|
||||
|
||||
@Schema(description = "국유인 반영일 (예: 2026-03-31)")
|
||||
@JsonFormatDttm
|
||||
private ZonedDateTime gukyuinApplyDttm;
|
||||
|
||||
@Schema(description = "작업 시작일 (예: 2026-04-06)")
|
||||
@JsonFormatDttm
|
||||
private ZonedDateTime startDttm;
|
||||
|
||||
@Schema(description = "프로젝트 UUID")
|
||||
private String uuid;
|
||||
|
||||
@Schema(description = "라벨링 종료 여부 (Y: 종료, N: 진행중)")
|
||||
private String labelingClosedYn;
|
||||
|
||||
@Schema(description = "검수 종료 여부 (Y: 종료, N: 진행중)")
|
||||
private String inspectionClosedYn;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "프로젝트 종료 여부 업데이트 요청")
|
||||
public static class UpdateClosedRequest {
|
||||
|
||||
@Schema(
|
||||
description = "프로젝트 UUID (선택) - 미입력 시 현재 진행중인 최신 프로젝트가 대상",
|
||||
example = "f97dc186-e6d3-4645-9737-3173dde8dc64")
|
||||
private String uuid;
|
||||
|
||||
@Pattern(
|
||||
regexp = "^(LABELING|INSPECTION|BOTH)$",
|
||||
message = "종료 유형은 LABELING, INSPECTION 또는 BOTH 이어야 합니다.")
|
||||
@Schema(
|
||||
description = "종료 유형 (LABELING: 라벨링만, INSPECTION: 검수만, BOTH: 라벨링+검수 동시)",
|
||||
example = "LABELING",
|
||||
allowableValues = {"LABELING", "INSPECTION", "BOTH"},
|
||||
requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private String closedType;
|
||||
|
||||
@NotBlank(message = "종료 여부는 필수입니다.")
|
||||
@Pattern(regexp = "^[YN]$", message = "종료 여부는 Y 또는 N이어야 합니다.")
|
||||
@Schema(
|
||||
description = "종료 여부 (Y: 종료, N: 진행중)",
|
||||
example = "Y",
|
||||
allowableValues = {"Y", "N"},
|
||||
requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String closedYn;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "작업자 통계 응답")
|
||||
public static class WorkerStatistics {
|
||||
|
||||
@Schema(description = "작업자 ID (사번)")
|
||||
private String workerId;
|
||||
|
||||
@Schema(description = "작업자 이름")
|
||||
private String workerName;
|
||||
|
||||
@Schema(description = "작업자 유형 (LABELER/INSPECTOR)")
|
||||
private String workerType;
|
||||
|
||||
@Schema(description = "전체 배정 건수")
|
||||
private Long totalAssigned;
|
||||
|
||||
@Schema(description = "완료 건수")
|
||||
private Long completed;
|
||||
|
||||
@Schema(description = "스킵 건수")
|
||||
private Long skipped;
|
||||
|
||||
@Schema(description = "남은 작업 건수")
|
||||
private Long remaining;
|
||||
|
||||
@Schema(description = "최근 3일간 처리 이력")
|
||||
private DailyHistory history;
|
||||
|
||||
@Schema(description = "작업 정체 여부 (3일간 실적이 저조하면 true)")
|
||||
private Boolean isStagnated;
|
||||
|
||||
// 레거시 필드 (기존 호환성 유지)
|
||||
@Deprecated private Long doneCnt; // completed로 대체
|
||||
|
||||
@Deprecated private Long skipCnt; // skipped로 대체
|
||||
|
||||
@Deprecated private Long remainingCnt; // remaining으로 대체
|
||||
|
||||
@Deprecated private Long day3AgoDoneCnt; // history.day3Ago로 대체
|
||||
|
||||
@Deprecated private Long day2AgoDoneCnt; // history.day2Ago로 대체
|
||||
|
||||
@Deprecated private Long day1AgoDoneCnt; // history.day1Ago로 대체
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "최근 3일간 일일 처리 이력")
|
||||
public static class DailyHistory {
|
||||
|
||||
@Schema(description = "1일 전 (어제) 처리량")
|
||||
private Long day1Ago;
|
||||
|
||||
@Schema(description = "2일 전 처리량")
|
||||
private Long day2Ago;
|
||||
|
||||
@Schema(description = "3일 전 처리량")
|
||||
private Long day3Ago;
|
||||
|
||||
@Schema(description = "3일 평균 처리량")
|
||||
private Long average;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "작업 진행 현황 정보")
|
||||
public static class WorkProgressInfo {
|
||||
|
||||
// === 라벨링 관련 ===
|
||||
@Schema(description = "라벨링 진행률 (완료건+스킵건)/배정건")
|
||||
private Double labelingProgressRate;
|
||||
|
||||
@Schema(description = "라벨링 작업 상태 (진행중/완료)")
|
||||
private String labelingStatus;
|
||||
|
||||
@Schema(description = "라벨링 전체 배정 건수")
|
||||
private Long labelingTotalCount;
|
||||
|
||||
@Schema(description = "라벨링 완료 건수 (LABEL_FIN + TEST_ING + DONE)")
|
||||
private Long labelingCompletedCount;
|
||||
|
||||
@Schema(description = "라벨링 스킵 건수 (SKIP)")
|
||||
private Long labelingSkipCount;
|
||||
|
||||
@Schema(description = "라벨링 남은 작업 건수")
|
||||
private Long labelingRemainingCount;
|
||||
|
||||
@Schema(description = "투입된 라벨러 수")
|
||||
private Long labelerCount;
|
||||
|
||||
// === 검수(Inspection) 관련 (신규 추가) ===
|
||||
@Schema(description = "검수 진행률 (완료건/대상건)")
|
||||
private Double inspectionProgressRate;
|
||||
|
||||
@Schema(description = "검수 작업 상태 (진행중/완료)")
|
||||
private String inspectionStatus;
|
||||
|
||||
@Schema(description = "검수 대상 건수 (라벨링 대상과 동일)")
|
||||
private Long inspectionTotalCount;
|
||||
|
||||
@Schema(description = "검수 완료 건수 (DONE)")
|
||||
private Long inspectionCompletedCount;
|
||||
|
||||
@Schema(description = "검수 제외 건수 (라벨링 스킵과 동일)")
|
||||
private Long inspectionSkipCount;
|
||||
|
||||
@Schema(description = "검수 남은 작업 건수")
|
||||
private Long inspectionRemainingCount;
|
||||
|
||||
@Schema(description = "투입된 검수자 수")
|
||||
private Long inspectorCount;
|
||||
|
||||
// === 레거시 호환 필드 (Deprecated) ===
|
||||
@Deprecated
|
||||
@Schema(description = "[Deprecated] labelingProgressRate 사용 권장")
|
||||
private Double progressRate;
|
||||
|
||||
@Deprecated
|
||||
@Schema(description = "[Deprecated] labelingTotalCount 사용 권장")
|
||||
private Long totalAssignedCount;
|
||||
|
||||
@Deprecated
|
||||
@Schema(description = "[Deprecated] labelingCompletedCount 사용 권장")
|
||||
private Long completedCount;
|
||||
|
||||
@Deprecated
|
||||
@Schema(description = "[Deprecated] labelingRemainingCount 사용 권장")
|
||||
private Long remainingLabelCount;
|
||||
|
||||
@Deprecated
|
||||
@Schema(description = "[Deprecated] inspectionRemainingCount 사용 권장")
|
||||
private Long remainingInspectCount;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "작업자 목록 응답 (작업 정보 포함)")
|
||||
public static class WorkerListResponse {
|
||||
|
||||
@Schema(description = "프로젝트 기본 정보 (상단 표시용)")
|
||||
private ProjectInfo projectInfo;
|
||||
|
||||
@Schema(description = "작업 진행 현황 정보")
|
||||
private WorkProgressInfo progressInfo;
|
||||
|
||||
// workers 필드는 제거되었습니다 (프로젝트 정보와 진행현황만 반환)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
package com.kamco.cd.kamcoback.members.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.kamco.cd.kamcoback.common.enums.RoleType;
|
||||
import com.kamco.cd.kamcoback.common.enums.StatusType;
|
||||
import com.kamco.cd.kamcoback.common.utils.enums.Enums;
|
||||
import com.kamco.cd.kamcoback.common.utils.interfaces.EnumValid;
|
||||
import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.UUID;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
|
||||
public class MembersDto {
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class Basic {
|
||||
|
||||
private Long id;
|
||||
private UUID uuid;
|
||||
private String userRole;
|
||||
private String userRoleName;
|
||||
private String name;
|
||||
private String employeeNo;
|
||||
private String status;
|
||||
private String statusName;
|
||||
@JsonFormatDttm private ZonedDateTime createdDttm;
|
||||
@JsonFormatDttm private ZonedDateTime firstLoginDttm;
|
||||
@JsonFormatDttm private ZonedDateTime lastLoginDttm;
|
||||
@JsonFormatDttm private ZonedDateTime statusChgDttm;
|
||||
|
||||
public Basic(
|
||||
Long id,
|
||||
UUID uuid,
|
||||
String userRole,
|
||||
String name,
|
||||
String employeeNo,
|
||||
String status,
|
||||
ZonedDateTime createdDttm,
|
||||
ZonedDateTime firstLoginDttm,
|
||||
ZonedDateTime lastLoginDttm,
|
||||
ZonedDateTime statusChgDttm,
|
||||
Boolean pwdResetYn) {
|
||||
this.id = id;
|
||||
this.uuid = uuid;
|
||||
this.userRole = userRole;
|
||||
this.userRoleName = getUserRoleName(userRole);
|
||||
this.name = name;
|
||||
this.employeeNo = employeeNo;
|
||||
this.status = status;
|
||||
this.statusName = getStatusName(status, pwdResetYn);
|
||||
this.createdDttm = createdDttm;
|
||||
this.firstLoginDttm = firstLoginDttm;
|
||||
this.lastLoginDttm = lastLoginDttm;
|
||||
this.statusChgDttm = statusChgDttm;
|
||||
}
|
||||
|
||||
private String getUserRoleName(String roleId) {
|
||||
RoleType type = Enums.fromId(RoleType.class, roleId);
|
||||
return type.getText();
|
||||
}
|
||||
|
||||
private String getStatusName(String status, Boolean pwdResetYn) {
|
||||
StatusType type = Enums.fromId(StatusType.class, status);
|
||||
pwdResetYn = pwdResetYn != null && pwdResetYn;
|
||||
if (type.equals(StatusType.PENDING) && pwdResetYn) {
|
||||
type = StatusType.ACTIVE;
|
||||
}
|
||||
return type.getText();
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class SearchReq {
|
||||
|
||||
@Schema(description = "전체, 관리자(ADMIN), 라벨러(LABELER), 검수자(REVIEWER)", example = "")
|
||||
private String userRole;
|
||||
|
||||
@Schema(description = "키워드", example = "홍길동")
|
||||
private String keyword;
|
||||
|
||||
// 페이징 파라미터
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class AddReq {
|
||||
|
||||
@Schema(description = "관리자 유형", example = "ADMIN")
|
||||
@NotBlank
|
||||
@EnumValid(enumClass = RoleType.class, message = "userRole은 ADMIN, LABELER, REVIEWER 만 가능합니다.")
|
||||
private String userRole;
|
||||
|
||||
@Schema(description = "사번", example = "123456")
|
||||
@Size(max = 6)
|
||||
private String employeeNo;
|
||||
|
||||
@Schema(description = "이름", example = "홍길동")
|
||||
@NotBlank
|
||||
@Size(min = 2, max = 100)
|
||||
private String name;
|
||||
|
||||
@NotBlank
|
||||
@Schema(description = "패스워드", example = "")
|
||||
@Size(max = 255)
|
||||
private String password;
|
||||
|
||||
public AddReq(String userRole, String employeeNo, String name, String password) {
|
||||
this.userRole = userRole;
|
||||
this.employeeNo = employeeNo;
|
||||
this.name = name;
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class UpdateReq {
|
||||
|
||||
@Schema(description = "이름", example = "홍길동")
|
||||
@Size(min = 2, max = 100)
|
||||
private String name;
|
||||
|
||||
@Schema(description = "상태", example = "ACTIVE")
|
||||
@EnumValid(enumClass = StatusType.class, message = "status는 ACTIVE, INACTIVE, DELETED 만 가능합니다.")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "패스워드", example = "")
|
||||
@Size(max = 255)
|
||||
private String password;
|
||||
|
||||
public UpdateReq(String name, String status, String password) {
|
||||
this.name = name;
|
||||
this.status = status;
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class InitReq {
|
||||
|
||||
@Schema(description = "기존 패스워드", example = "")
|
||||
@Size(max = 255)
|
||||
@NotBlank
|
||||
private String oldPassword;
|
||||
|
||||
@Schema(description = "신규 패스워드", example = "")
|
||||
@Size(max = 255)
|
||||
@NotBlank
|
||||
private String newPassword;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class Member {
|
||||
|
||||
private Long id;
|
||||
private String name;
|
||||
private String employeeNo;
|
||||
private String role;
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class RoleEntity {
|
||||
|
||||
private final RoleType type;
|
||||
private final String name;
|
||||
|
||||
public RoleEntity(RoleType status) {
|
||||
this.type = status;
|
||||
this.name = status.getText();
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class StatusEntity {
|
||||
|
||||
private final StatusType type;
|
||||
private final String name;
|
||||
|
||||
public StatusEntity(StatusType status) {
|
||||
this.type = status;
|
||||
this.name = status.getText();
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class EntityData {
|
||||
|
||||
@JsonIgnore private Long id;
|
||||
private UUID uuid;
|
||||
private String name;
|
||||
private String employeeNo;
|
||||
private RoleEntity role;
|
||||
private StatusEntity status;
|
||||
@JsonFormatDttm private ZonedDateTime createdDttm;
|
||||
@JsonFormatDttm private ZonedDateTime firstLoginDttm;
|
||||
@JsonFormatDttm private ZonedDateTime lastLoginDttm;
|
||||
@JsonFormatDttm private ZonedDateTime statusChgDttm;
|
||||
|
||||
private Boolean isReset;
|
||||
|
||||
public EntityData(
|
||||
Long id,
|
||||
UUID uuid,
|
||||
RoleType role,
|
||||
String name,
|
||||
String employeeNo,
|
||||
StatusType status,
|
||||
ZonedDateTime createdDttm,
|
||||
ZonedDateTime firstLoginDttm,
|
||||
ZonedDateTime lastLoginDttm,
|
||||
ZonedDateTime statusChgDttm,
|
||||
Boolean isReset) {
|
||||
this.id = id;
|
||||
this.uuid = uuid;
|
||||
this.name = name;
|
||||
this.employeeNo = employeeNo;
|
||||
this.status = new StatusEntity(status);
|
||||
this.createdDttm = createdDttm;
|
||||
this.firstLoginDttm = firstLoginDttm;
|
||||
this.lastLoginDttm = lastLoginDttm;
|
||||
this.statusChgDttm = statusChgDttm;
|
||||
this.isReset = isReset;
|
||||
this.role = new RoleEntity(role);
|
||||
}
|
||||
|
||||
private String getUserRoleName(String roleId) {
|
||||
RoleType type = Enums.fromId(RoleType.class, roleId);
|
||||
return type.getText();
|
||||
}
|
||||
|
||||
private String getStatusName(String status, Boolean pwdResetYn) {
|
||||
StatusType type = Enums.fromId(StatusType.class, status);
|
||||
pwdResetYn = pwdResetYn != null && pwdResetYn;
|
||||
if (type.equals(StatusType.PENDING) && pwdResetYn) {
|
||||
type = StatusType.ACTIVE;
|
||||
}
|
||||
return type.getText();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.kamco.cd.kamcoback.members.exception;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class MemberException {
|
||||
|
||||
// *** Duplicate Member Exception ***
|
||||
@Getter
|
||||
public static class DuplicateMemberException extends RuntimeException {
|
||||
|
||||
public enum Field {
|
||||
USER_ID,
|
||||
EMPLOYEE_NO,
|
||||
DEFAULT
|
||||
}
|
||||
|
||||
private final Field field;
|
||||
private final String value;
|
||||
|
||||
public DuplicateMemberException(Field field, String value) {
|
||||
super(field.name() + " duplicate: " + value);
|
||||
this.field = field;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
// *** Member Not Found Exception ***
|
||||
public static class MemberNotFoundException extends RuntimeException {
|
||||
|
||||
public MemberNotFoundException() {
|
||||
super("Member not found");
|
||||
}
|
||||
|
||||
public MemberNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
public static class PasswordNotFoundException extends RuntimeException {
|
||||
|
||||
public PasswordNotFoundException() {
|
||||
super("Password not found");
|
||||
}
|
||||
|
||||
public PasswordNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.kamco.cd.kamcoback.postgres;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.MappedSuperclass;
|
||||
import jakarta.persistence.PrePersist;
|
||||
import java.time.ZonedDateTime;
|
||||
import lombok.Getter;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
|
||||
@Getter
|
||||
@MappedSuperclass
|
||||
public class CommonCreateEntity {
|
||||
|
||||
@CreatedDate
|
||||
@Column(name = "created_dttm", updatable = false, nullable = false)
|
||||
private ZonedDateTime createdDate;
|
||||
|
||||
@PrePersist
|
||||
protected void onPersist() {
|
||||
this.createdDate = ZonedDateTime.now();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.kamco.cd.kamcoback.postgres.entity;
|
||||
package com.kamco.cd.kamcoback.postgres;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.MappedSuperclass;
|
||||
@@ -1,15 +1,16 @@
|
||||
package com.kamco.cd.kamcoback.config;
|
||||
package com.kamco.cd.kamcoback.postgres;
|
||||
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.PersistenceContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Configuration
|
||||
public class QuerydslConfig {
|
||||
public class QueryDslConfig {
|
||||
|
||||
@PersistenceContext private EntityManager entityManager;
|
||||
private final EntityManager entityManager;
|
||||
|
||||
@Bean
|
||||
public JPAQueryFactory jpaQueryFactory() {
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.kamco.cd.kamcoback.postgres;
|
||||
|
||||
import com.querydsl.core.types.Order;
|
||||
import com.querydsl.core.types.OrderSpecifier;
|
||||
import com.querydsl.core.types.dsl.PathBuilder;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
|
||||
public class QuerydslOrderUtil {
|
||||
/**
|
||||
* Pageable의 Sort 정보를 QueryDSL OrderSpecifier 배열로 변환
|
||||
*
|
||||
* @param pageable Spring Pageable
|
||||
* @param entityClass 엔티티 클래스 (예: User.class)
|
||||
* @param alias Q 엔티티 alias (예: "user")
|
||||
*/
|
||||
public static <T> OrderSpecifier<?>[] getOrderSpecifiers(
|
||||
Pageable pageable, Class<T> entityClass, String alias) {
|
||||
PathBuilder<T> entityPath = new PathBuilder<>(entityClass, alias);
|
||||
|
||||
return pageable.getSort().stream()
|
||||
.map(
|
||||
sort -> {
|
||||
Order order = sort.isAscending() ? Order.ASC : Order.DESC;
|
||||
// PathBuilder.get()는 컬럼명(String)을 동적 Path로 반환
|
||||
return new OrderSpecifier<>(
|
||||
order, entityPath.get(sort.getProperty(), Comparable.class));
|
||||
})
|
||||
.toArray(OrderSpecifier[]::new);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
package com.kamco.cd.kamcoback.postgres.core;
|
||||
|
||||
import com.kamco.cd.kamcoback.dto.TrainingDataReviewJobDto.InspectorPendingDto;
|
||||
import com.kamco.cd.kamcoback.dto.TrainingDataReviewJobDto.Tasks;
|
||||
import com.kamco.cd.kamcoback.postgres.repository.scheduler.TrainingDataLabelJobRepository;
|
||||
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.InspectorPendingDto;
|
||||
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.Tasks;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -14,8 +15,8 @@ public class TrainingDataLabelJobCoreService {
|
||||
|
||||
private final TrainingDataLabelJobRepository trainingDataLabelJobRepository;
|
||||
|
||||
public List<Tasks> findCompletedYesterdayUnassigned() {
|
||||
return trainingDataLabelJobRepository.findCompletedYesterdayUnassigned();
|
||||
public List<Tasks> findCompletedYesterdayUnassigned(LocalDate baseDate) {
|
||||
return trainingDataLabelJobRepository.findCompletedYesterdayUnassigned(baseDate);
|
||||
}
|
||||
|
||||
public void assignReviewerBatch(List<UUID> assignmentUids, String reviewerId) {
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.kamco.cd.kamcoback.postgres.entity;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.time.ZonedDateTime;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hibernate.annotations.ColumnDefault;
|
||||
import org.locationtech.jts.geom.Geometry;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "inference_results_testing")
|
||||
public class InferenceResultsTestingEntity {
|
||||
|
||||
@Column(name = "probability")
|
||||
private Double probability;
|
||||
|
||||
@Column(name = "before_year")
|
||||
private Long beforeYear;
|
||||
|
||||
@Column(name = "after_year")
|
||||
private Long afterYear;
|
||||
|
||||
@Column(name = "map_id", length = Integer.MAX_VALUE)
|
||||
private String mapId;
|
||||
|
||||
@Column(name = "model_version", length = Integer.MAX_VALUE)
|
||||
private String modelVersion;
|
||||
|
||||
@Column(name = "cls_model_path", length = Integer.MAX_VALUE)
|
||||
private String clsModelPath;
|
||||
|
||||
@Column(name = "cls_model_version", length = Integer.MAX_VALUE)
|
||||
private String clsModelVersion;
|
||||
|
||||
@Column(name = "cd_model_type", length = Integer.MAX_VALUE)
|
||||
private String cdModelType;
|
||||
|
||||
@Column(name = "id")
|
||||
private Long id;
|
||||
|
||||
@Column(name = "model_name", length = Integer.MAX_VALUE)
|
||||
private String modelName;
|
||||
|
||||
@Column(name = "batch_id")
|
||||
private Long batchId;
|
||||
|
||||
@Column(name = "area")
|
||||
private Double area;
|
||||
|
||||
@Column(name = "before_c", length = Integer.MAX_VALUE)
|
||||
private String beforeC;
|
||||
|
||||
@Column(name = "before_p")
|
||||
private Double beforeP;
|
||||
|
||||
@Column(name = "after_c", length = Integer.MAX_VALUE)
|
||||
private String afterC;
|
||||
|
||||
@Column(name = "after_p")
|
||||
private Double afterP;
|
||||
|
||||
@Id
|
||||
@NotNull
|
||||
@ColumnDefault("nextval('inference_results_testing_seq_seq')")
|
||||
@Column(name = "seq", nullable = false)
|
||||
private Long seq;
|
||||
|
||||
@ColumnDefault("now()")
|
||||
@Column(name = "created_date")
|
||||
private ZonedDateTime createdDate;
|
||||
|
||||
@Size(max = 32)
|
||||
@NotNull
|
||||
@ColumnDefault("upper(replace((uuid_generate_v4()), '-', ''))")
|
||||
@Column(name = "uid", nullable = false, length = 32)
|
||||
private String uid;
|
||||
|
||||
@Column(name = "geometry", columnDefinition = "geometry")
|
||||
private Geometry geometry;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.kamco.cd.kamcoback.postgres.entity;
|
||||
|
||||
import com.kamco.cd.kamcoback.dto.LabelAllocateDto;
|
||||
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto;
|
||||
import com.kamco.cd.kamcoback.postgres.CommonDateEntity;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.kamco.cd.kamcoback.postgres.entity;
|
||||
|
||||
import com.kamco.cd.kamcoback.dto.LabelInspectorDto;
|
||||
import com.kamco.cd.kamcoback.label.dto.LabelInspectorDto;
|
||||
import com.kamco.cd.kamcoback.postgres.CommonDateEntity;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.kamco.cd.kamcoback.postgres.entity;
|
||||
|
||||
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.MapSheet;
|
||||
import com.kamco.cd.kamcoback.postgres.CommonDateEntity;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
@@ -45,4 +47,8 @@ public class MapInkx50kEntity extends CommonDateEntity {
|
||||
this.mapidNo = mapidNo;
|
||||
this.geom = geom;
|
||||
}
|
||||
|
||||
public MapSheet toEntity() {
|
||||
return new MapSheet(mapidcdNo, mapidNm);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package com.kamco.cd.kamcoback.postgres.entity;
|
||||
|
||||
import com.kamco.cd.kamcoback.enums.CommonUseStatus;
|
||||
import com.kamco.cd.kamcoback.common.enums.CommonUseStatus;
|
||||
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto;
|
||||
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.MapSheet;
|
||||
import com.kamco.cd.kamcoback.postgres.CommonDateEntity;
|
||||
import com.kamco.cd.kamcoback.scene.dto.MapInkxMngDto.MapListEntity;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
@@ -53,10 +55,6 @@ public class MapInkx5kEntity extends CommonDateEntity {
|
||||
@Enumerated(EnumType.STRING)
|
||||
private CommonUseStatus useInference;
|
||||
|
||||
public InferenceDetailDto.MapSheet toEntity() {
|
||||
return new MapSheet(mapidcdNo, mapidNm);
|
||||
}
|
||||
|
||||
// Constructor
|
||||
public MapInkx5kEntity(
|
||||
String mapidcdNo, String mapidNm, Geometry geom, MapInkx50kEntity mapInkx50k) {
|
||||
@@ -72,4 +70,18 @@ public class MapInkx5kEntity extends CommonDateEntity {
|
||||
public void updateUseInference(CommonUseStatus useInference) {
|
||||
this.useInference = useInference;
|
||||
}
|
||||
|
||||
public InferenceDetailDto.MapSheet toEntity() {
|
||||
return new MapSheet(mapidcdNo, mapidNm);
|
||||
}
|
||||
|
||||
public MapListEntity toDto() {
|
||||
return MapListEntity.builder()
|
||||
.scene5k(this.toEntity())
|
||||
.scene50k(this.mapInkx50k.toEntity())
|
||||
.useInference(useInference)
|
||||
.createdDttm(super.getCreatedDate())
|
||||
.updatedDttm(super.getModifiedDate())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@Column(name = "file_created_dttm")
|
||||
private ZonedDateTime fileCreatedDttm;
|
||||
// @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;
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.kamco.cd.kamcoback.postgres.entity;
|
||||
|
||||
import com.kamco.cd.kamcoback.inference.dto.DetectionClassification;
|
||||
import com.kamco.cd.kamcoback.common.enums.DetectionClassification;
|
||||
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto;
|
||||
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.Clazzes;
|
||||
import jakarta.persistence.Column;
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
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.SequenceGenerator;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hibernate.annotations.ColumnDefault;
|
||||
import org.hibernate.annotations.JdbcTypeCode;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "tb_map_sheet_anal_inference")
|
||||
public class MapSheetAnalInferenceEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(
|
||||
strategy = GenerationType.SEQUENCE,
|
||||
generator = "tb_map_sheet_anal_inference_id_gen")
|
||||
@SequenceGenerator(
|
||||
name = "tb_map_sheet_anal_inference_id_gen",
|
||||
sequenceName = "tb_map_sheet_anal_inference_uid",
|
||||
allocationSize = 1)
|
||||
@Column(name = "anal_uid", nullable = false)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "compare_yyyy")
|
||||
private Integer compareYyyy;
|
||||
|
||||
@Column(name = "target_yyyy")
|
||||
private Integer targetYyyy;
|
||||
|
||||
@Column(name = "model_uid")
|
||||
private Long modelUid;
|
||||
|
||||
@Size(max = 100)
|
||||
@Column(name = "server_ids", length = 100)
|
||||
private String serverIds;
|
||||
|
||||
@Column(name = "anal_strt_dttm")
|
||||
private ZonedDateTime analStrtDttm;
|
||||
|
||||
@Column(name = "anal_end_dttm")
|
||||
private ZonedDateTime analEndDttm;
|
||||
|
||||
@Column(name = "anal_sec")
|
||||
private Long analSec;
|
||||
|
||||
@Size(max = 20)
|
||||
@Column(name = "anal_state", length = 20)
|
||||
private String analState;
|
||||
|
||||
@Size(max = 20)
|
||||
@Column(name = "gukyuin_used", length = 20)
|
||||
private String gukyuinUsed;
|
||||
|
||||
@Column(name = "accuracy")
|
||||
private Double accuracy;
|
||||
|
||||
@Size(max = 255)
|
||||
@Column(name = "result_url")
|
||||
private String resultUrl;
|
||||
|
||||
@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;
|
||||
|
||||
@Size(max = 255)
|
||||
@Column(name = "anal_title")
|
||||
private String analTitle;
|
||||
|
||||
@Column(name = "detecting_cnt")
|
||||
private Long detectingCnt;
|
||||
|
||||
@Column(name = "anal_pred_sec")
|
||||
private Long analPredSec;
|
||||
|
||||
@Column(name = "model_ver_uid")
|
||||
private Long modelVerUid;
|
||||
|
||||
@Column(name = "hyper_params")
|
||||
@JdbcTypeCode(SqlTypes.JSON)
|
||||
private Map<String, Object> hyperParams;
|
||||
|
||||
@Column(name = "tranning_rate")
|
||||
private List<Double> tranningRate;
|
||||
|
||||
@Column(name = "validation_rate")
|
||||
private List<Double> validationRate;
|
||||
|
||||
@Column(name = "test_rate", length = Integer.MAX_VALUE)
|
||||
private String testRate;
|
||||
|
||||
@Size(max = 128)
|
||||
@Column(name = "detecting_description", length = 128)
|
||||
private String detectingDescription;
|
||||
|
||||
@Size(max = 12)
|
||||
@Column(name = "base_map_sheet_num", length = 12)
|
||||
private String baseMapSheetNum;
|
||||
|
||||
@ColumnDefault("gen_random_uuid()")
|
||||
@Column(name = "uuid")
|
||||
private UUID uuid;
|
||||
|
||||
@Size(max = 50)
|
||||
@Column(name = "model_m1_ver", length = 50)
|
||||
private String modelM1Ver;
|
||||
|
||||
@Size(max = 50)
|
||||
@Column(name = "model_m2_ver", length = 50)
|
||||
private String modelM2Ver;
|
||||
|
||||
@Size(max = 50)
|
||||
@Column(name = "model_m3_ver", length = 50)
|
||||
private String modelM3Ver;
|
||||
|
||||
@Size(max = 20)
|
||||
@Column(name = "anal_target_type", length = 20)
|
||||
private String analTargetType;
|
||||
|
||||
@Column(name = "gukyuin_apply_dttm")
|
||||
private ZonedDateTime gukyuinApplyDttm;
|
||||
|
||||
@Size(max = 20)
|
||||
@Column(name = "detection_data_option", length = 20)
|
||||
private String detectionDataOption;
|
||||
|
||||
@Column(name = "stage")
|
||||
private Integer stage;
|
||||
|
||||
@Size(max = 1)
|
||||
@ColumnDefault("'N'")
|
||||
@Column(name = "labeling_closed_yn", length = 1)
|
||||
private String labelingClosedYn = "N";
|
||||
|
||||
@Size(max = 1)
|
||||
@ColumnDefault("'N'")
|
||||
@Column(name = "inspection_closed_yn", length = 1)
|
||||
private String inspectionClosedYn = "N";
|
||||
|
||||
@Column(name = "learn_id")
|
||||
private Long learnId;
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
package com.kamco.cd.kamcoback.postgres.repository.scheduler;
|
||||
|
||||
import com.kamco.cd.kamcoback.dto.TrainingDataReviewJobDto.InspectorPendingDto;
|
||||
import com.kamco.cd.kamcoback.dto.TrainingDataReviewJobDto.Tasks;
|
||||
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.InspectorPendingDto;
|
||||
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.Tasks;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface TrainingDataLabelJobRepositoryCustom {
|
||||
|
||||
List<Tasks> findCompletedYesterdayUnassigned();
|
||||
List<Tasks> findCompletedYesterdayUnassigned(LocalDate baseDate);
|
||||
|
||||
List<InspectorPendingDto> findInspectorPendingByRound(Long analUid);
|
||||
|
||||
|
||||
@@ -4,16 +4,17 @@ import static com.kamco.cd.kamcoback.postgres.entity.QLabelingAssignmentEntity.l
|
||||
import static com.kamco.cd.kamcoback.postgres.entity.QLabelingInspectorEntity.labelingInspectorEntity;
|
||||
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity;
|
||||
|
||||
import com.kamco.cd.kamcoback.dto.LabelAllocateDto.InspectState;
|
||||
import com.kamco.cd.kamcoback.dto.LabelAllocateDto.LabelState;
|
||||
import com.kamco.cd.kamcoback.dto.TrainingDataReviewJobDto.InspectorPendingDto;
|
||||
import com.kamco.cd.kamcoback.dto.TrainingDataReviewJobDto.Tasks;
|
||||
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InspectState;
|
||||
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelState;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.LabelingAssignmentEntity;
|
||||
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.InspectorPendingDto;
|
||||
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.Tasks;
|
||||
import com.querydsl.core.types.Projections;
|
||||
import com.querydsl.core.types.dsl.BooleanExpression;
|
||||
import com.querydsl.core.types.dsl.Expressions;
|
||||
import com.querydsl.core.types.dsl.StringExpression;
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
@@ -34,16 +35,23 @@ public class TrainingDataLabelJobRepositoryImpl extends QuerydslRepositorySuppor
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Tasks> findCompletedYesterdayUnassigned() {
|
||||
public List<Tasks> findCompletedYesterdayUnassigned(LocalDate baseDate) {
|
||||
ZoneId zone = ZoneId.of("Asia/Seoul");
|
||||
ZonedDateTime todayStart = ZonedDateTime.now(zone).toLocalDate().atStartOfDay(zone);
|
||||
ZonedDateTime yesterdayStart = todayStart.minusDays(1);
|
||||
|
||||
// baseDate가 null이면 "어제"를 타겟으로, 아니면 baseDate
|
||||
LocalDate targetDate = (baseDate != null) ? baseDate : LocalDate.now(zone).minusDays(1);
|
||||
|
||||
// end: targetDate + 1일 00:00
|
||||
ZonedDateTime baseStart = targetDate.plusDays(1).atStartOfDay(zone);
|
||||
|
||||
// start: targetDate 00:00
|
||||
ZonedDateTime yesterdayStart = baseStart.minusDays(1);
|
||||
|
||||
BooleanExpression isYesterday =
|
||||
labelingAssignmentEntity
|
||||
.workStatDttm
|
||||
.goe(yesterdayStart)
|
||||
.and(labelingAssignmentEntity.workStatDttm.lt(todayStart));
|
||||
.and(labelingAssignmentEntity.workStatDttm.lt(baseStart));
|
||||
|
||||
return queryFactory
|
||||
.select(
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
package com.kamco.cd.kamcoback.scene.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.kamco.cd.kamcoback.common.enums.ApiConfigEnum.EnumDto;
|
||||
import com.kamco.cd.kamcoback.common.enums.CommonUseStatus;
|
||||
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
|
||||
public class MapInkxMngDto {
|
||||
|
||||
// CommonUseStatus class로 통합 20251230
|
||||
// @CodeExpose
|
||||
// @Getter
|
||||
// @AllArgsConstructor
|
||||
// public enum UseInferenceType implements EnumType {
|
||||
// USE("사용중"),
|
||||
// EXCEPT("영구 추론제외");
|
||||
//
|
||||
// private final String desc;
|
||||
//
|
||||
// @Override
|
||||
// public String getId() {
|
||||
// return name();
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public String getText() {
|
||||
// return desc;
|
||||
// }
|
||||
// }
|
||||
|
||||
@Schema(name = "Basic", description = "Basic")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class Basic {
|
||||
|
||||
private Integer fid;
|
||||
private String mapidcdNo;
|
||||
private String mapidNm;
|
||||
private JsonNode geom;
|
||||
private String useInference;
|
||||
private ZonedDateTime createdDttm;
|
||||
private ZonedDateTime updatedDttm;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Schema(name = "MapListEntity", description = "목록 항목")
|
||||
public static class MapListEntity {
|
||||
|
||||
private InferenceDetailDto.MapSheet scene50k;
|
||||
private InferenceDetailDto.MapSheet scene5k;
|
||||
private CommonUseStatus useInference;
|
||||
|
||||
@JsonFormat(
|
||||
shape = JsonFormat.Shape.STRING,
|
||||
pattern = "yyyy-MM-dd'T'HH:mm:ssXXX",
|
||||
timezone = "Asia/Seoul")
|
||||
private ZonedDateTime createdDttm;
|
||||
|
||||
@JsonFormat(
|
||||
shape = JsonFormat.Shape.STRING,
|
||||
pattern = "yyyy-MM-dd'T'HH:mm:ssXXX",
|
||||
timezone = "Asia/Seoul")
|
||||
private ZonedDateTime updatedDttm;
|
||||
|
||||
public EnumDto<CommonUseStatus> getUseInference() {
|
||||
EnumDto<CommonUseStatus> enumDto = useInference.getEnumDto();
|
||||
return enumDto;
|
||||
}
|
||||
|
||||
@Builder
|
||||
public MapListEntity(
|
||||
InferenceDetailDto.MapSheet scene50k,
|
||||
InferenceDetailDto.MapSheet scene5k,
|
||||
CommonUseStatus useInference,
|
||||
ZonedDateTime createdDttm,
|
||||
ZonedDateTime updatedDttm) {
|
||||
this.scene50k = scene50k;
|
||||
this.scene5k = scene5k;
|
||||
this.useInference = useInference;
|
||||
this.createdDttm = createdDttm;
|
||||
this.updatedDttm = updatedDttm;
|
||||
}
|
||||
}
|
||||
|
||||
@Schema(name = "MapList", description = "목록 항목")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public static class MapList {
|
||||
|
||||
private Integer rowNum;
|
||||
private String mapidcdNo5k;
|
||||
private String mapidcdNo50k;
|
||||
private String mapidNm;
|
||||
private String createdDttm;
|
||||
private String updatedDttm;
|
||||
private String useInference;
|
||||
private ZonedDateTime createdDttmTime;
|
||||
private ZonedDateTime updatedDttmTime;
|
||||
|
||||
// 목록 Querydsl 에서 리턴 받는 건 생성자 기준임 -> 쿼리 컬럼 그대로 받고 여기서 Java 형변환 해서 return 하기
|
||||
public MapList(
|
||||
Integer rowNum,
|
||||
String mapidcdNo5k,
|
||||
String mapidcdNo50k,
|
||||
String mapidNm,
|
||||
ZonedDateTime createdDttmTime,
|
||||
ZonedDateTime updatedDttmTime,
|
||||
CommonUseStatus useInference) {
|
||||
this.rowNum = rowNum;
|
||||
this.mapidcdNo5k = mapidcdNo5k;
|
||||
this.mapidcdNo50k = mapidcdNo50k;
|
||||
this.mapidNm = mapidNm;
|
||||
|
||||
DateTimeFormatter fmt =
|
||||
DateTimeFormatter.ofPattern("yyyy-MM-dd").withZone(ZoneId.of("Asia/Seoul"));
|
||||
this.createdDttm = fmt.format(createdDttmTime);
|
||||
this.updatedDttm = fmt.format(updatedDttmTime);
|
||||
this.createdDttmTime = createdDttmTime;
|
||||
this.updatedDttmTime = updatedDttmTime;
|
||||
this.useInference = useInference.getId();
|
||||
}
|
||||
}
|
||||
|
||||
@Schema(name = "searchReq", description = "검색 요청")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class searchReq {
|
||||
|
||||
// 페이징 파라미터
|
||||
private int page = 0;
|
||||
private int size = 20;
|
||||
private String sort;
|
||||
|
||||
public Pageable toPageable() {
|
||||
if (sort != null && !sort.isEmpty()) {
|
||||
String[] sortParams = sort.split(",");
|
||||
String property = sortParams[0];
|
||||
Sort.Direction direction =
|
||||
sortParams.length > 1 ? Sort.Direction.fromString(sortParams[1]) : Sort.Direction.ASC;
|
||||
return PageRequest.of(page, size, Sort.by(direction, property));
|
||||
}
|
||||
return PageRequest.of(page, size);
|
||||
}
|
||||
}
|
||||
|
||||
@Schema(name = "AddMapReq", description = "등록 요청")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class AddMapReq {
|
||||
|
||||
@Schema(description = "도엽번호", example = "31540687")
|
||||
private String mapidcdNo;
|
||||
|
||||
@Schema(description = "도엽명", example = "공덕")
|
||||
private String mapidNm;
|
||||
|
||||
@Schema(
|
||||
description = "좌표 목록 (한 줄에 한 점, '경도 위도' 형식)",
|
||||
example =
|
||||
"127.17500001632317 36.17499998262991\n"
|
||||
+ "127.14999995475043 36.17500002877932\n"
|
||||
+ "127.15000004313612 36.199999984012415\n"
|
||||
+ "127.1750000466954 36.20000001863179")
|
||||
private String coordinates;
|
||||
}
|
||||
|
||||
@Schema(name = "UseInferReq", description = "추론제외 업데이트 요청")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class UseInferReq {
|
||||
|
||||
private String mapidcdNo;
|
||||
private CommonUseStatus useInference; // 변경하고자하는 상태
|
||||
|
||||
public void valid() {
|
||||
if (mapidcdNo == null || mapidcdNo.isEmpty()) {
|
||||
throw new IllegalArgumentException("도엽번호는 필수 입력값입니다.");
|
||||
}
|
||||
// 공백제거
|
||||
mapidcdNo = mapidcdNo.trim();
|
||||
|
||||
if (!mapidcdNo.matches("^\\d{8}$")) {
|
||||
throw new EntityNotFoundException("도엽번호는 8자리 숫자로 구성되어야 합니다.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class Search5kReq {
|
||||
|
||||
private String mapidcdNo;
|
||||
private String useInference;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.kamco.cd.kamcoback.scheduler;
|
||||
|
||||
import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
|
||||
import com.kamco.cd.kamcoback.scheduler.service.TrainingDataLabelJobService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import java.time.LocalDate;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Tag(name = "스케줄링 수동 호출 테스트", description = "스케줄링 수동 호출 테스트 API")
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/api/schedule")
|
||||
public class SchedulerApiController {
|
||||
|
||||
private final TrainingDataLabelJobService trainingDataLabelJobService;
|
||||
|
||||
@Operation(
|
||||
summary = "라벨완료 -> 검수할당 스케줄링",
|
||||
description = "스케줄링이 실패한 경우 수동 호출하는 API, 어제 라벨링 완료된 것을 해당 검수자들에게 할당함")
|
||||
@GetMapping("/label-to-review")
|
||||
public ApiResponseDto<Void> runTrainingReviewSchedule(
|
||||
@RequestParam(required = false) LocalDate baseDate) {
|
||||
trainingDataLabelJobService.assignReviewerYesterdayLabelComplete(baseDate);
|
||||
return ApiResponseDto.ok(null);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.kamco.cd.kamcoback.dto;
|
||||
package com.kamco.cd.kamcoback.scheduler.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
@@ -6,7 +6,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.kamco.cd.kamcoback.dto.TrainingDataReviewJobDto.CompleteLabelData.GeoJsonFeature;
|
||||
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.CompleteLabelData.GeoJsonFeature;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import lombok.AllArgsConstructor;
|
||||
@@ -1,9 +1,10 @@
|
||||
package com.kamco.cd.kamcoback.scheduler.service;
|
||||
|
||||
import com.kamco.cd.kamcoback.dto.TrainingDataReviewJobDto.InspectorPendingDto;
|
||||
import com.kamco.cd.kamcoback.dto.TrainingDataReviewJobDto.Tasks;
|
||||
import com.kamco.cd.kamcoback.postgres.core.TrainingDataLabelJobCoreService;
|
||||
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.InspectorPendingDto;
|
||||
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.Tasks;
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
@@ -13,6 +14,7 @@ import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -22,6 +24,7 @@ import org.springframework.stereotype.Service;
|
||||
public class TrainingDataLabelJobService {
|
||||
|
||||
private final TrainingDataLabelJobCoreService trainingDataLabelJobCoreService;
|
||||
private final ApplicationContext applicationContext;
|
||||
|
||||
@Value("${spring.profiles.active}")
|
||||
private String profile;
|
||||
@@ -30,16 +33,24 @@ public class TrainingDataLabelJobService {
|
||||
return "local".equalsIgnoreCase(profile);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Scheduled(cron = "0 0 0 * * *")
|
||||
public void assignReviewerYesterdayLabelComplete() {
|
||||
@Scheduled(cron = "0 * * * * *")
|
||||
public void runTask() {
|
||||
// 프록시를 통해 호출해야 @Transactional이 적용됨
|
||||
applicationContext
|
||||
.getBean(TrainingDataLabelJobService.class)
|
||||
.assignReviewerYesterdayLabelComplete(null);
|
||||
}
|
||||
|
||||
if (isLocalProfile()) {
|
||||
return;
|
||||
}
|
||||
@Transactional
|
||||
public void assignReviewerYesterdayLabelComplete(LocalDate baseDate) {
|
||||
|
||||
// if (isLocalProfile()) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
try {
|
||||
List<Tasks> tasks = trainingDataLabelJobCoreService.findCompletedYesterdayUnassigned();
|
||||
List<Tasks> tasks =
|
||||
trainingDataLabelJobCoreService.findCompletedYesterdayUnassigned(baseDate);
|
||||
|
||||
if (tasks.isEmpty()) {
|
||||
return;
|
||||
@@ -88,6 +99,7 @@ public class TrainingDataLabelJobService {
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("배치 처리 중 예외", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
138
label/label-to-review/src/main/resources/application-dev.yml
Normal file
138
label/label-to-review/src/main/resources/application-dev.yml
Normal file
@@ -0,0 +1,138 @@
|
||||
spring:
|
||||
config:
|
||||
activate:
|
||||
on-profile: dev
|
||||
|
||||
jpa:
|
||||
show-sql: false
|
||||
hibernate:
|
||||
ddl-auto: validate
|
||||
properties:
|
||||
hibernate:
|
||||
default_batch_fetch_size: 100 # ✅ 성능 - N+1 쿼리 방지
|
||||
order_updates: true # ✅ 성능 - 업데이트 순서 정렬로 데드락 방지
|
||||
order_inserts: true
|
||||
use_sql_comments: true # ⚠️ 선택 - SQL에 주석 추가 (디버깅용)
|
||||
format_sql: true # ⚠️ 선택 - SQL 포맷팅 (가독성)
|
||||
jdbc:
|
||||
batch_size: 1000 # ✅ 추가 (JDBC batch)
|
||||
open-in-view: false
|
||||
mvc:
|
||||
async:
|
||||
request-timeout: 300s # 5분 (예: 30s, 120s, 10m 등도 가능)
|
||||
|
||||
datasource:
|
||||
url: jdbc:postgresql://192.168.2.127:15432/kamco_cds
|
||||
#url: jdbc:postgresql://localhost:15432/kamco_cds
|
||||
username: kamco_cds
|
||||
password: kamco_cds_Q!W@E#R$
|
||||
hikari:
|
||||
minimum-idle: 10
|
||||
maximum-pool-size: 20
|
||||
connection-timeout: 60000 # 60초 연결 타임아웃
|
||||
idle-timeout: 300000 # 5분 유휴 타임아웃
|
||||
max-lifetime: 1800000 # 30분 최대 수명
|
||||
leak-detection-threshold: 60000 # 연결 누수 감지
|
||||
|
||||
transaction:
|
||||
default-timeout: 300 # 5분 트랜잭션 타임아웃
|
||||
|
||||
data:
|
||||
redis:
|
||||
host: 192.168.2.109
|
||||
port: 6379
|
||||
password: kamco
|
||||
|
||||
servlet:
|
||||
multipart:
|
||||
enabled: true
|
||||
max-file-size: 4GB
|
||||
max-request-size: 4GB
|
||||
file-size-threshold: 10MB
|
||||
|
||||
server:
|
||||
tomcat:
|
||||
max-swallow-size: 4GB
|
||||
max-http-form-post-size: 4GB
|
||||
|
||||
jwt:
|
||||
secret: "kamco_token_9b71e778-19a3-4c1d-97bf-2d687de17d5b"
|
||||
access-token-validity-in-ms: 86400000 # 1일
|
||||
refresh-token-validity-in-ms: 604800000 # 7일
|
||||
#access-token-validity-in-ms: 60000 # 1분
|
||||
#refresh-token-validity-in-ms: 300000 # 5분
|
||||
|
||||
token:
|
||||
refresh-cookie-name: kamco-dev # 개발용 쿠키 이름
|
||||
refresh-cookie-secure: false # 로컬 http 테스트면 false
|
||||
|
||||
springdoc:
|
||||
swagger-ui:
|
||||
persist-authorization: true # 스웨거 새로고침해도 토큰 유지, 로컬스토리지에 저장
|
||||
|
||||
logging:
|
||||
level:
|
||||
root: INFO
|
||||
org.springframework.web: DEBUG
|
||||
org.springframework.security: DEBUG
|
||||
|
||||
# 헬스체크 노이즈 핵심만 다운
|
||||
org.springframework.security.web.FilterChainProxy: INFO
|
||||
org.springframework.security.web.authentication.AnonymousAuthenticationFilter: INFO
|
||||
org.springframework.security.web.authentication.Http403ForbiddenEntryPoint: INFO
|
||||
org.springframework.web.servlet.DispatcherServlet: INFO
|
||||
|
||||
|
||||
mapsheet:
|
||||
upload:
|
||||
skipGdalValidation: true
|
||||
shp:
|
||||
baseurl: /app/tmp/detect/result #현재사용안함
|
||||
|
||||
|
||||
|
||||
file:
|
||||
#sync-root-dir: D:/kamco-nfs/images/
|
||||
sync-root-dir: /kamco-nfs/images/
|
||||
sync-tmp-dir: /kamco-nfs/requests/temp # image upload temp dir
|
||||
#sync-tmp-dir: ${file.sync-root-dir}/tmp
|
||||
sync-file-extention: tfw,tif
|
||||
sync-auto-exception-start-year: 2024
|
||||
sync-auto-exception-before-year-cnt: 3
|
||||
|
||||
#dataset-dir: D:/kamco-nfs/model_output/
|
||||
dataset-dir: /kamco-nfs/model_output/export/ # 마운트경로 AI 추론결과
|
||||
dataset-tmp-dir: ${file.dataset-dir}tmp/
|
||||
|
||||
#model-dir: D:/kamco-nfs/ckpt/model/
|
||||
model-dir: /kamco-nfs/ckpt/model/ # 학습서버에서 트레이닝한 모델업로드경로
|
||||
model-tmp-dir: ${file.model-dir}tmp/
|
||||
model-file-extention: pth,json,py
|
||||
|
||||
pt-path: /kamco-nfs/ckpt/model/v6-cls-checkpoints/
|
||||
pt-FileName: yolov8_6th-6m.pt
|
||||
|
||||
dataset-response: /kamco-nfs/dataset/response/
|
||||
|
||||
inference:
|
||||
url: http://192.168.2.183:8000/jobs
|
||||
batch-url: http://192.168.2.183:8000/batches
|
||||
geojson-dir: /kamco-nfs/requests/ # 추론실행을 위한 파일생성경로
|
||||
jar-path: /kamco-nfs/repo/jar/shp-exporter.jar
|
||||
inference-server-name: server1,server2,server3,server4
|
||||
|
||||
gukyuin:
|
||||
#url: http://localhost:8080
|
||||
url: http://192.168.2.129:5301
|
||||
cdi: ${gukyuin.url}/api/kcd/cdi
|
||||
|
||||
training-data:
|
||||
geojson-dir: /kamco-nfs/dataset/request/
|
||||
|
||||
layer:
|
||||
geoserver-url: https://kamco.geo-dev.gs.dabeeo.com
|
||||
wms-path: geoserver/cd
|
||||
wmts-path: geoserver/cd/gwc/service
|
||||
workspace: cd
|
||||
|
||||
|
||||
122
label/label-to-review/src/main/resources/application-prod.yml
Normal file
122
label/label-to-review/src/main/resources/application-prod.yml
Normal file
@@ -0,0 +1,122 @@
|
||||
spring:
|
||||
config:
|
||||
activate:
|
||||
on-profile: prod
|
||||
|
||||
jpa:
|
||||
show-sql: true
|
||||
hibernate:
|
||||
ddl-auto: validate
|
||||
properties:
|
||||
hibernate:
|
||||
default_batch_fetch_size: 100 # ✅ 성능 - N+1 쿼리 방지
|
||||
order_updates: true # ✅ 성능 - 업데이트 순서 정렬로 데드락 방지
|
||||
order_inserts: true
|
||||
use_sql_comments: true # ⚠️ 선택 - SQL에 주석 추가 (디버깅용)
|
||||
format_sql: true # ⚠️ 선택 - SQL 포맷팅 (가독성)
|
||||
jdbc:
|
||||
batch_size: 1000 # ✅ 추가 (JDBC batch)
|
||||
open-in-view: false
|
||||
mvc:
|
||||
async:
|
||||
request-timeout: 300s # 5분 (예: 30s, 120s, 10m 등도 가능)
|
||||
|
||||
datasource:
|
||||
url: jdbc:postgresql://kamco-cd-postgis:5432/kamco_cds
|
||||
#url: jdbc:postgresql://localhost:15432/kamco_cds
|
||||
username: kamco_cds
|
||||
password: kamco_cds_Q!W@E#R$
|
||||
hikari:
|
||||
minimum-idle: 10
|
||||
maximum-pool-size: 20
|
||||
connection-timeout: 60000 # 60초 연결 타임아웃
|
||||
idle-timeout: 300000 # 5분 유휴 타임아웃
|
||||
max-lifetime: 1800000 # 30분 최대 수명
|
||||
leak-detection-threshold: 60000 # 연결 누수 감지
|
||||
|
||||
transaction:
|
||||
default-timeout: 300 # 5분 트랜잭션 타임아웃
|
||||
|
||||
data:
|
||||
redis:
|
||||
host: 127.0.0.1
|
||||
port: 16379
|
||||
password: kamco
|
||||
|
||||
servlet:
|
||||
multipart:
|
||||
enabled: true
|
||||
max-file-size: 4GB
|
||||
max-request-size: 4GB
|
||||
file-size-threshold: 10MB
|
||||
|
||||
server:
|
||||
tomcat:
|
||||
max-swallow-size: 4GB
|
||||
max-http-form-post-size: 4GB
|
||||
|
||||
jwt:
|
||||
secret: "kamco_token_9b71e778-19a3-4c1d-97bf-2d687de17d5b"
|
||||
access-token-validity-in-ms: 86400000 # 1일
|
||||
refresh-token-validity-in-ms: 604800000 # 7일
|
||||
|
||||
token:
|
||||
refresh-cookie-name: kamco # 개발용 쿠키 이름
|
||||
refresh-cookie-secure: true # 로컬 http 테스트면 false
|
||||
|
||||
logging:
|
||||
level:
|
||||
root: INFO
|
||||
org.springframework.web: DEBUG
|
||||
org.springframework.security: DEBUG
|
||||
|
||||
# 헬스체크 노이즈 핵심만 다운
|
||||
org.springframework.security.web.FilterChainProxy: INFO
|
||||
org.springframework.security.web.authentication.AnonymousAuthenticationFilter: INFO
|
||||
org.springframework.security.web.authentication.Http403ForbiddenEntryPoint: INFO
|
||||
org.springframework.web.servlet.DispatcherServlet: INFO
|
||||
|
||||
|
||||
mapsheet:
|
||||
upload:
|
||||
skipGdalValidation: true
|
||||
shp:
|
||||
baseurl: /app/detect/result #현재사용안함
|
||||
|
||||
file:
|
||||
sync-root-dir: /kamco-nfs/images/
|
||||
sync-tmp-dir: /kamco-nfs/repo/tmp # image upload temp dir
|
||||
sync-file-extention: tfw,tif
|
||||
|
||||
#dataset-dir: D:/kamco-nfs/model_output/ #변경 model_output
|
||||
dataset-dir: /kamco-nfs/model_output/export/ # 마운트경로 AI 추론결과
|
||||
dataset-tmp-dir: ${file.dataset-dir}tmp/
|
||||
|
||||
#model-dir: D:/kamco-nfs/ckpt/model/
|
||||
model-dir: /kamco-nfs/ckpt/model/ # 학습서버에서 트레이닝한 모델업로드경로
|
||||
model-tmp-dir: ${file.model-dir}tmp/
|
||||
model-file-extention: pth,json,py
|
||||
|
||||
pt-path: /kamco-nfs/ckpt/v6-cls-checkpoints/
|
||||
pt-FileName: yolov8_6th-6m.pt
|
||||
dataset-response: /kamco-nfs/dataset/response/
|
||||
|
||||
inference:
|
||||
url: http://127.0.0.1:8000/jobs
|
||||
batch-url: http://127.0.0.1:8000/batches
|
||||
geojson-dir: /kamco-nfs/requests/ # 학습서버에서 트레이닝한 모델업로드경로
|
||||
jar-path: /kamco-nfs/repo/jar/shp-exporter.jar # 추론실행을 위한 파일생성경로
|
||||
inference-server-name: server1,server2,server3,server4
|
||||
|
||||
gukyuin:
|
||||
url: http://127.0.0.1:5301
|
||||
cdi: ${gukyuin.url}/api/kcd/cdi
|
||||
|
||||
training-data:
|
||||
geojson-dir: /kamco-nfs/dataset/request/
|
||||
|
||||
layer:
|
||||
geoserver-url: https://kamco.geo-dev.gs.dabeeo.com
|
||||
wms-path: geoserver/cd
|
||||
wmts-path: geoserver/cd/gwc/service
|
||||
workspace: cd
|
||||
@@ -1,4 +1,69 @@
|
||||
server:
|
||||
port: 9080
|
||||
port: 8080
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: kamco-change-detection-api
|
||||
profiles:
|
||||
active: prod # 사용할 프로파일 지정 (ex. dev, prod, test)
|
||||
|
||||
datasource:
|
||||
driver-class-name: org.postgresql.Driver
|
||||
hikari:
|
||||
jdbc:
|
||||
time_zone: UTC
|
||||
batch_size: 50
|
||||
# 권장 설정
|
||||
minimum-idle: 2
|
||||
maximum-pool-size: 2
|
||||
connection-timeout: 20000
|
||||
idle-timeout: 300000
|
||||
max-lifetime: 1800000
|
||||
leak-detection-threshold: 60000
|
||||
|
||||
data:
|
||||
redis:
|
||||
host: localhost
|
||||
port: 6379
|
||||
password:
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: update # 테이블이 없으면 생성, 있으면 업데이트
|
||||
properties:
|
||||
hibernate:
|
||||
jdbc:
|
||||
batch_size: 50
|
||||
default_batch_fetch_size: 100
|
||||
logging:
|
||||
level:
|
||||
root: INFO
|
||||
org.springframework.web: DEBUG
|
||||
org.springframework.security: DEBUG
|
||||
|
||||
# 헬스체크 노이즈 핵심만 다운
|
||||
org.springframework.security.web.FilterChainProxy: INFO
|
||||
org.springframework.security.web.authentication.AnonymousAuthenticationFilter: INFO
|
||||
org.springframework.security.web.authentication.Http403ForbiddenEntryPoint: INFO
|
||||
org.springframework.web.servlet.DispatcherServlet: INFO
|
||||
# actuator
|
||||
management:
|
||||
health:
|
||||
readinessstate:
|
||||
enabled: true
|
||||
livenessstate:
|
||||
enabled: true
|
||||
endpoint:
|
||||
health:
|
||||
probes:
|
||||
enabled: true
|
||||
show-details: always
|
||||
endpoints:
|
||||
jmx:
|
||||
exposure:
|
||||
exclude: "*"
|
||||
web:
|
||||
base-path: /monitor
|
||||
exposure:
|
||||
include:
|
||||
- "health"
|
||||
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
server:
|
||||
port: 9080
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: label-to-review
|
||||
profiles:
|
||||
active: dev # 사용할 프로파일 지정 (ex. dev, prod, test)
|
||||
|
||||
datasource:
|
||||
url: jdbc:postgresql://192.168.2.127:15432/kamco_cds
|
||||
#url: jdbc:postgresql://localhost:5432/kamco_cds
|
||||
username: kamco_cds
|
||||
password: kamco_cds_Q!W@E#R$
|
||||
hikari:
|
||||
minimum-idle: 1
|
||||
maximum-pool-size: 5
|
||||
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: update # 테이블이 없으면 생성, 있으면 업데이트
|
||||
properties:
|
||||
hibernate:
|
||||
jdbc:
|
||||
batch_size: 50
|
||||
default_batch_fetch_size: 100
|
||||
logging:
|
||||
level:
|
||||
root: INFO
|
||||
org.springframework.web: DEBUG
|
||||
org.springframework.security: DEBUG
|
||||
|
||||
# 헬스체크 노이즈 핵심만 다운
|
||||
org.springframework.security.web.FilterChainProxy: INFO
|
||||
org.springframework.security.web.authentication.AnonymousAuthenticationFilter: INFO
|
||||
org.springframework.security.web.authentication.Http403ForbiddenEntryPoint: INFO
|
||||
org.springframework.web.servlet.DispatcherServlet: INFO
|
||||
# actuator
|
||||
management:
|
||||
health:
|
||||
readinessstate:
|
||||
enabled: true
|
||||
livenessstate:
|
||||
enabled: true
|
||||
endpoint:
|
||||
health:
|
||||
probes:
|
||||
enabled: true
|
||||
show-details: always
|
||||
endpoints:
|
||||
jmx:
|
||||
exposure:
|
||||
exclude: "*"
|
||||
web:
|
||||
base-path: /monitor
|
||||
exposure:
|
||||
include:
|
||||
- "health"
|
||||
|
||||
file:
|
||||
#sync-root-dir: D:/kamco-nfs/images/
|
||||
sync-root-dir: /kamco-nfs/images/
|
||||
sync-tmp-dir: ${file.sync-root-dir}/tmp
|
||||
sync-file-extention: tfw,tif
|
||||
sync-auto-exception-start-year: 2025
|
||||
sync-auto-exception-before-year-cnt: 3
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
server:
|
||||
port: 9080
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: imagery-make-dataset
|
||||
profiles:
|
||||
active: local # 사용할 프로파일 지정 (ex. dev, prod, test)
|
||||
|
||||
datasource:
|
||||
url: jdbc:postgresql://192.168.2.127:15432/kamco_cds
|
||||
#url: jdbc:postgresql://localhost:5432/kamco_cds
|
||||
username: kamco_cds
|
||||
password: kamco_cds_Q!W@E#R$
|
||||
hikari:
|
||||
minimum-idle: 1
|
||||
maximum-pool-size: 5
|
||||
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: update # 테이블이 없으면 생성, 있으면 업데이트
|
||||
properties:
|
||||
hibernate:
|
||||
jdbc:
|
||||
batch_size: 50
|
||||
default_batch_fetch_size: 100
|
||||
logging:
|
||||
level:
|
||||
root: INFO
|
||||
org.springframework.web: DEBUG
|
||||
org.springframework.security: DEBUG
|
||||
|
||||
# 헬스체크 노이즈 핵심만 다운
|
||||
org.springframework.security.web.FilterChainProxy: INFO
|
||||
org.springframework.security.web.authentication.AnonymousAuthenticationFilter: INFO
|
||||
org.springframework.security.web.authentication.Http403ForbiddenEntryPoint: INFO
|
||||
org.springframework.web.servlet.DispatcherServlet: INFO
|
||||
# actuator
|
||||
management:
|
||||
health:
|
||||
readinessstate:
|
||||
enabled: true
|
||||
livenessstate:
|
||||
enabled: true
|
||||
endpoint:
|
||||
health:
|
||||
probes:
|
||||
enabled: true
|
||||
show-details: always
|
||||
endpoints:
|
||||
jmx:
|
||||
exposure:
|
||||
exclude: "*"
|
||||
web:
|
||||
base-path: /monitor
|
||||
exposure:
|
||||
include:
|
||||
- "health"
|
||||
|
||||
file:
|
||||
#sync-root-dir: D:/kamco-nfs/images/
|
||||
sync-root-dir: /kamco-nfs/images/
|
||||
sync-tmp-dir: ${file.sync-root-dir}/tmp
|
||||
sync-file-extention: tfw,tif
|
||||
sync-auto-exception-start-year: 2025
|
||||
sync-auto-exception-before-year-cnt: 3
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
server:
|
||||
port: 9080
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: imagery-make-dataset
|
||||
profiles:
|
||||
active: prod # 사용할 프로파일 지정 (ex. dev, prod, test)
|
||||
|
||||
datasource:
|
||||
url: jdbc:postgresql://192.168.2.127:15432/kamco_cds
|
||||
#url: jdbc:postgresql://localhost:5432/kamco_cds
|
||||
username: kamco_cds
|
||||
password: kamco_cds_Q!W@E#R$
|
||||
hikari:
|
||||
minimum-idle: 1
|
||||
maximum-pool-size: 5
|
||||
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: update # 테이블이 없으면 생성, 있으면 업데이트
|
||||
properties:
|
||||
hibernate:
|
||||
jdbc:
|
||||
batch_size: 50
|
||||
default_batch_fetch_size: 100
|
||||
logging:
|
||||
level:
|
||||
root: INFO
|
||||
org.springframework.web: DEBUG
|
||||
org.springframework.security: DEBUG
|
||||
|
||||
# 헬스체크 노이즈 핵심만 다운
|
||||
org.springframework.security.web.FilterChainProxy: INFO
|
||||
org.springframework.security.web.authentication.AnonymousAuthenticationFilter: INFO
|
||||
org.springframework.security.web.authentication.Http403ForbiddenEntryPoint: INFO
|
||||
org.springframework.web.servlet.DispatcherServlet: INFO
|
||||
# actuator
|
||||
management:
|
||||
health:
|
||||
readinessstate:
|
||||
enabled: true
|
||||
livenessstate:
|
||||
enabled: true
|
||||
endpoint:
|
||||
health:
|
||||
probes:
|
||||
enabled: true
|
||||
show-details: always
|
||||
endpoints:
|
||||
jmx:
|
||||
exposure:
|
||||
exclude: "*"
|
||||
web:
|
||||
base-path: /monitor
|
||||
exposure:
|
||||
include:
|
||||
- "health"
|
||||
|
||||
file:
|
||||
#sync-root-dir: D:/kamco-nfs/images/
|
||||
sync-root-dir: /kamco-nfs/images/
|
||||
sync-tmp-dir: ${file.sync-root-dir}/tmp
|
||||
sync-file-extention: tfw,tif
|
||||
sync-auto-exception-start-year: 2025
|
||||
sync-auto-exception-before-year-cnt: 3
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
<!doctype html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>라벨 ZIP 다운로드</title>
|
||||
</head>
|
||||
<body>
|
||||
<h3>라벨 ZIP 다운로드</h3>
|
||||
|
||||
UUID:
|
||||
<input id="uuid" value="6d8d49dc-0c9d-4124-adc7-b9ca610cc394" />
|
||||
<br><br>
|
||||
|
||||
JWT Token:
|
||||
<input id="token" style="width:600px;" placeholder="Bearer 토큰 붙여넣기" />
|
||||
<br><br>
|
||||
|
||||
<button onclick="download()">다운로드</button>
|
||||
|
||||
<br><br>
|
||||
<progress id="bar" value="0" max="100" style="width:400px;"></progress>
|
||||
<div id="status"></div>
|
||||
|
||||
<script>
|
||||
async function download() {
|
||||
const uuid = document.getElementById("uuid").value.trim();
|
||||
const token = document.getElementById("token").value.trim();
|
||||
|
||||
if (!uuid) {
|
||||
alert("UUID 입력하세요");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
alert("토큰 입력하세요");
|
||||
return;
|
||||
}
|
||||
|
||||
const url = `/api/training-data/stage/download/${uuid}`;
|
||||
|
||||
const res = await fetch(url, {
|
||||
headers: {
|
||||
"Authorization": token.startsWith("Bearer ")
|
||||
? token
|
||||
: `Bearer ${token}`,
|
||||
"kamco-download-uuid": uuid
|
||||
}
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
document.getElementById("status").innerText =
|
||||
"실패: " + res.status;
|
||||
return;
|
||||
}
|
||||
|
||||
const total = parseInt(res.headers.get("Content-Length") || "0", 10);
|
||||
const reader = res.body.getReader();
|
||||
const chunks = [];
|
||||
let received = 0;
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
chunks.push(value);
|
||||
received += value.length;
|
||||
|
||||
if (total) {
|
||||
document.getElementById("bar").value =
|
||||
(received / total) * 100;
|
||||
}
|
||||
}
|
||||
|
||||
const blob = new Blob(chunks);
|
||||
const a = document.createElement("a");
|
||||
a.href = URL.createObjectURL(blob);
|
||||
a.download = uuid + ".zip";
|
||||
a.click();
|
||||
|
||||
document.getElementById("status").innerText = "완료 ✅";
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user