From f54655c191131b57f64110a025dfbcaf5c8a7a2b Mon Sep 17 00:00:00 2001 From: teddy Date: Fri, 27 Feb 2026 09:25:53 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=EC=B6=94=EB=A1=A0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inference/service/InferenceResultService.java | 1 + .../cd/kamcoback/postgres/core/MapSheetMngCoreService.java | 2 +- .../repository/mapsheet/MapSheetMngRepositoryCustom.java | 7 +++++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceResultService.java b/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceResultService.java index 1a2fd26f..96bfa5d7 100644 --- a/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceResultService.java +++ b/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceResultService.java @@ -431,6 +431,7 @@ public class InferenceResultService { .toList(); // 추론 실행 목록 테이블 저장, 도엽목록별 상태 체크 테이블 저장 + // TODO 추론 호출 후 저장으로 순서를 변경해야할지 .. UUID uuid = inferenceResultCoreService.saveInferenceInfo(req, newTargetList); // 추론 AI 전달 파라미터 생성 diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/core/MapSheetMngCoreService.java b/src/main/java/com/kamco/cd/kamcoback/postgres/core/MapSheetMngCoreService.java index 7012420e..3b1670ec 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/core/MapSheetMngCoreService.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/core/MapSheetMngCoreService.java @@ -351,7 +351,7 @@ public class MapSheetMngCoreService { } /** - * 이전 년도 도엽 조회 + * 이전 년도 도엽 조회 조건이 많을 수 있으므로 chunk 줘서 끊어서 조회 * * @param year * @param mapIds diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryCustom.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryCustom.java index d27653a9..3dd13cae 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryCustom.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryCustom.java @@ -125,5 +125,12 @@ public interface MapSheetMngRepositoryCustom { */ List getMapSheetMngHst(Integer year, String mapSheetScope, List mapSheetNum); + /** + * 비교연도 사용 가능한 이전도엽을 조회한다. + * + * @param year 연도 + * @param mapIds 도엽목록 + * @return 사용 가능한 이전도엽목록 + */ List findFallbackCompareYearByMapSheets(Integer year, List mapIds); } -- 2.49.1 From 7b55204ae1e156b30cd6a2dc3f7194b93a5c0e92 Mon Sep 17 00:00:00 2001 From: teddy Date: Fri, 27 Feb 2026 10:06:39 +0900 Subject: [PATCH 2/3] =?UTF-8?q?=EC=B6=94=EB=A1=A0=20api=20=ED=98=B8?= =?UTF-8?q?=EC=B6=9C=EC=8B=9C=20=EB=8C=80=EC=9A=A9=EB=9F=89=EC=9D=B4?= =?UTF-8?q?=EB=A9=B4=203=EC=B4=88=20timeout=20=EB=AC=B8=EC=A0=9C=2060?= =?UTF-8?q?=EC=B4=88=20long=20restTemplate=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/InferenceCommonService.java | 2 +- .../resttemplate/ExternalHttpClient.java | 68 ++++++++++++++----- .../resttemplate/RestTemplateConfig.java | 34 ++++------ .../service/InferenceResultService.java | 3 +- 4 files changed, 67 insertions(+), 40 deletions(-) diff --git a/src/main/java/com/kamco/cd/kamcoback/common/inference/service/InferenceCommonService.java b/src/main/java/com/kamco/cd/kamcoback/common/inference/service/InferenceCommonService.java index ae3fef7e..2776a59e 100644 --- a/src/main/java/com/kamco/cd/kamcoback/common/inference/service/InferenceCommonService.java +++ b/src/main/java/com/kamco/cd/kamcoback/common/inference/service/InferenceCommonService.java @@ -78,7 +78,7 @@ public class InferenceCommonService { // 4) 추론 실행 API 호출 ExternalCallResult result = - externalHttpClient.call(inferenceUrl, HttpMethod.POST, dto, headers, String.class); + externalHttpClient.callLong(inferenceUrl, HttpMethod.POST, dto, headers, String.class); if (result.statusCode() < 200 || result.statusCode() >= 300) { log.error("Inference API failed. status={}, body={}", result.statusCode(), result.body()); 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 e4e656d2..7bf95b9f 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 @@ -3,8 +3,8 @@ 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.beans.factory.annotation.Qualifier; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -14,54 +14,86 @@ 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 RestTemplate restTemplate; // short (@Primary) + private final RestTemplate restTemplateLong; // long private final ObjectMapper objectMapper; + public ExternalHttpClient( + RestTemplate restTemplate, + @Qualifier("restTemplateLong") RestTemplate restTemplateLong, + ObjectMapper objectMapper) { + this.restTemplate = restTemplate; + this.restTemplateLong = restTemplateLong; + this.objectMapper = objectMapper; + } + + /** 기본(짧은 timeout) 호출 */ public ExternalCallResult call( String url, HttpMethod method, Object body, HttpHeaders headers, Class responseType) { - // responseType 기반으로 Accept 동적 세팅 + return doCall(restTemplate, url, method, body, headers, responseType); + } + + /** 추론/대용량 전용 (긴 timeout) */ + public ExternalCallResult callLong( + String url, HttpMethod method, Object body, HttpHeaders headers, Class responseType) { + + return doCall(restTemplateLong, url, method, body, headers, responseType); + } + + private ExternalCallResult doCall( + RestTemplate rt, + String url, + HttpMethod method, + Object body, + HttpHeaders headers, + Class responseType) { + HttpHeaders resolvedHeaders = resolveHeaders(headers, responseType); logRequestBody(body); HttpEntity entity = new HttpEntity<>(body, resolvedHeaders); try { - // String: raw bytes -> UTF-8 string + + // String 응답은 raw byte로 받아 UTF-8 변환 if (responseType == String.class) { - ResponseEntity res = restTemplate.exchange(url, method, entity, byte[].class); + ResponseEntity res = rt.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이면 에러로 처리 + // byte[] 응답 처리 if (responseType == byte[].class) { - ResponseEntity res = restTemplate.exchange(url, method, entity, byte[].class); + ResponseEntity res = rt.exchange(url, method, entity, byte[].class); MediaType ct = res.getHeaders().getContentType(); byte[] bytes = res.getBody(); + // JSON이면 에러로 간주 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); + // DTO 응답 + ResponseEntity res = rt.exchange(url, method, entity, responseType); return new ExternalCallResult<>(res.getStatusCodeValue(), true, res.getBody(), null); } catch (HttpStatusCodeException e) { @@ -70,29 +102,28 @@ public class ExternalHttpClient { } } - // 기존 resolveJsonHeaders를 "동적"으로 교체 + /** Accept / Content-Type 자동 처리 */ private HttpHeaders resolveHeaders(HttpHeaders headers, Class responseType) { - // 원본 headers를 그대로 쓰면 외부에서 재사용할 때 사이드이펙트 날 수 있어서 복사 권장 + HttpHeaders h = (headers == null) ? new HttpHeaders() : new HttpHeaders(headers); - // 요청 바디 기본은 JSON이라고 가정 (필요하면 호출부에서 덮어쓰기) + // 기본 Content-Type if (h.getContentType() == null) { h.setContentType(MediaType.APPLICATION_JSON); } - // 호출부에서 Accept를 명시했으면 존중 + // Accept 이미 있으면 존중 if (h.getAccept() != null && !h.getAccept().isEmpty()) { return h; } - // responseType 기반 Accept 자동 지정 + // 응답 타입 기준 Accept 자동 지정 if (responseType == byte[].class) { h.setAccept( List.of( MediaType.APPLICATION_OCTET_STREAM, MediaType.valueOf("application/zip"), - MediaType.APPLICATION_JSON // 실패(JSON 에러 바디) 대비 - )); + MediaType.APPLICATION_JSON)); } else { h.setAccept(List.of(MediaType.APPLICATION_JSON)); } @@ -100,12 +131,15 @@ public class ExternalHttpClient { return h; } + /** JSON 응답 여부 체크 */ 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) { 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 402f6fe7..203b3aa3 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.context.annotation.Primary; import org.springframework.http.client.BufferingClientHttpRequestFactory; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.web.client.RestTemplate; @@ -13,10 +14,20 @@ import org.springframework.web.client.RestTemplate; public class RestTemplateConfig { @Bean + @Primary public RestTemplate restTemplate(RestTemplateBuilder builder) { + return build(builder, 2000, 3000); + } + + @Bean("restTemplateLong") + public RestTemplate restTemplateLong(RestTemplateBuilder builder) { + return build(builder, 2000, 60000); + } + + private RestTemplate build(RestTemplateBuilder builder, int connectTimeoutMs, int readTimeoutMs) { SimpleClientHttpRequestFactory baseFactory = new SimpleClientHttpRequestFactory(); - baseFactory.setConnectTimeout(2000); - baseFactory.setReadTimeout(60000); + baseFactory.setConnectTimeout(connectTimeoutMs); + baseFactory.setReadTimeout(readTimeoutMs); RestTemplate rt = builder @@ -24,27 +35,8 @@ public class RestTemplateConfig { .additionalInterceptors(new RetryInterceptor()) .build(); - // byte[] 응답은 무조건 raw로 읽게 강제 (Jackson이 끼어들 여지 제거) rt.getMessageConverters() .add(0, new org.springframework.http.converter.ByteArrayHttpMessageConverter()); - return rt; } - - // @Bean(name = "restTemplateLong") - // public RestTemplate restTemplateLong(RestTemplateBuilder builder) { - // SimpleClientHttpRequestFactory baseFactory = new SimpleClientHttpRequestFactory(); - // baseFactory.setConnectTimeout(2000); - // baseFactory.setReadTimeout(60000); // 길게(예: 60초) - // - // RestTemplate rt = - // builder - // .requestFactory(() -> new BufferingClientHttpRequestFactory(baseFactory)) - // .additionalInterceptors(new RetryInterceptor()) - // .build(); - // - // rt.getMessageConverters() - // .add(0, new org.springframework.http.converter.ByteArrayHttpMessageConverter()); - // return rt; - // } } diff --git a/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceResultService.java b/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceResultService.java index 96bfa5d7..8e8150f2 100644 --- a/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceResultService.java +++ b/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceResultService.java @@ -144,6 +144,7 @@ public class InferenceResultService { * @return */ public UUID runExcl(InferenceResultDto.RegReq req) { + // TODO 쿼리로 한번에 할수 있게 수정해야하나.. // 기준연도 실행가능 도엽 조회 List targetMngList = mapSheetMngCoreService.getMapSheetMngHst( @@ -267,7 +268,7 @@ public class InferenceResultService { */ @Transactional public UUID runPrev(InferenceResultDto.RegReq req) { - + // TODO 쿼리로 한번에 할수 있게 수정해야하나.. // 기준연도 실행가능 도엽 조회 List targetMngList = mapSheetMngCoreService.getMapSheetMngHst( -- 2.49.1 From 96d4bb4af3cebdec2b0baf5f67d92f93c1c73a57 Mon Sep 17 00:00:00 2001 From: teddy Date: Fri, 27 Feb 2026 10:08:39 +0900 Subject: [PATCH 3/3] =?UTF-8?q?=EC=A3=BC=EC=84=9D=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cd/kamcoback/inference/service/InferenceResultService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceResultService.java b/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceResultService.java index 8e8150f2..0d4011f9 100644 --- a/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceResultService.java +++ b/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceResultService.java @@ -432,7 +432,6 @@ public class InferenceResultService { .toList(); // 추론 실행 목록 테이블 저장, 도엽목록별 상태 체크 테이블 저장 - // TODO 추론 호출 후 저장으로 순서를 변경해야할지 .. UUID uuid = inferenceResultCoreService.saveInferenceInfo(req, newTargetList); // 추론 AI 전달 파라미터 생성 -- 2.49.1