feat/infer_dev_260211 #114
@@ -78,7 +78,7 @@ public class InferenceCommonService {
|
||||
|
||||
// 4) 추론 실행 API 호출
|
||||
ExternalCallResult<String> 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());
|
||||
|
||||
@@ -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 <T> ExternalCallResult<T> call(
|
||||
String url, HttpMethod method, Object body, HttpHeaders headers, Class<T> responseType) {
|
||||
|
||||
// responseType 기반으로 Accept 동적 세팅
|
||||
return doCall(restTemplate, url, method, body, headers, responseType);
|
||||
}
|
||||
|
||||
/** 추론/대용량 전용 (긴 timeout) */
|
||||
public <T> ExternalCallResult<T> callLong(
|
||||
String url, HttpMethod method, Object body, HttpHeaders headers, Class<T> responseType) {
|
||||
|
||||
return doCall(restTemplateLong, url, method, body, headers, responseType);
|
||||
}
|
||||
|
||||
private <T> ExternalCallResult<T> doCall(
|
||||
RestTemplate rt,
|
||||
String url,
|
||||
HttpMethod method,
|
||||
Object body,
|
||||
HttpHeaders headers,
|
||||
Class<T> responseType) {
|
||||
|
||||
HttpHeaders resolvedHeaders = resolveHeaders(headers, responseType);
|
||||
logRequestBody(body);
|
||||
|
||||
HttpEntity<Object> entity = new HttpEntity<>(body, resolvedHeaders);
|
||||
|
||||
try {
|
||||
// String: raw bytes -> UTF-8 string
|
||||
|
||||
// String 응답은 raw byte로 받아 UTF-8 변환
|
||||
if (responseType == String.class) {
|
||||
ResponseEntity<byte[]> res = restTemplate.exchange(url, method, entity, byte[].class);
|
||||
ResponseEntity<byte[]> 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<byte[]> res = restTemplate.exchange(url, method, entity, byte[].class);
|
||||
ResponseEntity<byte[]> 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<T> res = restTemplate.exchange(url, method, entity, responseType);
|
||||
// DTO 응답
|
||||
ResponseEntity<T> 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) {
|
||||
|
||||
@@ -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;
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -144,6 +144,7 @@ public class InferenceResultService {
|
||||
* @return
|
||||
*/
|
||||
public UUID runExcl(InferenceResultDto.RegReq req) {
|
||||
// TODO 쿼리로 한번에 할수 있게 수정해야하나..
|
||||
// 기준연도 실행가능 도엽 조회
|
||||
List<MngListDto> targetMngList =
|
||||
mapSheetMngCoreService.getMapSheetMngHst(
|
||||
@@ -267,7 +268,7 @@ public class InferenceResultService {
|
||||
*/
|
||||
@Transactional
|
||||
public UUID runPrev(InferenceResultDto.RegReq req) {
|
||||
|
||||
// TODO 쿼리로 한번에 할수 있게 수정해야하나..
|
||||
// 기준연도 실행가능 도엽 조회
|
||||
List<MngListDto> targetMngList =
|
||||
mapSheetMngCoreService.getMapSheetMngHst(
|
||||
|
||||
@@ -351,7 +351,7 @@ public class MapSheetMngCoreService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 이전 년도 도엽 조회
|
||||
* 이전 년도 도엽 조회 조건이 많을 수 있으므로 chunk 줘서 끊어서 조회
|
||||
*
|
||||
* @param year
|
||||
* @param mapIds
|
||||
|
||||
@@ -125,5 +125,12 @@ public interface MapSheetMngRepositoryCustom {
|
||||
*/
|
||||
List<MngListDto> getMapSheetMngHst(Integer year, String mapSheetScope, List<String> mapSheetNum);
|
||||
|
||||
/**
|
||||
* 비교연도 사용 가능한 이전도엽을 조회한다.
|
||||
*
|
||||
* @param year 연도
|
||||
* @param mapIds 도엽목록
|
||||
* @return 사용 가능한 이전도엽목록
|
||||
*/
|
||||
List<MngListDto> findFallbackCompareYearByMapSheets(Integer year, List<String> mapIds);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user