diff --git a/src/main/java/com/kamco/cd/kamcoback/common/enums/CrsType.java b/src/main/java/com/kamco/cd/kamcoback/common/enums/CrsType.java new file mode 100644 index 00000000..42fffdfc --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/common/enums/CrsType.java @@ -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; + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/common/service/ExternalJarRunner.java b/src/main/java/com/kamco/cd/kamcoback/common/service/ExternalJarRunner.java index ea436e50..6739b6de 100644 --- a/src/main/java/com/kamco/cd/kamcoback/common/service/ExternalJarRunner.java +++ b/src/main/java/com/kamco/cd/kamcoback/common/service/ExternalJarRunner.java @@ -54,8 +54,8 @@ public class ExternalJarRunner { public void run(String jarPath, String register, String layer) { List args = new ArrayList<>(); - addArg(args, "register", register); - addArg(args, "layer", layer); + addArg(args, "upload-shp", register); + // addArg(args, "layer", layer); execJar(jarPath, args); } diff --git a/src/main/java/com/kamco/cd/kamcoback/common/utils/FIleChecker.java b/src/main/java/com/kamco/cd/kamcoback/common/utils/FIleChecker.java index 89656629..ddee1fe9 100644 --- a/src/main/java/com/kamco/cd/kamcoback/common/utils/FIleChecker.java +++ b/src/main/java/com/kamco/cd/kamcoback/common/utils/FIleChecker.java @@ -30,12 +30,14 @@ import java.util.stream.Stream; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import lombok.Getter; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.FilenameUtils; import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.gce.geotiff.GeoTiffReader; import org.springframework.util.FileSystemUtils; import org.springframework.web.multipart.MultipartFile; +@Slf4j public class FIleChecker { static SimpleDateFormat dttmFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @@ -138,7 +140,9 @@ public class FIleChecker { // null을 넣으면 전체 영역을 읽지 않고 메타데이터 위주로 체크하여 빠름 GridCoverage2D coverage = reader.read(null); - if (coverage == null) return false; + if (coverage == null) { + return false; + } // 3. GIS 필수 정보(좌표계)가 있는지 확인 // if (coverage.getCoordinateReferenceSystem() == null) { @@ -152,7 +156,9 @@ public class FIleChecker { return false; } finally { // 리소스 해제 (필수) - if (reader != null) reader.dispose(); + if (reader != null) { + reader.dispose(); + } } } @@ -296,7 +302,8 @@ public class FIleChecker { boolean isValid = !NameValidator.containsKorean(folderNm) - && !NameValidator.containsWhitespaceRegex(folderNm); + && !NameValidator.containsWhitespaceRegex(folderNm) + && !parentFolderNm.equals("kamco-nfs"); File file = new File(fullPath); int childCnt = getChildFolderCount(file); @@ -586,7 +593,9 @@ public class FIleChecker { } public static boolean checkExtensions(String fileName, String ext) { - if (fileName == null) return false; + if (fileName == null) { + return false; + } if (!fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase().equals(ext)) { return false; @@ -690,6 +699,7 @@ public class FIleChecker { @Schema(name = "Folder", description = "폴더 정보") @Getter public static class Folder { + private final String folderNm; private final String parentFolderNm; private final String parentPath; diff --git a/src/main/java/com/kamco/cd/kamcoback/common/utils/NetUtils.java b/src/main/java/com/kamco/cd/kamcoback/common/utils/NetUtils.java index ce74d280..9c6ee783 100644 --- a/src/main/java/com/kamco/cd/kamcoback/common/utils/NetUtils.java +++ b/src/main/java/com/kamco/cd/kamcoback/common/utils/NetUtils.java @@ -5,11 +5,9 @@ import java.net.InetAddress; import java.net.URLEncoder; import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; -import java.util.List; import java.util.Map; import java.util.stream.Collectors; import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; public class NetUtils { @@ -56,9 +54,8 @@ public class NetUtils { public HttpHeaders jsonHeaders() { HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - headers.setAccept(List.of(MediaType.APPLICATION_JSON)); - + headers.set(HttpHeaders.ACCEPT, "application/json;charset=UTF-8"); + headers.set(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8"); return headers; } } diff --git a/src/main/java/com/kamco/cd/kamcoback/config/GlobalExceptionHandler.java b/src/main/java/com/kamco/cd/kamcoback/config/GlobalExceptionHandler.java index 85907d1a..cc9a2b2b 100644 --- a/src/main/java/com/kamco/cd/kamcoback/config/GlobalExceptionHandler.java +++ b/src/main/java/com/kamco/cd/kamcoback/config/GlobalExceptionHandler.java @@ -465,8 +465,7 @@ public class GlobalExceptionHandler { String stackTraceStr = Arrays.stream(stackTrace) .map(StackTraceElement::toString) - .collect(Collectors.joining("\n")) - .substring(0, 255); + .collect(Collectors.joining("\n")); String actionType = HeaderUtil.get(request, "kamco-action-type"); diff --git a/src/main/java/com/kamco/cd/kamcoback/config/SecurityConfig.java b/src/main/java/com/kamco/cd/kamcoback/config/SecurityConfig.java index d89330c5..c0093364 100644 --- a/src/main/java/com/kamco/cd/kamcoback/config/SecurityConfig.java +++ b/src/main/java/com/kamco/cd/kamcoback/config/SecurityConfig.java @@ -85,7 +85,8 @@ public class SecurityConfig { "/api/model/file-chunk-upload", "/api/upload/file-chunk-upload", "/api/upload/chunk-upload-complete", - "/api/change-detection/**") + "/api/change-detection/**", + "/api/layer/map/**") .permitAll() // 로그인한 사용자만 가능 IAM .requestMatchers( diff --git a/src/main/java/com/kamco/cd/kamcoback/config/resttemplate/ExternalHttpClient.java b/src/main/java/com/kamco/cd/kamcoback/config/resttemplate/ExternalHttpClient.java index 013e01b5..e4e656d2 100644 --- a/src/main/java/com/kamco/cd/kamcoback/config/resttemplate/ExternalHttpClient.java +++ b/src/main/java/com/kamco/cd/kamcoback/config/resttemplate/ExternalHttpClient.java @@ -1,72 +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.HttpClientErrorException; -import org.springframework.web.client.HttpServerErrorException; +import org.springframework.web.client.HttpStatusCodeException; import org.springframework.web.client.RestTemplate; +@RequiredArgsConstructor @Component @Log4j2 -@RequiredArgsConstructor public class ExternalHttpClient { private final RestTemplate restTemplate; + private final ObjectMapper objectMapper; public ExternalCallResult call( String url, HttpMethod method, Object body, HttpHeaders headers, Class responseType) { - HttpEntity entity = new HttpEntity<>(body, headers); + // responseType 기반으로 Accept 동적 세팅 + HttpHeaders resolvedHeaders = resolveHeaders(headers, responseType); + logRequestBody(body); - // 요청 로그 - log.info("[HTTP-REQ] {} {}", method, url); - if (body != null) { - log.debug("[HTTP-REQ-BODY] {}", body); - } + HttpEntity entity = new HttpEntity<>(body, resolvedHeaders); try { + // String: raw bytes -> UTF-8 string + if (responseType == String.class) { + ResponseEntity 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 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 res = restTemplate.exchange(url, method, entity, responseType); + return new ExternalCallResult<>(res.getStatusCodeValue(), true, res.getBody(), null); - int code = res.getStatusCodeValue(); - - // 응답 로그 - log.info("[HTTP-RES] {} {} -> {}", method, url, code); - log.debug("[HTTP-RES-BODY] {}", res.getBody()); - - return new ExternalCallResult<>(code, code >= 200 && code < 300, res.getBody()); - - } catch (HttpClientErrorException.NotFound e) { - log.info("[HTTP-RES] {} {} -> 404 (Not Found)", method, url); - log.debug("[HTTP-RES-BODY] {}", e.getResponseBodyAsString()); - - return new ExternalCallResult<>(404, false, null); - - } catch (HttpClientErrorException e) { - // 기타 4xx - log.warn( - "[HTTP-ERR] {} {} -> {} body={}", - method, - url, - e.getStatusCode().value(), - e.getResponseBodyAsString()); - throw e; - - } catch (HttpServerErrorException e) { - // 5xx - log.error( - "[HTTP-ERR] {} {} -> {} body={}", - method, - url, - e.getStatusCode().value(), - e.getResponseBodyAsString()); - throw e; + } catch (HttpStatusCodeException e) { + return new ExternalCallResult<>( + e.getStatusCode().value(), false, null, e.getResponseBodyAsString()); } } - public record ExternalCallResult(int statusCode, boolean success, T body) {} + // 기존 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(int statusCode, boolean success, T body, String errBody) {} } diff --git a/src/main/java/com/kamco/cd/kamcoback/config/resttemplate/RestTemplateConfig.java b/src/main/java/com/kamco/cd/kamcoback/config/resttemplate/RestTemplateConfig.java index 5b1bb394..7a4ba07e 100644 --- a/src/main/java/com/kamco/cd/kamcoback/config/resttemplate/RestTemplateConfig.java +++ b/src/main/java/com/kamco/cd/kamcoback/config/resttemplate/RestTemplateConfig.java @@ -4,6 +4,7 @@ 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; @@ -13,10 +14,20 @@ public class RestTemplateConfig { @Bean public RestTemplate restTemplate(RestTemplateBuilder builder) { - SimpleClientHttpRequestFactory f = new SimpleClientHttpRequestFactory(); - f.setConnectTimeout(2000); - f.setReadTimeout(3000); + SimpleClientHttpRequestFactory baseFactory = new SimpleClientHttpRequestFactory(); + baseFactory.setConnectTimeout(2000); + baseFactory.setReadTimeout(3000); - return builder.requestFactory(() -> f).additionalInterceptors(new RetryInterceptor()).build(); + 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; } } diff --git a/src/main/java/com/kamco/cd/kamcoback/config/resttemplate/RetryInterceptor.java b/src/main/java/com/kamco/cd/kamcoback/config/resttemplate/RetryInterceptor.java index e8e74ba7..ab80e9b4 100644 --- a/src/main/java/com/kamco/cd/kamcoback/config/resttemplate/RetryInterceptor.java +++ b/src/main/java/com/kamco/cd/kamcoback/config/resttemplate/RetryInterceptor.java @@ -1,12 +1,15 @@ 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; @@ -20,21 +23,25 @@ public class RetryInterceptor implements ClientHttpRequestInterceptor { for (int attempt = 1; attempt <= MAX_RETRY; attempt++) { try { - // HTTP 응답을 받으면(2xx/4xx/5xx 포함) 그대로 반환 - return execution.execute(request, body); + 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) { - // 네트워크/타임아웃 등 I/O 예외만 재시도 lastException = e; + log.error("[WIRE-IO-ERR] attempt={} msg={}", attempt, e.getMessage(), e); } - // 마지막 시도가 아니면 대기 if (attempt < MAX_RETRY) { sleep(); } } - // 마지막 예외를 그대로 던져서 원인이 로그에 남게 함 throw lastException; } diff --git a/src/main/java/com/kamco/cd/kamcoback/gukyuin/GukYuinApiController.java b/src/main/java/com/kamco/cd/kamcoback/gukyuin/GukYuinApiController.java index 2217b939..19f0783f 100644 --- a/src/main/java/com/kamco/cd/kamcoback/gukyuin/GukYuinApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/gukyuin/GukYuinApiController.java @@ -1,10 +1,12 @@ package com.kamco.cd.kamcoback.gukyuin; import com.kamco.cd.kamcoback.config.api.ApiResponseDto; +import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ResponseObj; import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectContDto; import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto; import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.ChnDetectMastReqDto; import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.ChngDetectMastSearchDto; +import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.LabelSendDto; import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.ResReturn; import com.kamco.cd.kamcoback.gukyuin.dto.DetectMastDto.Basic; import com.kamco.cd.kamcoback.gukyuin.dto.DetectMastDto.DetectMastReq; @@ -18,6 +20,8 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import java.time.LocalDate; +import java.util.List; import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; @@ -51,7 +55,7 @@ public class GukYuinApiController { @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) }) @PostMapping("/chn/mast/regist") - public ApiResponseDto regist( + public ApiResponseDto regist( @RequestBody @Valid ChngDetectMastDto.ChnDetectMastReqDto chnDetectMastReq) { return ApiResponseDto.ok(gukYuinApiService.regist(chnDetectMastReq)); } @@ -133,9 +137,9 @@ public class GukYuinApiController { @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) }) - public ApiResponseDto selectChangeDetectionList( + public ApiResponseDto selectChangeDetectionDetail( @PathVariable String chnDtctMstId) { - return ApiResponseDto.ok(gukYuinApiService.list(chnDtctMstId)); + return ApiResponseDto.ok(gukYuinApiService.detail(chnDtctMstId)); } @Operation(summary = "국유in연동 가능여부 확인", description = "국유in연동 가능여부 확인") @@ -239,4 +243,20 @@ public class GukYuinApiController { @PathVariable String chnDtctObjtId, @PathVariable String lblYn) { return ApiResponseDto.ok(gukYuinApiService.updateChnDtctObjtLabelingYn(chnDtctObjtId, lblYn)); } + + @Operation(summary = "국유in연동 등록", description = "국유in연동 등록") + @PostMapping("/mast/reg/{uuid}") + public ApiResponseDto connectChnMastRegist( + @Parameter(description = "uuid", example = "7a593d0e-76a8-4b50-8978-9af1fbe871af") + @PathVariable + UUID uuid) { + return ApiResponseDto.ok(gukYuinApiService.connectChnMastRegist(uuid)); + } + + @Operation(summary = "라벨 전송 완료 리스트", description = "라벨 전송 완료 리스트") + @GetMapping("/label/send-list") + public ApiResponseDto> findLabelingCompleteSendList( + @Parameter(description = "어제 날짜", example = "2026-01-29") LocalDate yesterday) { + return ApiResponseDto.ok(gukYuinApiService.findLabelingCompleteSendList(yesterday)); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/gukyuin/dto/ChngDetectMastDto.java b/src/main/java/com/kamco/cd/kamcoback/gukyuin/dto/ChngDetectMastDto.java index 35d1febe..a124ac66 100644 --- a/src/main/java/com/kamco/cd/kamcoback/gukyuin/dto/ChngDetectMastDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/gukyuin/dto/ChngDetectMastDto.java @@ -1,6 +1,7 @@ package com.kamco.cd.kamcoback.gukyuin.dto; import io.swagger.v3.oas.annotations.media.Schema; +import java.time.ZonedDateTime; import java.util.List; import lombok.AllArgsConstructor; import lombok.Getter; @@ -66,13 +67,30 @@ public class ChngDetectMastDto { @AllArgsConstructor public static class ChnDetectMastReqDto { - private String cprsYr; // 비교년도 2023 - private String crtrYr; // 기준년도 2024 - private String chnDtctSno; // 차수 (1 | 2 | ...) - private String chnDtctId; // 탐지아이디. UUID를 기반으로 '-'를 제거하고 대문자/숫자로 구성 - private String pathNm; // 탐지결과 절대경로명 /kamco_nas/export/{chnDtctId} - private String reqEpno; // 사원번호 - private String reqIp; // 사원아이피 + @Schema(description = "비교년도", example = "2023") + private String cprsYr; + + @Schema(description = "기준년도", example = "2024") + private String crtrYr; + + @Schema(description = "차수", example = "1") + private String chnDtctSno; + + @Schema( + description = "탐지아이디, UUID를 기반으로 '-'를 제거하고 대문자/숫자로 구성", + example = "D5F192EC76D34F6592035BE63A84F591") + private String chnDtctId; + + @Schema( + description = "탐지결과 절대경로명 /kamco_nas/export/{chnDtctId}", + example = "/kamco-nfs/dataset/export/D5F192EC76D34F6592035BE63A84F591") + private String pathNm; + + @Schema(description = "사원번호", example = "123456") + private String reqEpno; + + @Schema(description = "사원아이피", example = "127.0.0.1") + private String reqIp; } @Getter @@ -158,4 +176,44 @@ public class ChngDetectMastDto { private List result; private Boolean success; } + + @Schema(name = "RegistResDto", description = "reg 등록 후 리턴 형태") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class RegistResDto { + + private Integer code; + private String message; + private Basic result; + private Boolean success; + } + + @Schema(name = "LearnKeyDto", description = "learn 엔티티 key 정보") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class LearnKeyDto { + + private Long id; + private String uid; + private String chnDtctMstId; + } + + @Schema(name = "LabelSendDto", description = "라벨링 전송한 목록") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class LabelSendDto { + + private String chnDtctObjtId; + private String labelerId; + private ZonedDateTime labelerWorkDttm; + private String reviewerId; + private ZonedDateTime reviewerWorkDttm; + private ZonedDateTime labelSendDttm; + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/gukyuin/dto/GukYuinDto.java b/src/main/java/com/kamco/cd/kamcoback/gukyuin/dto/GukYuinDto.java index d236bfb4..f196184b 100644 --- a/src/main/java/com/kamco/cd/kamcoback/gukyuin/dto/GukYuinDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/gukyuin/dto/GukYuinDto.java @@ -1,21 +1,13 @@ package com.kamco.cd.kamcoback.gukyuin.dto; import com.kamco.cd.kamcoback.common.utils.enums.EnumType; +import java.util.UUID; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; public class GukYuinDto { - @Getter - @Setter - public static class GukYuinLinkableRes { - - private boolean linkable; - // private GukYuinLinkFailCode code; - private String message; - } - /** 실패 코드 enum */ @Getter @AllArgsConstructor @@ -39,10 +31,42 @@ public class GukYuinDto { } } + @Getter + @Setter + public static class GukYuinLinkableRes { + + private boolean linkable; + // private GukYuinLinkFailCode code; + private String message; + } + // Repository가 반환할 Fact(조회 결과) public record GukYuinLinkFacts( boolean existsLearn, boolean isPartScope, boolean hasRunningInference, boolean hasOtherUnfinishedGukYuin) {} + + @Getter + @Setter + @AllArgsConstructor + public static class LearnInfo { + + private Long id; + private UUID uuid; + private Integer compareYyyy; + private Integer targetYyyy; + private Integer stage; + private String uid; + private String applyStatus; + } + + @Getter + @Setter + @AllArgsConstructor + public static class GeomUidDto { + + private Long geoUid; + private String resultUid; + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/gukyuin/dto/GukYuinStatus.java b/src/main/java/com/kamco/cd/kamcoback/gukyuin/dto/GukYuinStatus.java index 174e9714..e6472c04 100644 --- a/src/main/java/com/kamco/cd/kamcoback/gukyuin/dto/GukYuinStatus.java +++ b/src/main/java/com/kamco/cd/kamcoback/gukyuin/dto/GukYuinStatus.java @@ -11,6 +11,7 @@ public enum GukYuinStatus implements EnumType { IN_PROGRESS("진행중"), GUK_COMPLETED("국유인 매핑 완료"), PNU_COMPLETED("PNU 싱크 완료"), + PNU_FAILED("PNU 싱크 중 에러"), CANCELED("취소"); private final String desc; diff --git a/src/main/java/com/kamco/cd/kamcoback/gukyuin/service/GukYuinApiService.java b/src/main/java/com/kamco/cd/kamcoback/gukyuin/service/GukYuinApiService.java index 82fec220..7214279b 100644 --- a/src/main/java/com/kamco/cd/kamcoback/gukyuin/service/GukYuinApiService.java +++ b/src/main/java/com/kamco/cd/kamcoback/gukyuin/service/GukYuinApiService.java @@ -1,6 +1,10 @@ package com.kamco.cd.kamcoback.gukyuin.service; import com.kamco.cd.kamcoback.common.utils.NetUtils; +import com.kamco.cd.kamcoback.common.utils.UserUtil; +import com.kamco.cd.kamcoback.config.api.ApiLogFunction; +import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ApiResponseCode; +import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ResponseObj; import com.kamco.cd.kamcoback.config.resttemplate.ExternalHttpClient; import com.kamco.cd.kamcoback.config.resttemplate.ExternalHttpClient.ExternalCallResult; import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectContDto; @@ -8,22 +12,33 @@ import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectContDto.ContBasic; import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectContDto.ResultContDto; import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectContDto.ResultPnuDto; import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto; +import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.ChnDetectMastReqDto; +import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.LabelSendDto; import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.ResReturn; import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.ResultDto; import com.kamco.cd.kamcoback.gukyuin.dto.GukYuinDto.GukYuinLinkFacts; import com.kamco.cd.kamcoback.gukyuin.dto.GukYuinDto.GukYuinLinkFailCode; import com.kamco.cd.kamcoback.gukyuin.dto.GukYuinDto.GukYuinLinkableRes; +import com.kamco.cd.kamcoback.gukyuin.dto.GukYuinDto.LearnInfo; +import com.kamco.cd.kamcoback.log.dto.EventStatus; +import com.kamco.cd.kamcoback.log.dto.EventType; import com.kamco.cd.kamcoback.postgres.core.GukYuinCoreService; +import com.kamco.cd.kamcoback.postgres.entity.AuditLogEntity; +import com.kamco.cd.kamcoback.postgres.repository.log.AuditLogRepository; +import java.time.LocalDate; import java.util.List; import java.util.UUID; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpMethod; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; +@Slf4j @Service -@Transactional(readOnly = true) +@Transactional @RequiredArgsConstructor public class GukYuinApiService { @@ -31,6 +46,9 @@ public class GukYuinApiService { private final ExternalHttpClient externalHttpClient; private final NetUtils netUtils = new NetUtils(); + private final UserUtil userUtil; + private final AuditLogRepository auditLogRepository; + @Value("${spring.profiles.active:local}") private String profile; @@ -41,25 +59,42 @@ public class GukYuinApiService { private String gukyuinCdiUrl; @Transactional - public ChngDetectMastDto.Basic regist(ChngDetectMastDto.ChnDetectMastReqDto chnDetectMastReq) { + public ChngDetectMastDto.RegistResDto regist( + ChngDetectMastDto.ChnDetectMastReqDto chnDetectMastReq) { String url = gukyuinCdiUrl + "/chn/mast/regist"; String myip = netUtils.getLocalIP(); chnDetectMastReq.setReqIp(myip); - chnDetectMastReq.setReqEpno("1234567"); // TODO + chnDetectMastReq.setReqEpno(userUtil.getEmployeeNo()); - ExternalCallResult result = + ExternalCallResult result = externalHttpClient.call( url, HttpMethod.POST, chnDetectMastReq, netUtils.jsonHeaders(), - ChngDetectMastDto.Basic.class); + ChngDetectMastDto.RegistResDto.class); - ChngDetectMastDto.Basic resultBody = result.body(); - gukyuinCoreService.updateGukYuinMastRegResult(resultBody); + ChngDetectMastDto.RegistResDto resultBody = result.body(); + Boolean success = false; + if (resultBody != null) { + ChngDetectMastDto.Basic registRes = resultBody.getResult(); + // 추론 회차에 applyStatus, applyStatusDttm 업데이트 + gukyuinCoreService.updateGukYuinMastRegResult(registRes); + // anal_inference 에도 국유인 반영여부, applyDttm 업데이트 + gukyuinCoreService.updateAnalInferenceApplyDttm(registRes); + success = resultBody.getSuccess(); + } + + this.insertGukyuinAuditLog( + EventType.ADDED.getId(), + myip, + userUtil.getId(), + url.replace(gukyuinUrl, ""), + chnDetectMastReq, + success); return resultBody; } @@ -69,7 +104,7 @@ public class GukYuinApiService { String myip = netUtils.getLocalIP(); chnDetectMastReq.setReqIp(myip); - chnDetectMastReq.setReqEpno("1234567"); // TODO + chnDetectMastReq.setReqEpno(userUtil.getEmployeeNo()); ExternalCallResult result = externalHttpClient.call( @@ -82,11 +117,18 @@ public class GukYuinApiService { ChngDetectMastDto.Basic resultBody = result.body(); gukyuinCoreService.updateGukYuinMastRegRemove(resultBody); + this.insertGukyuinAuditLog( + EventType.REMOVE.getId(), + myip, + userUtil.getId(), + url.replace(gukyuinUrl, ""), + chnDetectMastReq, + true); // TODO : successFail 여부 return new ResReturn("success", "탐지결과 삭제 되었습니다."); } // 등록목록 1개 확인 - public ChngDetectMastDto.ResultDto list(String chnDtctMstId) { + public ChngDetectMastDto.ResultDto detail(String chnDtctMstId) { String url = gukyuinCdiUrl + "/chn/mast/list/" + chnDtctMstId; @@ -94,6 +136,13 @@ public class GukYuinApiService { externalHttpClient.call( url, HttpMethod.GET, null, netUtils.jsonHeaders(), ChngDetectMastDto.ResultDto.class); + this.insertGukyuinAuditLog( + EventType.DETAIL.getId(), + netUtils.getLocalIP(), + userUtil.getId(), + url.replace(gukyuinUrl, ""), + null, + result.body().getSuccess()); return result.body(); } @@ -108,6 +157,13 @@ public class GukYuinApiService { externalHttpClient.call( url, HttpMethod.GET, null, netUtils.jsonHeaders(), ChngDetectMastDto.ResultDto.class); + this.insertGukyuinAuditLog( + EventType.LIST.getId(), + netUtils.getLocalIP(), + userUtil.getId(), + url.replace(gukyuinUrl, ""), + null, + result.body().getSuccess()); return result.body(); } @@ -188,6 +244,14 @@ public class GukYuinApiService { } } + this.insertGukyuinAuditLog( + EventType.LIST.getId(), + netUtils.getLocalIP(), + userUtil.getId(), + url.replace(gukyuinUrl, ""), + null, + result.body().getSuccess()); + return result.body(); } @@ -202,6 +266,14 @@ public class GukYuinApiService { netUtils.jsonHeaders(), ChngDetectContDto.ResultPnuDto.class); + this.insertGukyuinAuditLog( + EventType.DETAIL.getId(), + netUtils.getLocalIP(), + userUtil.getId(), + url.replace(gukyuinUrl, ""), + null, + result.body().getSuccess()); + return result.body(); } @@ -218,6 +290,14 @@ public class GukYuinApiService { ChngDetectContDto.ResultPnuDto dto = result.body(); + this.insertGukyuinAuditLog( + EventType.MODIFIED.getId(), + netUtils.getLocalIP(), + userUtil.getId(), + url.replace(gukyuinUrl, ""), + null, + result.body().getSuccess()); + return new ResReturn(dto.getCode() > 200000 ? "fail" : "success", dto.getMessage()); } @@ -232,6 +312,13 @@ public class GukYuinApiService { netUtils.jsonHeaders(), ChngDetectContDto.ResultContDto.class); + this.insertGukyuinAuditLog( + EventType.LIST.getId(), + netUtils.getLocalIP(), + userUtil.getId(), + url.replace(gukyuinUrl, ""), + null, + result.body().getSuccess()); return result.body(); } @@ -242,6 +329,74 @@ public class GukYuinApiService { externalHttpClient.call( url, HttpMethod.GET, null, netUtils.jsonHeaders(), ChngDetectMastDto.ResultDto.class); + this.insertGukyuinAuditLog( + EventType.DETAIL.getId(), + netUtils.getLocalIP(), + userUtil.getId(), + url.replace(gukyuinUrl, ""), + null, + result.body().getSuccess()); + return result.body(); } + + @Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = false) + public void insertGukyuinAuditLog( + String actionType, + String myIp, + Long userUid, + String requestUri, + Object requestBody, + boolean successFail) { + try { + AuditLogEntity log = + new AuditLogEntity( + userUid, + EventType.fromName(actionType), + successFail ? EventStatus.SUCCESS : EventStatus.FAILED, + "GUKYUIN", // 메뉴도 국유인으로 하나 따기 + myIp, + requestUri, + requestBody == null ? null : ApiLogFunction.cutRequestBody(requestBody.toString()), + null, + null, + null); + auditLogRepository.save(log); + + } catch (Exception e) { + log.error(e.getMessage()); + throw e; + } + } + + public ResponseObj connectChnMastRegist(UUID uuid) { + // uuid로 추론 회차 조회 + LearnInfo info = gukyuinCoreService.findMapSheetLearnInfo(uuid); + // if (info.getApplyStatus() != null && + // !info.getApplyStatus().equals(GukYuinStatus.PENDING.getId())) + // { + // return new ResponseObj(ApiResponseCode.DUPLICATE_DATA, "이미 국유인 연동을 한 추론 회차입니다."); + // } + + // 비교년도,기준년도로 전송한 데이터 있는지 확인 후 회차 번호 생성 + Integer maxStage = + gukyuinCoreService.findMapSheetLearnYearStage(info.getCompareYyyy(), info.getTargetYyyy()); + + // reqDto 셋팅 + ChnDetectMastReqDto reqDto = new ChnDetectMastReqDto(); + reqDto.setCprsYr(String.valueOf(info.getCompareYyyy())); + reqDto.setCrtrYr(String.valueOf(info.getTargetYyyy())); + reqDto.setChnDtctSno(String.valueOf(maxStage + 1)); + reqDto.setChnDtctId(info.getUid()); + reqDto.setPathNm("/kamco-nfs/dataset/export/" + info.getUid()); + + // 국유인 /chn/mast/regist 전송 + this.regist(reqDto); + + return new ResponseObj(ApiResponseCode.OK, ""); + } + + public List findLabelingCompleteSendList(LocalDate yesterday) { + return gukyuinCoreService.findLabelingCompleteSendList(yesterday); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/inference/InferenceResultShpApiController.java b/src/main/java/com/kamco/cd/kamcoback/inference/InferenceResultShpApiController.java index c4571f40..4821941c 100644 --- a/src/main/java/com/kamco/cd/kamcoback/inference/InferenceResultShpApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/inference/InferenceResultShpApiController.java @@ -1,5 +1,7 @@ package com.kamco.cd.kamcoback.inference; +import com.kamco.cd.kamcoback.common.exception.CustomApiException; +import com.kamco.cd.kamcoback.common.geometry.GeoJsonFileWriter.Scene; import com.kamco.cd.kamcoback.config.api.ApiResponseDto; import com.kamco.cd.kamcoback.inference.dto.InferenceResultShpDto; import com.kamco.cd.kamcoback.inference.service.InferenceResultShpService; @@ -10,20 +12,28 @@ import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; +import java.util.Map; import java.util.UUID; import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @Tag(name = "추론결과 데이터 생성", description = "추론결과 데이터 생성 API") +@Log4j2 @RestController @RequiredArgsConstructor @RequestMapping("/api/inference/shp") public class InferenceResultShpApiController { private final InferenceResultShpService inferenceResultShpService; + public static final String MAP_ID = + "{ \"mapIds\": [\"37716096\",\"37716095\",\"37716094\",\"37716091\",\"37716086\",\"37716085\",\"37716084\",\"37716083\",\"37716076\",\"37716066\",\"37716065\",\"37716064\",\"37716063\",\"37716061\",\"37716051\",\"37716011\"] }"; @Operation(summary = "추론결과 데이터 저장", description = "추론결과 데이터 저장") @ApiResponses( @@ -52,4 +62,29 @@ public class InferenceResultShpApiController { inferenceResultShpService.createShp(uuid); return ApiResponseDto.createOK(null); } + + @Operation(summary = "추론실행에 필요한 geojson 파일 생성", description = "추론실행에 필요한 geojson 파일 생성") + @PostMapping("/geojson/{yyyy}/{mapSheetScope}/{detectOption}") + public ApiResponseDto createGeojson( + @Schema(description = "년도") @PathVariable String yyyy, + @Schema(description = "전체(ALL),부분(PART)", example = "PART") @PathVariable + String mapSheetScope, + @Schema(description = "추론제외(EXCL),이전 년도 도엽 사용(PREV)", example = "EXCL") @PathVariable + String detectOption, + @Schema(description = "5k도엽번호", example = MAP_ID) @RequestBody Map body) { + + Object raw = body.get("mapIds"); + + if (raw == null) { + throw new CustomApiException("BAD_REQUEST", HttpStatus.BAD_REQUEST); + } + + @SuppressWarnings("unchecked") + List mapIds = (List) raw; + + Scene scene = + inferenceResultShpService.createGeojson(yyyy, mapSheetScope, detectOption, mapIds); + + return ApiResponseDto.createOK(scene); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceResultShpService.java b/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceResultShpService.java index 3e5cfff2..fc41a727 100644 --- a/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceResultShpService.java +++ b/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceResultShpService.java @@ -1,15 +1,20 @@ package com.kamco.cd.kamcoback.inference.service; +import com.kamco.cd.kamcoback.common.geometry.GeoJsonFileWriter.Scene; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.InferenceLearnDto; import com.kamco.cd.kamcoback.inference.dto.InferenceResultShpDto; import com.kamco.cd.kamcoback.postgres.core.InferenceResultCoreService; import com.kamco.cd.kamcoback.postgres.core.InferenceResultShpCoreService; +import com.kamco.cd.kamcoback.postgres.core.MapSheetMngCoreService; import com.kamco.cd.kamcoback.scheduler.service.ShpPipelineService; +import java.util.List; import java.util.Objects; import java.util.UUID; import java.util.stream.Collectors; import java.util.stream.Stream; import lombok.RequiredArgsConstructor; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -19,9 +24,11 @@ import org.springframework.transaction.annotation.Transactional; @Transactional(readOnly = true) public class InferenceResultShpService { + private static final Logger log = LogManager.getLogger(InferenceResultShpService.class); private final InferenceResultShpCoreService coreService; private final InferenceResultCoreService inferenceResultCoreService; private final ShpPipelineService shpPipelineService; + private final MapSheetMngCoreService mapSheetMngCoreService; @Value("${mapsheet.shp.baseurl}") private String baseDir; @@ -59,4 +66,21 @@ public class InferenceResultShpService { // shp 파일 비동기 생성 shpPipelineService.runPipeline(jarPath, datasetDir, batchId, dto.getUid()); } + + /** + * 추론 실행전 geojson 파일 생성 + * + * @param yyyy + * @param mapSheetScope + * @param detectOption + * @param mapIds + * @return + */ + public Scene createGeojson( + String yyyy, String mapSheetScope, String detectOption, List mapIds) { + Scene getSceneInference = + mapSheetMngCoreService.getSceneInference(yyyy, mapIds, mapSheetScope, detectOption); + log.info("getSceneInference: {}", getSceneInference); + return getSceneInference; + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/layer/LayerApiController.java b/src/main/java/com/kamco/cd/kamcoback/layer/LayerApiController.java index e407e92d..5e06afd7 100644 --- a/src/main/java/com/kamco/cd/kamcoback/layer/LayerApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/layer/LayerApiController.java @@ -2,10 +2,13 @@ package com.kamco.cd.kamcoback.layer; import com.kamco.cd.kamcoback.config.api.ApiResponseDto; import com.kamco.cd.kamcoback.layer.dto.LayerDto; +import com.kamco.cd.kamcoback.layer.dto.LayerDto.IsMapYn; +import com.kamco.cd.kamcoback.layer.dto.LayerDto.LayerMapDto; import com.kamco.cd.kamcoback.layer.dto.LayerDto.OrderReq; import com.kamco.cd.kamcoback.layer.dto.LayerDto.SearchReq; import com.kamco.cd.kamcoback.layer.service.LayerService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; @@ -76,10 +79,24 @@ public class LayerApiController { }) @PostMapping("/save/{layerType}") public ApiResponseDto save( - @PathVariable String layerType, @RequestBody LayerDto.AddReq dto) { + @Schema(description = "TILE,GEOJSON,WMS,WMTS", example = "GEOJSON") @PathVariable + String layerType, + @RequestBody LayerDto.AddReq dto) { return ApiResponseDto.ok(layerService.saveLayers(layerType, dto)); } + @Operation(summary = "순서 변경", description = "순서 변경 api") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "201", + description = "등록 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Void.class))), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) @PutMapping("/order") public ApiResponseDto updateOrder(@RequestBody List dto) { layerService.orderUpdate(dto); @@ -152,6 +169,24 @@ public class LayerApiController { return ApiResponseDto.ok(null); } + @Operation(summary = "맵 노출여부 수정", description = "맵 노출여부 수정 api") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "201", + description = "수정 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Void.class))), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @PutMapping("/update-map/{uuid}") + public ApiResponseDto updateIsMap(@PathVariable UUID uuid, @RequestBody IsMapYn isMapYn) { + layerService.updateIsMap(uuid, isMapYn); + return ApiResponseDto.ok(null); + } + @Operation(summary = "wmts tile 조회", description = "wmts tile 조회 api") @ApiResponses( value = { @@ -187,4 +222,55 @@ public class LayerApiController { public ApiResponseDto> getWmsTile() { return ApiResponseDto.ok(layerService.getWmsTitle()); } + + @Operation(summary = "변화지도 레이어 조회", description = "변화지도 레이어 조회") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "검색 성공", + content = + @Content( + mediaType = "application/json", + array = @ArraySchema(schema = @Schema(implementation = String.class)))), + @ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @GetMapping("/map/change-detection") + public ApiResponseDto> changeDetectionMap() { + return ApiResponseDto.ok(layerService.findLayerMapList("change-detection")); + } + + @Operation(summary = "라벨링 툴 레이어 조회", description = "라벨링 툴 레이어 조회") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "검색 성공", + content = + @Content( + mediaType = "application/json", + array = @ArraySchema(schema = @Schema(implementation = String.class)))), + @ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @GetMapping("/map/labeling") + public ApiResponseDto> labelingMap() { + return ApiResponseDto.ok(layerService.findLayerMapList("labeling")); + } + + @Operation(summary = "년도별 tile Url(before,after 모두 조회)", description = "년도별 tile Url") + @GetMapping("/tile-url") + public ApiResponseDto getChangeDetectionTileUrl( + @Parameter(description = "이전 년도", example = "2023") @RequestParam Integer beforeYear, + @Parameter(description = "이후 년도", example = "2024") @RequestParam Integer afterYear) { + return ApiResponseDto.ok(layerService.getChangeDetectionTileUrl(beforeYear, afterYear)); + } + + @Operation(summary = "년도별 tile Url(년도 1개만 조회)", description = "년도별 tile Url") + @GetMapping("/tile-url-year") + public ApiResponseDto getChangeDetectionTileOneYearUrl( + @Parameter(description = "년도", example = "2023") @RequestParam Integer year) { + return ApiResponseDto.ok(layerService.getChangeDetectionTileOneYearUrl(year)); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/layer/dto/LayerDto.java b/src/main/java/com/kamco/cd/kamcoback/layer/dto/LayerDto.java index 77c9012b..9b5fe5be 100644 --- a/src/main/java/com/kamco/cd/kamcoback/layer/dto/LayerDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/layer/dto/LayerDto.java @@ -1,5 +1,10 @@ package com.kamco.cd.kamcoback.layer.dto; +import com.fasterxml.jackson.annotation.JsonIgnore; +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.interfaces.JsonFormatDttm; import io.swagger.v3.oas.annotations.media.Schema; import java.math.BigDecimal; @@ -12,11 +17,17 @@ import lombok.Setter; public class LayerDto { + public enum MapType { + CHANGE_MAP, + LABELING_MAP + } + @Getter @Setter @AllArgsConstructor @Schema(name = "LayerBasic") public static class Basic { + @Schema(description = "uuid") private UUID uuid; @@ -48,6 +59,7 @@ public class LayerDto { @AllArgsConstructor @Schema(name = "LayerDetail") public static class Detail { + @Schema(description = "uuid") private UUID uuid; @@ -96,6 +108,9 @@ public class LayerDto { @JsonFormatDttm @Schema(description = "등록일시") private ZonedDateTime createdDttm; + + @Schema(description = "좌표계") + private String crs; } @Getter @@ -104,7 +119,7 @@ public class LayerDto { @Schema(name = "LayerAddReq") public static class AddReq { - @Schema(description = "title") + @Schema(description = "title WMS, WMTS 선택한 tile") private String title; @Schema(description = "설명") @@ -133,6 +148,9 @@ public class LayerDto { @Schema(description = "zoom max", example = "18") private Short max; + + @Schema(description = "좌표계", example = "EPSG_3857") + private String crs; } @Getter @@ -153,6 +171,7 @@ public class LayerDto { @AllArgsConstructor @NoArgsConstructor public static class SearchReq { + private String tag; private String layerType; } @@ -162,6 +181,7 @@ public class LayerDto { @AllArgsConstructor @NoArgsConstructor public static class TileAddReqDto { + @Schema(description = "설명", example = "배경지도 입니다.") private String description; @@ -189,4 +209,224 @@ public class LayerDto { @Schema(description = "zoom max", example = "18") private Short max; } + + @Getter + @Setter + @Schema(name = "LayerMapDto") + public static class LayerMapDto { + + @Schema(example = "WMTS", description = "유형 (TILE/GEOJSON/WMTS/WMS)") + private String layerType; + + @Schema(description = "title") + private String title; + + @Schema(description = "설명") + private String description; + + @Schema(description = "태그") + private String tag; + + @Schema(description = "순서") + private Long sortOrder; + + @Schema(description = "url") + private String url; + + @Schema(description = "좌측상단 경도", example = "126.0") + private BigDecimal minLon; + + @Schema(description = "좌측상단 위도", example = "34.0") + private BigDecimal minLat; + + @Schema(description = "우측하단 경도", example = "130.0") + private BigDecimal maxLon; + + @Schema(description = "우측하단 위도", example = "38.5") + private BigDecimal maxLat; + + @Schema(description = "zoom min", example = "5") + private Short minZoom; + + @Schema(description = "zoom max", example = "18") + private Short maxZoom; + + @Schema(description = "bbox") + private JsonNode bbox; + + @JsonIgnore private String bboxGeometry; + + @Schema(description = "uuid") + private UUID uuid; + + @JsonIgnore private String rawJsonString; + + @Schema(description = "rawJson") + private JsonNode rawJson; + + @Schema(description = "crs") + private String crs; + + public LayerMapDto( + String layerType, + String tag, + Long sortOrder, + String url, + BigDecimal minLon, + BigDecimal minLat, + BigDecimal maxLon, + BigDecimal maxLat, + Short minZoom, + Short maxZoom, + String bboxGeometry, + UUID uuid, + String rawJsonString, + String crs) { + this.layerType = layerType; + this.tag = tag; + this.sortOrder = sortOrder; + this.url = url; + this.minLon = minLon; + this.minLat = minLat; + this.maxLon = maxLon; + this.maxLat = maxLat; + this.minZoom = minZoom; + this.maxZoom = maxZoom; + this.bboxGeometry = bboxGeometry; + this.uuid = uuid; + this.rawJsonString = rawJsonString; + + JsonNode geoJson = null; + JsonNode rawJson = null; + ObjectMapper mapper = new ObjectMapper(); + if (bboxGeometry != null) { + try { + geoJson = mapper.readTree(bboxGeometry); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + if (rawJsonString != null) { + try { + rawJson = mapper.readTree(rawJsonString); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + this.rawJson = rawJson; + this.bbox = geoJson; + this.crs = crs; + } + + @JsonProperty("workspace") + public String getWorkSpace() { + return "cd"; + } + } + + @Schema(name = "TileUrlDto", description = "Tile Url 정보") + @Getter + @Setter + @NoArgsConstructor + public static class TileUrlDto { + + @Schema(description = "mngYyyy") + private Integer mngYyyy; + + @Schema(description = "url") + private String url; + + @Schema(description = "태그") + private String tag; + + @Schema(description = "좌측상단 경도", example = "126.0") + private BigDecimal minLon; + + @Schema(description = "좌측상단 위도", example = "34.0") + private BigDecimal minLat; + + @Schema(description = "우측하단 경도", example = "130.0") + private BigDecimal maxLon; + + @Schema(description = "우측하단 위도", example = "38.5") + private BigDecimal maxLat; + + @Schema(description = "zoom min", example = "5") + private Short minZoom; + + @Schema(description = "zoom max", example = "18") + private Short maxZoom; + + @Schema(description = "bbox") + private JsonNode bbox; + + @JsonIgnore private String bboxGeometry; + + private String crs; + + public TileUrlDto( + Integer mngYyyy, + String url, + String tag, + BigDecimal minLon, + BigDecimal minLat, + BigDecimal maxLon, + BigDecimal maxLat, + Short minZoom, + Short maxZoom, + String bboxGeometry, + String crs) { + this.mngYyyy = mngYyyy; + this.url = url; + this.tag = tag; + this.minLon = minLon; + this.minLat = minLat; + this.maxLon = maxLon; + this.maxLat = maxLat; + this.minZoom = minZoom; + this.maxZoom = maxZoom; + this.bboxGeometry = bboxGeometry; + + JsonNode geoJson = null; + + if (bboxGeometry != null) { + ObjectMapper mapper = new ObjectMapper(); + try { + geoJson = mapper.readTree(bboxGeometry); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + this.bbox = geoJson; + this.crs = crs; + } + } + + @Schema(name = "TileUrlDto", description = "Tile Url 정보") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class YearTileDto { + + private TileUrlDto before; + private TileUrlDto after; + } + + @Schema(name = "맵 노출 여부", description = "맵 노출 여부") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class IsMapYn { + + @Schema(description = "CHANGE_MAP(변화지도), LABELING_MAP(라벨링지도)", example = "CHANGE_MAP") + private String mapType; + + @Schema(description = "노출여부 true, false", example = "true") + private Boolean isMapYn; + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/layer/dto/WmsLayerInfo.java b/src/main/java/com/kamco/cd/kamcoback/layer/dto/WmsLayerInfo.java index 5fbf0647..03fa3a44 100755 --- a/src/main/java/com/kamco/cd/kamcoback/layer/dto/WmsLayerInfo.java +++ b/src/main/java/com/kamco/cd/kamcoback/layer/dto/WmsLayerInfo.java @@ -2,171 +2,46 @@ package com.kamco.cd.kamcoback.layer.dto; import java.util.ArrayList; import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; /** WMS 레이어 정보를 담는 DTO 클래스 */ +@Data +@NoArgsConstructor +@AllArgsConstructor public class WmsLayerInfo { + private String name; private String title; private String abstractText; - private List keywords; + + private List keywords = new ArrayList<>(); private BoundingBox boundingBox; - private List crs; // 지원하는 좌표계 목록 - @Override - public String toString() { - return "WmsLayerInfo{" - + "name='" - + name - + '\'' - + ", title='" - + title - + '\'' - + ", abstractText='" - + abstractText - + '\'' - + ", keywords=" - + keywords - + ", boundingBox=" - + boundingBox - + ", crs=" - + crs - + '}'; - } + /** 지원하는 좌표계 목록 */ + private List crs = new ArrayList<>(); - public WmsLayerInfo() { - this.keywords = new ArrayList<>(); - this.crs = new ArrayList<>(); - } - - // Getters and Setters - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getAbstractText() { - return abstractText; - } - - public void setAbstractText(String abstractText) { - this.abstractText = abstractText; - } - - public List getKeywords() { - return keywords; - } - - public void setKeywords(List keywords) { - this.keywords = keywords; - } + /* ===== convenience methods ===== */ public void addKeyword(String keyword) { this.keywords.add(keyword); } - public BoundingBox getBoundingBox() { - return boundingBox; - } - - public void setBoundingBox(BoundingBox boundingBox) { - this.boundingBox = boundingBox; - } - - public List getCrs() { - return crs; - } - - public void setCrs(List crs) { - this.crs = crs; - } - public void addCrs(String crsValue) { this.crs.add(crsValue); } /** BoundingBox 정보를 담는 내부 클래스 */ + @Data + @NoArgsConstructor + @AllArgsConstructor public static class BoundingBox { + private String crs; private double minX; private double minY; private double maxX; private double maxY; - - public BoundingBox(String crs, double minX, double minY, double maxX, double maxY) { - this.crs = crs; - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; - } - - // Getters and Setters - public String getCrs() { - return crs; - } - - public void setCrs(String crs) { - this.crs = crs; - } - - public double getMinX() { - return minX; - } - - public void setMinX(double minX) { - this.minX = minX; - } - - public double getMinY() { - return minY; - } - - public void setMinY(double minY) { - this.minY = minY; - } - - public double getMaxX() { - return maxX; - } - - public void setMaxX(double maxX) { - this.maxX = maxX; - } - - public double getMaxY() { - return maxY; - } - - public void setMaxY(double maxY) { - this.maxY = maxY; - } - - @Override - public String toString() { - return "BoundingBox{" - + "crs='" - + crs - + '\'' - + ", minX=" - + minX - + ", minY=" - + minY - + ", maxX=" - + maxX - + ", maxY=" - + maxY - + '}'; - } } } diff --git a/src/main/java/com/kamco/cd/kamcoback/layer/dto/WmtsLayerInfo.java b/src/main/java/com/kamco/cd/kamcoback/layer/dto/WmtsLayerInfo.java index beb6e4ea..44e9b2f3 100755 --- a/src/main/java/com/kamco/cd/kamcoback/layer/dto/WmtsLayerInfo.java +++ b/src/main/java/com/kamco/cd/kamcoback/layer/dto/WmtsLayerInfo.java @@ -1,70 +1,51 @@ package com.kamco.cd.kamcoback.layer.dto; -import com.fasterxml.jackson.annotation.JsonProperty; import java.util.ArrayList; import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; /** WMTS 레이어 정보를 담는 DTO 클래스 */ +@Data +@NoArgsConstructor +@AllArgsConstructor public class WmtsLayerInfo { - public String identifier; - public String title; - public String abstractText; - public List keywords = new ArrayList<>(); - public BoundingBox boundingBox; - public List formats = new ArrayList<>(); - public List tileMatrixSetLinks = new ArrayList<>(); - public List resourceUrls = new ArrayList<>(); - public List