실태조사 적합여부 log 추가
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package com.kamco.cd.kamcoback.config;
|
||||
|
||||
import java.time.Duration;
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -10,6 +11,10 @@ public class RestTemplateConfig {
|
||||
|
||||
@Bean
|
||||
public RestTemplate restTemplate(RestTemplateBuilder builder) {
|
||||
return builder.build();
|
||||
|
||||
return builder
|
||||
.connectTimeout(Duration.ofSeconds(5))
|
||||
.readTimeout(Duration.ofSeconds(120)) // read timeout
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,11 @@ public class ExternalHttpClient {
|
||||
public <T> ExternalCallResult<T> call(
|
||||
String url, HttpMethod method, Object body, HttpHeaders headers, Class<T> responseType) {
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
log.info("[API-REQ] {} {}", method, url);
|
||||
log.debug("[API-REQ] headers={}", headers);
|
||||
|
||||
// responseType 기반으로 Accept 동적 세팅
|
||||
HttpHeaders resolvedHeaders = resolveHeaders(headers, responseType);
|
||||
logRequestBody(body);
|
||||
@@ -32,39 +37,67 @@ public class ExternalHttpClient {
|
||||
HttpEntity<Object> entity = new HttpEntity<>(body, resolvedHeaders);
|
||||
|
||||
try {
|
||||
|
||||
// String: raw bytes -> UTF-8 string
|
||||
if (responseType == String.class) {
|
||||
ResponseEntity<byte[]> res = restTemplate.exchange(url, method, entity, byte[].class);
|
||||
|
||||
long elapsed = System.currentTimeMillis() - start;
|
||||
log.info("[API-RES] status={} elapsed={}ms", res.getStatusCodeValue(), elapsed);
|
||||
|
||||
String raw =
|
||||
(res.getBody() == null) ? null : new String(res.getBody(), StandardCharsets.UTF_8);
|
||||
|
||||
log.debug("[API-RES] body={}", raw);
|
||||
|
||||
@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);
|
||||
|
||||
long elapsed = System.currentTimeMillis() - start;
|
||||
log.info("[API-RES] status={} elapsed={}ms", res.getStatusCodeValue(), elapsed);
|
||||
|
||||
MediaType ct = res.getHeaders().getContentType();
|
||||
byte[] bytes = res.getBody();
|
||||
|
||||
if (isJsonLike(ct)) {
|
||||
String err = (bytes == null) ? null : new String(bytes, StandardCharsets.UTF_8);
|
||||
log.warn("[API-RES] JSON error body={}", err);
|
||||
return new ExternalCallResult<>(res.getStatusCodeValue(), false, null, err);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
T casted = (T) bytes;
|
||||
|
||||
return new ExternalCallResult<>(res.getStatusCodeValue(), true, casted, null);
|
||||
}
|
||||
|
||||
// DTO 등: 일반 역직렬화
|
||||
// DTO
|
||||
ResponseEntity<T> res = restTemplate.exchange(url, method, entity, responseType);
|
||||
|
||||
long elapsed = System.currentTimeMillis() - start;
|
||||
|
||||
log.info("[API-RES] status={} elapsed={}ms", res.getStatusCodeValue(), elapsed);
|
||||
log.debug("[API-RES] body={}", res.getBody());
|
||||
|
||||
return new ExternalCallResult<>(res.getStatusCodeValue(), true, res.getBody(), null);
|
||||
|
||||
} catch (HttpStatusCodeException e) {
|
||||
|
||||
long elapsed = System.currentTimeMillis() - start;
|
||||
|
||||
log.error(
|
||||
"[API-ERROR] status={} elapsed={}ms body={}",
|
||||
e.getStatusCode().value(),
|
||||
elapsed,
|
||||
e.getResponseBodyAsString());
|
||||
|
||||
return new ExternalCallResult<>(
|
||||
e.getStatusCode().value(), false, null, e.getResponseBodyAsString());
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.kamco.cd.kamcoback.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectContDto.StbltResult;
|
||||
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.LearnKeyDto;
|
||||
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.RlbDtctDto;
|
||||
@@ -11,6 +12,8 @@ import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
@@ -24,116 +27,251 @@ public class GukYuinApiStbltJobService {
|
||||
|
||||
private final GukYuinStbltJobCoreService gukYuinStbltJobCoreService;
|
||||
private final GukYuinApiService gukYuinApiService;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@Value("${spring.profiles.active}")
|
||||
private String profile;
|
||||
|
||||
/**
|
||||
* 실행중인 profile
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private boolean isLocalProfile() {
|
||||
return "local".equalsIgnoreCase(profile);
|
||||
}
|
||||
|
||||
public void runTask() {
|
||||
findGukYuinEligibleForSurvey(null);
|
||||
}
|
||||
|
||||
/** 국유인 연동 후, 실태조사 적합여부 확인하여 update */
|
||||
public void findGukYuinEligibleForSurvey(LocalDate baseDate) {
|
||||
long jobStart = System.currentTimeMillis();
|
||||
String jobId = UUID.randomUUID().toString().substring(0, 8);
|
||||
|
||||
log.info("[Step 1-1] 국유인 연동 PNU 완료된 추론 회차 정보 가져오기 ");
|
||||
log.info(" learn 테이블의 apply_status : {}", GukYuinStatus.PNU_COMPLETED.getId());
|
||||
List<LearnKeyDto> list =
|
||||
gukYuinStbltJobCoreService.findGukYuinEligibleForSurveyList(
|
||||
GukYuinStatus.PNU_COMPLETED.getId());
|
||||
int totalTargetCount = 0;
|
||||
int successCount = 0;
|
||||
int failCount = 0;
|
||||
int emptyCount = 0;
|
||||
|
||||
log.info("[Step 1-2] 국유인 연동 PNU 완료된 추론 회차 갯수 : {}", list == null ? 0 : list.size());
|
||||
if (list.isEmpty()) {
|
||||
log.info("[Step 1-3] 국유인 연동 PNU 완료된 추론 회차 갯수 없어서 return");
|
||||
return;
|
||||
}
|
||||
log.info("=== Jenkins Job : AM00-PNU-UPDATE-DATA 를 수행한 이후 진행해야 함 ===");
|
||||
log.info("=== AM00-PNU-UPDATE-DATA 수행 내용");
|
||||
log.info("=== 1) 추론결과 폴리곤 tb_map_sheet_anal_data_inference_geom 테이블에 pnu 갯수 업데이트");
|
||||
log.info("=== 2) 추론 tb_map_sheet_learn 테이블의 apply_status = PNU_COMPLETED 로 업데이트");
|
||||
log.info("* 이 과정이 수행되어야 아래 쿼리에서 조건을 조회할 수 있음");
|
||||
|
||||
log.info("[Step 2-1] 추론 회차 list 로 for문 실행하기 ");
|
||||
for (LearnKeyDto dto : list) {
|
||||
try {
|
||||
String targetDate =
|
||||
LocalDate.now(ZoneId.of("Asia/Seoul"))
|
||||
.minusDays(1)
|
||||
.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
|
||||
log.info("[JOB-START][{}] GukYuinApiStbltJob 시작", jobId);
|
||||
log.info("[JOB-START][{}] profile={}, BATCH_DATE={}", jobId, profile, baseDate);
|
||||
|
||||
log.info("[Step 2-2] 실태조사 적합여부 조회 날짜 확인 : {}", targetDate);
|
||||
if (baseDate != null) { // 파라미터가 있으면
|
||||
targetDate = baseDate.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
|
||||
log.info("[Step 2-3] 수동호출 baseDate 가 있을 경우, 실태조사 적합여부 조회 날짜 확인 : {}", targetDate);
|
||||
}
|
||||
try {
|
||||
log.info("[Step 1-1][{}] 국유인 연동 PNU 완료된 추론 회차 정보 조회 시작", jobId);
|
||||
|
||||
log.info("[Step 3-1] 국유인 실태조사 적합여부 API 호출 시작 ");
|
||||
log.info(" === 값 확인 - uid : {}", dto.getUid());
|
||||
log.info(" === 값 확인 - targetDate : {}", targetDate);
|
||||
RlbDtctDto result = gukYuinApiService.findRlbDtctList(dto.getUid(), targetDate, "Y");
|
||||
log.info("[Step 1-1] 조회 대상 추론 회차 조건 : ");
|
||||
log.info(
|
||||
"===== 추론 tb_map_sheet_learn 테이블의 apply_status = {}",
|
||||
GukYuinStatus.PNU_COMPLETED.getId());
|
||||
log.info("===== 추론결과 폴리곤 tb_map_sheet_anal_data_inference_geom 테이블의 pnu > 0");
|
||||
log.info("===== 추론결과 폴리곤 tb_map_sheet_anal_data_inference_geom 테이블의 fit_state is null");
|
||||
List<LearnKeyDto> list =
|
||||
gukYuinStbltJobCoreService.findGukYuinEligibleForSurveyList(
|
||||
GukYuinStatus.PNU_COMPLETED.getId());
|
||||
|
||||
if (result == null || result.getResult() == null || result.getResult().isEmpty()) {
|
||||
log.info("[GUKYUIN] empty result chnDtctId={}", dto.getUid());
|
||||
log.info("=== 국유인 API 조회 결과 없어서 continue");
|
||||
continue;
|
||||
}
|
||||
totalTargetCount = (list == null) ? 0 : list.size();
|
||||
|
||||
log.info("[Step 4-1] 국유인 실태조사 적합여부 result 값으로 데이터 업데이트");
|
||||
log.info(" === 데이터 갯수 : {}", result.getResult() == null ? 0 : result.getResult().size());
|
||||
log.info("[Step 1-2][{}] 조회 대상 추론 회차 수={}", jobId, totalTargetCount);
|
||||
|
||||
for (RlbDtctMastDto stbltDto : result.getResult()) {
|
||||
log.info("[Step 4-2] 국유인 실태조사 적합여부 결과 가져오기");
|
||||
String resultUid = stbltDto.getChnDtctObjtId();
|
||||
log.info(" == 테이블 tb_pnu 에 적합여부 리턴 결과를 upsert 진행, 객체 uid : {}", resultUid);
|
||||
gukYuinStbltJobCoreService.updateGukYuinEligibleForSurvey(resultUid, stbltDto);
|
||||
}
|
||||
|
||||
Map<String, StbltResult> resultMap =
|
||||
result.getResult().stream()
|
||||
.collect(Collectors.groupingBy(RlbDtctMastDto::getChnDtctObjtId))
|
||||
.entrySet()
|
||||
.stream()
|
||||
.collect(
|
||||
Collectors.toMap(
|
||||
Map.Entry::getKey,
|
||||
e -> {
|
||||
List<RlbDtctMastDto> pnuList = e.getValue();
|
||||
|
||||
log.info("[Step 4-3] 국유인 실태조사 적합여부 업데이트 값을 객체 uid 기준으로 DTO 생성");
|
||||
boolean hasY = pnuList.stream().anyMatch(v -> "Y".equals(v.getStbltYn()));
|
||||
|
||||
String fitYn = hasY ? "Y" : "N";
|
||||
|
||||
RlbDtctMastDto selected =
|
||||
hasY
|
||||
? pnuList.stream()
|
||||
.filter(v -> "Y".equals(v.getStbltYn()))
|
||||
.findFirst()
|
||||
.orElse(null)
|
||||
: pnuList.stream()
|
||||
.filter(v -> "N".equals(v.getStbltYn()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
log.info(" === selected DTO : {}", selected);
|
||||
if (selected == null) {
|
||||
log.info(" === selected NULL");
|
||||
return null; // 방어 코드
|
||||
}
|
||||
|
||||
return new StbltResult(
|
||||
fitYn, selected.getIncyCd(), selected.getIncyRsnCont());
|
||||
}));
|
||||
|
||||
log.info("[Step 4-4] 국유인 실태조사 적합여부, 사유, 내용을 inference_geom 테이블에 update");
|
||||
resultMap.forEach(gukYuinStbltJobCoreService::updateGukYuinObjectStbltYn);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[GUKYUIN] failed uid={}", dto.getUid(), e);
|
||||
if (list == null || list.isEmpty()) {
|
||||
log.info("[Step 1-3][{}] 처리 대상 없음. job 종료", jobId);
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("[Step 2-1][{}] 대상 회차 for문 처리 시작", jobId);
|
||||
|
||||
int index = 0;
|
||||
for (LearnKeyDto dto : list) {
|
||||
index++;
|
||||
long itemStart = System.currentTimeMillis();
|
||||
String uid = dto.getUid();
|
||||
|
||||
try {
|
||||
log.info("[ITEM-START][{}][{}/{}] uid={} 처리 시작", jobId, index, totalTargetCount, uid);
|
||||
|
||||
String targetDate =
|
||||
LocalDate.now(ZoneId.of("Asia/Seoul"))
|
||||
.minusDays(1)
|
||||
.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
|
||||
|
||||
log.info("[Step 2-2][{}] 기본은 어제 날짜 targetDate={}", jobId, targetDate);
|
||||
|
||||
if (baseDate != null) {
|
||||
targetDate = baseDate.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
|
||||
log.info("[Step 2-3][{}] baseDate 파라미터가 있으면 targetDate={}", jobId, targetDate);
|
||||
}
|
||||
|
||||
log.info(
|
||||
"[Step 3-1][{}] 국유인 실태조사 적합여부 API 호출 시작 uid={}, targetDate={}",
|
||||
jobId,
|
||||
uid,
|
||||
targetDate);
|
||||
|
||||
long apiStart = System.currentTimeMillis();
|
||||
RlbDtctDto result = gukYuinApiService.findRlbDtctList(uid, targetDate, "Y");
|
||||
long apiEnd = System.currentTimeMillis();
|
||||
|
||||
try {
|
||||
log.info("[Step 3-2] API result JSON : {}", objectMapper.writeValueAsString(result));
|
||||
} catch (Exception e) {
|
||||
log.error("result json convert error", e);
|
||||
}
|
||||
|
||||
int resultSize =
|
||||
(result == null || result.getResult() == null) ? 0 : result.getResult().size();
|
||||
|
||||
log.info(
|
||||
"[Step 3-2][{}] API 호출 종료 uid={}, resultSize={}, elapsed={}ms",
|
||||
jobId,
|
||||
uid,
|
||||
resultSize,
|
||||
(apiEnd - apiStart));
|
||||
|
||||
if (result == null) {
|
||||
log.warn("[ITEM-WARN][{}] API result 가 null 입니다. uid={}", jobId, uid);
|
||||
emptyCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (result.getResult() == null || result.getResult().isEmpty()) {
|
||||
log.info("[ITEM-EMPTY][{}] API 조회 결과 없음 uid={}", jobId, uid);
|
||||
emptyCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
log.info(
|
||||
"[Step 4-1][{}] API 결과 기반 tb_pnu upsert 시작 uid={}, count={}",
|
||||
jobId,
|
||||
uid,
|
||||
result.getResult().size());
|
||||
|
||||
int upsertCount = 0;
|
||||
for (RlbDtctMastDto stbltDto : result.getResult()) {
|
||||
String resultUid = stbltDto.getChnDtctObjtId();
|
||||
log.debug(
|
||||
"[Step 4-2][{}] tb_pnu upsert 대상 resultUid={}, stbltYn={}, incyCd={}",
|
||||
jobId,
|
||||
resultUid,
|
||||
stbltDto.getStbltYn(),
|
||||
stbltDto.getIncyCd());
|
||||
|
||||
gukYuinStbltJobCoreService.updateGukYuinEligibleForSurvey(resultUid, stbltDto);
|
||||
upsertCount++;
|
||||
}
|
||||
|
||||
log.info(
|
||||
"[Step 4-2][{}] tb_pnu upsert 완료 uid={}, upsertCount={}", jobId, uid, upsertCount);
|
||||
|
||||
log.info("[Step 4-3] 1개 폴리곤 객체 objtId 에 여러 pnu가 존재할 수 있음.");
|
||||
log.info("1개 폴리곤 객체 objtId에 여러 pnu의 적합여부가 들어온다면 ");
|
||||
log.info("폴리곤 객체 objtId 에 해당하는 pnu의 적합여부가 하나라도 Y가 있다면 Y (적합)");
|
||||
log.info("Y가 없다면 N (부적합) 으로 판단한다.");
|
||||
Map<String, StbltResult> resultMap =
|
||||
result.getResult().stream()
|
||||
.collect(Collectors.groupingBy(RlbDtctMastDto::getChnDtctObjtId))
|
||||
.entrySet()
|
||||
.stream()
|
||||
.map(
|
||||
e -> {
|
||||
String resultUid = e.getKey();
|
||||
List<RlbDtctMastDto> pnuList = e.getValue();
|
||||
|
||||
log.debug(
|
||||
"[Step 4-3][{}] 객체별 결과 집계 resultUid={}, rowCount={}",
|
||||
jobId,
|
||||
resultUid,
|
||||
pnuList == null ? 0 : pnuList.size());
|
||||
|
||||
boolean hasY = pnuList.stream().anyMatch(v -> "Y".equals(v.getStbltYn()));
|
||||
String fitYn = hasY ? "Y" : "N";
|
||||
log.info("=== 적합여부 fitYn={}", fitYn);
|
||||
|
||||
RlbDtctMastDto selected =
|
||||
hasY
|
||||
? pnuList.stream()
|
||||
.filter(v -> "Y".equals(v.getStbltYn()))
|
||||
.findFirst()
|
||||
.orElse(null)
|
||||
: pnuList.stream()
|
||||
.filter(v -> "N".equals(v.getStbltYn()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
log.info(
|
||||
"객체 objtId 에 해당하는 pnuList 중에서 적합여부: {}에 해당하는 첫번째 값을 조회한다. => selected",
|
||||
fitYn);
|
||||
if (selected == null) {
|
||||
log.warn("[Step 4-3][{}] selected null resultUid={}", jobId, resultUid);
|
||||
return null;
|
||||
}
|
||||
|
||||
log.debug(
|
||||
"[Step 4-3][{}] resultUid={}, fitYn={}, incyCd={}, incyRsnCont={}",
|
||||
jobId,
|
||||
resultUid,
|
||||
fitYn,
|
||||
selected.getIncyCd(),
|
||||
selected.getIncyRsnCont());
|
||||
|
||||
return Map.entry(
|
||||
resultUid,
|
||||
new StbltResult(
|
||||
fitYn, selected.getIncyCd(), selected.getIncyRsnCont()));
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
|
||||
log.info(
|
||||
"[Step 4-4][{}] inference_geom 에 적합여부 update 시작 uid={}, objectCount={}",
|
||||
jobId,
|
||||
uid,
|
||||
resultMap.size());
|
||||
|
||||
int objectUpdateCount = 0;
|
||||
for (Map.Entry<String, StbltResult> entry : resultMap.entrySet()) {
|
||||
log.info("result_uid: {}", entry.getKey());
|
||||
log.info("StbltResult: {}", entry.getValue());
|
||||
|
||||
gukYuinStbltJobCoreService.updateGukYuinObjectStbltYn(entry.getKey(), entry.getValue());
|
||||
objectUpdateCount++;
|
||||
}
|
||||
|
||||
log.info(
|
||||
"[Step 4-5][{}] inference_geom update 완료 uid={}, objectUpdateCount={}",
|
||||
jobId,
|
||||
uid,
|
||||
objectUpdateCount);
|
||||
|
||||
successCount++;
|
||||
log.info(
|
||||
"[ITEM-END][{}][{}/{}] uid={} 처리 완료 elapsed={}ms",
|
||||
jobId,
|
||||
index,
|
||||
totalTargetCount,
|
||||
uid,
|
||||
(System.currentTimeMillis() - itemStart));
|
||||
|
||||
} catch (Exception e) {
|
||||
failCount++;
|
||||
log.error(
|
||||
"[ITEM-ERROR][{}][{}/{}] uid={} 처리 실패 elapsed={}ms",
|
||||
jobId,
|
||||
index,
|
||||
totalTargetCount,
|
||||
uid,
|
||||
(System.currentTimeMillis() - itemStart),
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
} finally {
|
||||
log.info(
|
||||
"[JOB-END][{}] totalTargetCount={}, successCount={}, failCount={}, emptyCount={}, totalElapsed={}ms",
|
||||
jobId,
|
||||
totalTargetCount,
|
||||
successCount,
|
||||
failCount,
|
||||
emptyCount,
|
||||
(System.currentTimeMillis() - jobStart));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user