Merge pull request 'feat/infer_dev_260211' (#114) from feat/infer_dev_260211 into develop
Reviewed-on: #114
This commit was merged in pull request #114.
This commit is contained in:
@@ -78,7 +78,7 @@ public class InferenceCommonService {
|
|||||||
|
|
||||||
// 4) 추론 실행 API 호출
|
// 4) 추론 실행 API 호출
|
||||||
ExternalCallResult<String> result =
|
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) {
|
if (result.statusCode() < 200 || result.statusCode() >= 300) {
|
||||||
log.error("Inference API failed. status={}, body={}", result.statusCode(), result.body());
|
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 com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.log4j.Log4j2;
|
import lombok.extern.log4j.Log4j2;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.http.HttpEntity;
|
import org.springframework.http.HttpEntity;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpMethod;
|
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.HttpStatusCodeException;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
@Component
|
@Component
|
||||||
@Log4j2
|
@Log4j2
|
||||||
public class ExternalHttpClient {
|
public class ExternalHttpClient {
|
||||||
|
|
||||||
private final RestTemplate restTemplate;
|
private final RestTemplate restTemplate; // short (@Primary)
|
||||||
|
private final RestTemplate restTemplateLong; // long
|
||||||
private final ObjectMapper objectMapper;
|
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(
|
public <T> ExternalCallResult<T> call(
|
||||||
String url, HttpMethod method, Object body, HttpHeaders headers, Class<T> responseType) {
|
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);
|
HttpHeaders resolvedHeaders = resolveHeaders(headers, responseType);
|
||||||
logRequestBody(body);
|
logRequestBody(body);
|
||||||
|
|
||||||
HttpEntity<Object> entity = new HttpEntity<>(body, resolvedHeaders);
|
HttpEntity<Object> entity = new HttpEntity<>(body, resolvedHeaders);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// String: raw bytes -> UTF-8 string
|
|
||||||
|
// String 응답은 raw byte로 받아 UTF-8 변환
|
||||||
if (responseType == String.class) {
|
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 =
|
String raw =
|
||||||
(res.getBody() == null) ? null : new String(res.getBody(), StandardCharsets.UTF_8);
|
(res.getBody() == null) ? null : new String(res.getBody(), StandardCharsets.UTF_8);
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
T casted = (T) raw;
|
T casted = (T) raw;
|
||||||
|
|
||||||
return new ExternalCallResult<>(res.getStatusCodeValue(), true, casted, null);
|
return new ExternalCallResult<>(res.getStatusCodeValue(), true, casted, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// byte[]: raw bytes로 받고, JSON이면 에러로 처리
|
// byte[] 응답 처리
|
||||||
if (responseType == byte[].class) {
|
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();
|
MediaType ct = res.getHeaders().getContentType();
|
||||||
byte[] bytes = res.getBody();
|
byte[] bytes = res.getBody();
|
||||||
|
|
||||||
|
// JSON이면 에러로 간주
|
||||||
if (isJsonLike(ct)) {
|
if (isJsonLike(ct)) {
|
||||||
String err = (bytes == null) ? null : new String(bytes, StandardCharsets.UTF_8);
|
String err = (bytes == null) ? null : new String(bytes, StandardCharsets.UTF_8);
|
||||||
|
|
||||||
return new ExternalCallResult<>(res.getStatusCodeValue(), false, null, err);
|
return new ExternalCallResult<>(res.getStatusCodeValue(), false, null, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
T casted = (T) bytes;
|
T casted = (T) bytes;
|
||||||
|
|
||||||
return new ExternalCallResult<>(res.getStatusCodeValue(), true, casted, null);
|
return new ExternalCallResult<>(res.getStatusCodeValue(), true, casted, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// DTO 등: 일반 역직렬화
|
// DTO 응답
|
||||||
ResponseEntity<T> res = restTemplate.exchange(url, method, entity, responseType);
|
ResponseEntity<T> res = rt.exchange(url, method, entity, responseType);
|
||||||
return new ExternalCallResult<>(res.getStatusCodeValue(), true, res.getBody(), null);
|
return new ExternalCallResult<>(res.getStatusCodeValue(), true, res.getBody(), null);
|
||||||
|
|
||||||
} catch (HttpStatusCodeException e) {
|
} catch (HttpStatusCodeException e) {
|
||||||
@@ -70,29 +102,28 @@ public class ExternalHttpClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 기존 resolveJsonHeaders를 "동적"으로 교체
|
/** Accept / Content-Type 자동 처리 */
|
||||||
private HttpHeaders resolveHeaders(HttpHeaders headers, Class<?> responseType) {
|
private HttpHeaders resolveHeaders(HttpHeaders headers, Class<?> responseType) {
|
||||||
// 원본 headers를 그대로 쓰면 외부에서 재사용할 때 사이드이펙트 날 수 있어서 복사 권장
|
|
||||||
HttpHeaders h = (headers == null) ? new HttpHeaders() : new HttpHeaders(headers);
|
HttpHeaders h = (headers == null) ? new HttpHeaders() : new HttpHeaders(headers);
|
||||||
|
|
||||||
// 요청 바디 기본은 JSON이라고 가정 (필요하면 호출부에서 덮어쓰기)
|
// 기본 Content-Type
|
||||||
if (h.getContentType() == null) {
|
if (h.getContentType() == null) {
|
||||||
h.setContentType(MediaType.APPLICATION_JSON);
|
h.setContentType(MediaType.APPLICATION_JSON);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 호출부에서 Accept를 명시했으면 존중
|
// Accept 이미 있으면 존중
|
||||||
if (h.getAccept() != null && !h.getAccept().isEmpty()) {
|
if (h.getAccept() != null && !h.getAccept().isEmpty()) {
|
||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
|
|
||||||
// responseType 기반 Accept 자동 지정
|
// 응답 타입 기준 Accept 자동 지정
|
||||||
if (responseType == byte[].class) {
|
if (responseType == byte[].class) {
|
||||||
h.setAccept(
|
h.setAccept(
|
||||||
List.of(
|
List.of(
|
||||||
MediaType.APPLICATION_OCTET_STREAM,
|
MediaType.APPLICATION_OCTET_STREAM,
|
||||||
MediaType.valueOf("application/zip"),
|
MediaType.valueOf("application/zip"),
|
||||||
MediaType.APPLICATION_JSON // 실패(JSON 에러 바디) 대비
|
MediaType.APPLICATION_JSON));
|
||||||
));
|
|
||||||
} else {
|
} else {
|
||||||
h.setAccept(List.of(MediaType.APPLICATION_JSON));
|
h.setAccept(List.of(MediaType.APPLICATION_JSON));
|
||||||
}
|
}
|
||||||
@@ -100,12 +131,15 @@ public class ExternalHttpClient {
|
|||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** JSON 응답 여부 체크 */
|
||||||
private boolean isJsonLike(MediaType ct) {
|
private boolean isJsonLike(MediaType ct) {
|
||||||
if (ct == null) return false;
|
if (ct == null) return false;
|
||||||
|
|
||||||
return ct.includes(MediaType.APPLICATION_JSON)
|
return ct.includes(MediaType.APPLICATION_JSON)
|
||||||
|| "application/problem+json".equalsIgnoreCase(ct.toString());
|
|| "application/problem+json".equalsIgnoreCase(ct.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 요청 바디 로그 */
|
||||||
private void logRequestBody(Object body) {
|
private void logRequestBody(Object body) {
|
||||||
try {
|
try {
|
||||||
if (body != null) {
|
if (body != null) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import lombok.extern.log4j.Log4j2;
|
|||||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Primary;
|
||||||
import org.springframework.http.client.BufferingClientHttpRequestFactory;
|
import org.springframework.http.client.BufferingClientHttpRequestFactory;
|
||||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
@@ -13,10 +14,20 @@ import org.springframework.web.client.RestTemplate;
|
|||||||
public class RestTemplateConfig {
|
public class RestTemplateConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
@Primary
|
||||||
public RestTemplate restTemplate(RestTemplateBuilder builder) {
|
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();
|
SimpleClientHttpRequestFactory baseFactory = new SimpleClientHttpRequestFactory();
|
||||||
baseFactory.setConnectTimeout(2000);
|
baseFactory.setConnectTimeout(connectTimeoutMs);
|
||||||
baseFactory.setReadTimeout(60000);
|
baseFactory.setReadTimeout(readTimeoutMs);
|
||||||
|
|
||||||
RestTemplate rt =
|
RestTemplate rt =
|
||||||
builder
|
builder
|
||||||
@@ -24,27 +35,8 @@ public class RestTemplateConfig {
|
|||||||
.additionalInterceptors(new RetryInterceptor())
|
.additionalInterceptors(new RetryInterceptor())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// byte[] 응답은 무조건 raw로 읽게 강제 (Jackson이 끼어들 여지 제거)
|
|
||||||
rt.getMessageConverters()
|
rt.getMessageConverters()
|
||||||
.add(0, new org.springframework.http.converter.ByteArrayHttpMessageConverter());
|
.add(0, new org.springframework.http.converter.ByteArrayHttpMessageConverter());
|
||||||
|
|
||||||
return rt;
|
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
|
* @return
|
||||||
*/
|
*/
|
||||||
public UUID runExcl(InferenceResultDto.RegReq req) {
|
public UUID runExcl(InferenceResultDto.RegReq req) {
|
||||||
|
// TODO 쿼리로 한번에 할수 있게 수정해야하나..
|
||||||
// 기준연도 실행가능 도엽 조회
|
// 기준연도 실행가능 도엽 조회
|
||||||
List<MngListDto> targetMngList =
|
List<MngListDto> targetMngList =
|
||||||
mapSheetMngCoreService.getMapSheetMngHst(
|
mapSheetMngCoreService.getMapSheetMngHst(
|
||||||
@@ -267,7 +268,7 @@ public class InferenceResultService {
|
|||||||
*/
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public UUID runPrev(InferenceResultDto.RegReq req) {
|
public UUID runPrev(InferenceResultDto.RegReq req) {
|
||||||
|
// TODO 쿼리로 한번에 할수 있게 수정해야하나..
|
||||||
// 기준연도 실행가능 도엽 조회
|
// 기준연도 실행가능 도엽 조회
|
||||||
List<MngListDto> targetMngList =
|
List<MngListDto> targetMngList =
|
||||||
mapSheetMngCoreService.getMapSheetMngHst(
|
mapSheetMngCoreService.getMapSheetMngHst(
|
||||||
|
|||||||
@@ -351,7 +351,7 @@ public class MapSheetMngCoreService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 이전 년도 도엽 조회
|
* 이전 년도 도엽 조회 조건이 많을 수 있으므로 chunk 줘서 끊어서 조회
|
||||||
*
|
*
|
||||||
* @param year
|
* @param year
|
||||||
* @param mapIds
|
* @param mapIds
|
||||||
|
|||||||
@@ -125,5 +125,12 @@ public interface MapSheetMngRepositoryCustom {
|
|||||||
*/
|
*/
|
||||||
List<MngListDto> getMapSheetMngHst(Integer year, String mapSheetScope, List<String> mapSheetNum);
|
List<MngListDto> getMapSheetMngHst(Integer year, String mapSheetScope, List<String> mapSheetNum);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 비교연도 사용 가능한 이전도엽을 조회한다.
|
||||||
|
*
|
||||||
|
* @param year 연도
|
||||||
|
* @param mapIds 도엽목록
|
||||||
|
* @return 사용 가능한 이전도엽목록
|
||||||
|
*/
|
||||||
List<MngListDto> findFallbackCompareYearByMapSheets(Integer year, List<String> mapIds);
|
List<MngListDto> findFallbackCompareYearByMapSheets(Integer year, List<String> mapIds);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user