status-update log 추가

This commit is contained in:
2026-03-11 22:26:01 +09:00
parent 141b735ccc
commit 4322e46e3a
20 changed files with 144 additions and 15 deletions

View File

@@ -25,46 +25,105 @@ public class ExternalHttpClient {
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) {
long start = System.currentTimeMillis();
log.info("[API-REQ] method={}, url={}", method, url);
// responseType 기반으로 Accept 동적 세팅 // responseType 기반으로 Accept 동적 세팅
HttpHeaders resolvedHeaders = resolveHeaders(headers, responseType); HttpHeaders resolvedHeaders = resolveHeaders(headers, responseType);
log.debug("[API-REQ] headers={}", resolvedHeaders);
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 bytes -> UTF-8 string
if (responseType == String.class) { if (responseType == String.class) {
ResponseEntity<byte[]> res = restTemplate.exchange(url, method, entity, byte[].class); ResponseEntity<byte[]> res = restTemplate.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);
long elapsed = System.currentTimeMillis() - start;
log.info(
"[API-RES] method={}, url={}, status={}, elapsed={}ms",
method,
url,
res.getStatusCodeValue(),
elapsed);
log.debug("[API-RES] body={}", raw);
@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[]: raw bytes로 받고, JSON이면 에러로 처리
if (responseType == byte[].class) { if (responseType == byte[].class) {
ResponseEntity<byte[]> res = restTemplate.exchange(url, method, entity, byte[].class); ResponseEntity<byte[]> res = restTemplate.exchange(url, method, entity, byte[].class);
long elapsed = System.currentTimeMillis() - start;
log.info(
"[API-RES] method={}, url={}, status={}, elapsed={}ms",
method,
url,
res.getStatusCodeValue(),
elapsed);
MediaType ct = res.getHeaders().getContentType(); MediaType ct = res.getHeaders().getContentType();
byte[] bytes = res.getBody(); byte[] bytes = res.getBody();
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);
log.warn("[API-RES] JSON error body={}", err);
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 = restTemplate.exchange(url, method, entity, responseType);
long elapsed = System.currentTimeMillis() - start;
log.info(
"[API-RES] method={}, url={}, status={}, elapsed={}ms",
method,
url,
res.getStatusCodeValue(),
elapsed);
log.debug("[API-RES] body={}", res.getBody());
return new ExternalCallResult<>(res.getStatusCodeValue(), true, res.getBody(), null); return new ExternalCallResult<>(res.getStatusCodeValue(), true, res.getBody(), null);
} catch (HttpStatusCodeException e) { } catch (HttpStatusCodeException e) {
long elapsed = System.currentTimeMillis() - start;
log.error(
"[API-ERROR] method={}, url={}, status={}, elapsed={}ms, body={}",
method,
url,
e.getStatusCode().value(),
elapsed,
e.getResponseBodyAsString());
return new ExternalCallResult<>( return new ExternalCallResult<>(
e.getStatusCode().value(), false, null, e.getResponseBodyAsString()); e.getStatusCode().value(), false, null, e.getResponseBodyAsString());
} }

View File

@@ -15,8 +15,8 @@ public class GukYuinPnuCntUpdateJobCoreService {
} }
@Transactional @Transactional
public void updateGukYuinContListPnuUpdateCnt() { public int updateGukYuinContListPnuUpdateCnt() {
gukYuinPnuCntUpdateRepository.updateGukYuinContListPnuUpdateCnt(); return gukYuinPnuCntUpdateRepository.updateGukYuinContListPnuUpdateCnt();
} }
@Transactional @Transactional

View File

@@ -2,7 +2,7 @@ package com.kamco.cd.kamcoback.postgres.repository.gukyuin;
public interface GukYuinPnuCntUpdateJobRepositoryCustom { public interface GukYuinPnuCntUpdateJobRepositoryCustom {
void updateGukYuinContListPnuUpdateCnt(); int updateGukYuinContListPnuUpdateCnt();
void updateGukYuinApplyStatus(String uid, String status); void updateGukYuinApplyStatus(String uid, String status);
} }

View File

@@ -20,7 +20,7 @@ public class GukYuinPnuCntUpdateJobRepositoryImpl
@PersistenceContext private EntityManager em; @PersistenceContext private EntityManager em;
@Override @Override
public void updateGukYuinContListPnuUpdateCnt() { public int updateGukYuinContListPnuUpdateCnt() {
String sql = String sql =
""" """
update tb_map_sheet_anal_data_inference_geom p update tb_map_sheet_anal_data_inference_geom p
@@ -33,7 +33,7 @@ public class GukYuinPnuCntUpdateJobRepositoryImpl
where p.geo_uid = c_count.geo_uid and p.pnu != c_count.actual_count; where p.geo_uid = c_count.geo_uid and p.pnu != c_count.actual_count;
"""; """;
jdbcTemplate.update(sql); return jdbcTemplate.update(sql);
} }
@Override @Override

View File

@@ -23,11 +23,6 @@ public class GukYuinApiStatusJobService {
@Value("${spring.profiles.active}") @Value("${spring.profiles.active}")
private String profile; private String profile;
/**
* 실행중인 profile
*
* @return
*/
private boolean isLocalProfile() { private boolean isLocalProfile() {
return "local".equalsIgnoreCase(profile); return "local".equalsIgnoreCase(profile);
} }
@@ -35,34 +30,109 @@ public class GukYuinApiStatusJobService {
/** 매일 00시에 pnu cnt 업데이트 */ /** 매일 00시에 pnu cnt 업데이트 */
public void findGukYuinPnuCntUpdate() { public void findGukYuinPnuCntUpdate() {
long jobStart = System.currentTimeMillis();
log.info("[JOB-START] GukYuin PNU CNT Update Job start profile={}", profile);
log.info("추론 learn 테이블의 apply_status = IN_PROGRESS 인 회차 조회");
log.info("국유인 연동 시작 상태 : IN_PROGRESS 로 업데이트 된다.");
log.info("국유인에서 shp 파일 모두 다운로드 되는지 확인 후 100%가 되면 PNU_COMPLETED 로 업데이트 한다.");
List<LearnKeyDto> list = List<LearnKeyDto> list =
gukYuinJobCoreService.findGukyuinApplyStatusUidList( gukYuinJobCoreService.findGukyuinApplyStatusUidList(
List.of(GukYuinStatus.IN_PROGRESS.getId())); List.of(GukYuinStatus.IN_PROGRESS.getId()));
if (list.isEmpty()) {
int total = list == null ? 0 : list.size();
log.info("[Step 1] 대상 추론 회차 UID 갯수 count={}", total);
if (list == null || list.isEmpty()) {
log.info("[JOB-END] 처리 대상 없음");
return; return;
} }
int success = 0;
int skip = 0;
int fail = 0;
for (LearnKeyDto dto : list) { for (LearnKeyDto dto : list) {
long itemStart = System.currentTimeMillis();
try { try {
log.info("[Step 2] 처리 시작 uid={}, chnDtctMstId={}", dto.getUid(), dto.getChnDtctMstId());
log.info("[Step 3] 국유인 API 호출 시작 uid={}", dto.getUid());
ChngDetectMastDto.ResultDto result = gukYuinApiService.listChnDtctId(dto.getUid(), "Y"); ChngDetectMastDto.ResultDto result = gukYuinApiService.listChnDtctId(dto.getUid(), "Y");
if (result == null || result.getResult() == null || result.getResult().isEmpty()) { if (result == null) {
log.warn("[GUKYUIN] empty result chnDtctMstId={}", dto.getChnDtctMstId()); log.warn("[Step 3] API result NULL uid={}", dto.getUid());
skip++;
continue; continue;
} }
if (result.getResult() == null || result.getResult().isEmpty()) {
log.warn("[Step 3] API result empty chnDtctMstId={}", dto.getChnDtctMstId());
skip++;
continue;
}
log.info("[Step 3] API result size={}", result.getResult().size());
ChngDetectMastDto.Basic basic = result.getResult().getFirst(); ChngDetectMastDto.Basic basic = result.getResult().getFirst();
log.debug("[Step 4] API first result={}", basic);
Integer progress = Integer progress =
basic.getExcnPgrt() == null ? null : Integer.parseInt(basic.getExcnPgrt().trim()); basic.getExcnPgrt() == null ? null : Integer.parseInt(basic.getExcnPgrt().trim());
log.info("[Step 4] 실행 진행률 progress={}", progress);
if (progress != null && progress == 100) { if (progress != null && progress == 100) {
gukYuinPnuCntUpdateJobCoreService.updateGukYuinContListPnuUpdateCnt(); log.info("[Step 5] progress 100 확인 → PNU CNT update 실행");
log.info(
"=== tb_pnu 에 insert 된 데이터 중 tb_map_sheet_anal_data_inference_geom 의 pnu (cnt)값이 다른 것 update");
int updateCnt = gukYuinPnuCntUpdateJobCoreService.updateGukYuinContListPnuUpdateCnt();
log.info("[Step 5] PNU CNT update 완료 updatedRows={}", updateCnt);
log.info(
"[Step 6] 추론 learn 테이블 apply_status={} 로 업데이트", GukYuinStatus.PNU_COMPLETED.getId());
gukYuinPnuCntUpdateJobCoreService.updateGukYuinApplyStatus( gukYuinPnuCntUpdateJobCoreService.updateGukYuinApplyStatus(
dto.getUid(), GukYuinStatus.PNU_COMPLETED.getId()); dto.getUid(), GukYuinStatus.PNU_COMPLETED.getId());
log.info(
"[Step 6] apply_status 변경 완료 uid={}, status={}",
dto.getUid(),
GukYuinStatus.PNU_COMPLETED.getId());
success++;
} else {
log.info("[Step 5] progress != 100 skip uid={}, progress={}", dto.getUid(), progress);
skip++;
} }
log.info(
"[ITEM-END] uid={} elapsed={}ms", dto.getUid(), System.currentTimeMillis() - itemStart);
} catch (Exception e) { } catch (Exception e) {
log.error("[GUKYUIN] failed uid={}", dto.getChnDtctMstId(), e);
fail++;
log.error(
"[ITEM-ERROR] uid={} elapsed={}ms",
dto.getChnDtctMstId(),
System.currentTimeMillis() - itemStart,
e);
} }
} }
log.info(
"[JOB-END] total={}, success={}, skip={}, fail={}, elapsed={}ms",
total,
success,
skip,
fail,
System.currentTimeMillis() - jobStart);
} }
} }