실태조사 적합여부 log 추가
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:postgresql://localhost:5432/kamco_cds
|
||||
url: jdbc:postgresql://192.168.2.127:15432/kamco_cds
|
||||
username: kamco_cds
|
||||
password: kamco_cds
|
||||
password: kamco_cds_Q!W@E#R$
|
||||
batch:
|
||||
job:
|
||||
name: exportGeoJsonJob # 기본 실행 Job 지정
|
||||
|
||||
Reference in New Issue
Block a user