From 335c7ec53aa0fe198cb972fb13026779ccf33108 Mon Sep 17 00:00:00 2001 From: eastargh Date: Thu, 14 May 2026 11:19:55 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B1=B0=EB=9E=98=EB=A1=9C=EA=B7=B8=20>=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=EB=B3=84=20=EC=A7=91=EA=B3=84=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EB=B0=B0=EC=B9=98=EC=9E=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../djb/job/ApiStatsHourlyAggregationJob.java | 147 ++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 src/main/java/com/eactive/eai/rms/ext/djb/job/ApiStatsHourlyAggregationJob.java diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/job/ApiStatsHourlyAggregationJob.java b/src/main/java/com/eactive/eai/rms/ext/djb/job/ApiStatsHourlyAggregationJob.java new file mode 100644 index 0000000..b29c7f3 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/ext/djb/job/ApiStatsHourlyAggregationJob.java @@ -0,0 +1,147 @@ +package com.eactive.eai.rms.ext.djb.job; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; + +import org.quartz.Job; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.quartz.SchedulerException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import com.eactive.eai.rms.common.datasource.DataSourceContextHolder; +import com.eactive.eai.rms.common.datasource.DataSourceType; +import com.eactive.eai.rms.common.datasource.DataSourceTypeManager; +import com.eactive.eai.rms.common.util.CommonUtil; +import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsHourService; +import com.eactive.eai.rms.data.entity.onl.kjb.statistics.QApiStatsHour; + +/** + * API 로그 테이블을 시간별 통계로 집계하는 배치 작업 + * TSEAILGXX → API_STATS_HOUR + * 실행 주기: 매시 00:10 (당일 데이터 집계) + */ +@Component +public class ApiStatsHourlyAggregationJob implements Job { + private static final Logger log = LoggerFactory.getLogger(ApiStatsHourlyAggregationJob.class); + + @PersistenceContext + private EntityManager entityManager; + + private ApiStatsHourService apiStatsHourService; + + @Autowired + public void setApiStatsHourService(ApiStatsHourService apiStatsHourService) { + this.apiStatsHourService = apiStatsHourService; + } + + @Override + public void execute(JobExecutionContext context) throws JobExecutionException { + ApplicationContext appContext = null; + final ApiStatsHourlyAggregationJob selfJob; + try { + appContext = (ApplicationContext) context.getScheduler().getContext().get("applicationContext"); + selfJob = appContext.getBean(ApiStatsHourlyAggregationJob.class); + } catch (SchedulerException e) { + log.error("applicationContext get module error", e); + return; + } + + DataSourceType dataType = DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.APIGW); + DataSourceContextHolder.setDataSourceType(dataType); + try { + LocalDate targetDate = LocalDate.now(); + if (LocalTime.now().isBefore(LocalTime.of(1, 0))) { + targetDate = targetDate.minusDays(1); + } + selfJob.executeManual(targetDate); + } catch (Exception e) { + throw new JobExecutionException(e); + } finally { + DataSourceContextHolder.clearDataSourceType(); + } + } + + /** + * 수동 실행 메서드 (날짜 지정) + */ + @Transactional + public int executeManual(LocalDate targetDate) { + long startTime = System.currentTimeMillis(); + log.info("=== 시간별 통계 집계 작업 시작 === 대상: {}", targetDate); + + String searchDate = targetDate.format(DateTimeFormatter.ofPattern("yyyyMMdd")); + String schemaEMS = System.getProperty("eai.tableowner", "EMSADM").toUpperCase(); + String logTableName = CommonUtil.getLogTable(searchDate, true); + + // 1. 기존 데이터 삭제 + QApiStatsHour q = QApiStatsHour.apiStatsHour; + long deletedCount = apiStatsHourService.getJPAQueryFactory() + .delete(q) + .where(q.statTime.goe(targetDate.atStartOfDay()) + .and(q.statTime.lt(targetDate.plusDays(1).atStartOfDay()))) + .execute(); + log.info("기존 데이터 삭제: {} 건", deletedCount); + + // 2. 집계 및 데이터 입력 + /** + * EAISVCSERNO로 그룹핑하여 1행으로 집계를 한 다음 + * 총건수 : EAISVCSERNO로 그룹핑한 총 건수 + * 성공건수 : seq = 400 and EAIERRCD = null 인 건수 + * Timeout : EAIERRCD in (공통코드 CODEGROUP = 'API_ERROR_CODE' and PARENTCODE = 'T') 인 건수 + * 업무오류 : EAIERRCD in (공통코드 CODEGROUP = 'API_ERROR_CODE' and PARENTCODE = 'B') 인 건수 + * 시스템오류 : (seq400 = null or EAIERRCD is not null) and 공통코드에 정의되지 않은 errorcode 인 건수 + */ + String sql = + "INSERT INTO API_STATS_HOUR" + + " (STAT_TIME, API_NAME, GW_INSTANCE_ID, BIZ_DIV_CODE, CLIENT_ID" + + " , INBOUND_ADAPTER, OUTBOUND_ADAPTER" + + ", TOTAL_CNT, SUCCESS_CNT, TIMEOUT_CNT, SYSTEM_ERR_CNT, BIZ_ERR_CNT" + + ", SEQ900_TIMEOUT_CNT, SEQ900_SYSTEM_ERR_CNT, SEQ900_BIZ_ERR_CNT" + + ", AVG_RESP_TIME, MIN_RESP_TIME, MAX_RESP_TIME, P50_RESP_TIME, P95_RESP_TIME)" + + " SELECT TO_TIMESTAMP(SUBSTR(DT,1,10) || '0000', 'YYYYMMDDHH24MISS')" + + " , API_NAME, GW_INSTANCE_ID, BIZ_DIV_CODE, NVL(A.CLIENT_ID, 'NONE')" + + " , INBOUND_ADAPTER, OUTBOUND_ADAPTER" + + " , COUNT(EAISVCSERNO)" + + " , SUM(CASE WHEN A.ERROR_CODE IS NULL AND E400 IS NOT NULL THEN 1 ELSE 0 END)" + + " , SUM(CASE WHEN B.PARENTCODE = 'T' THEN 1 ELSE 0 END)" + + " , SUM(CASE WHEN (A.E400 IS NULL OR A.ERROR_CODE IS NOT NULL) AND B.PARENTCODE IS NULL THEN 1 ELSE 0 END)" + + " , SUM(CASE WHEN B.PARENTCODE = 'B' THEN 1 ELSE 0 END)" + + " , 0, 0, 0" + + " , TRUNC(AVG(RESP_TIME)), MIN(RESP_TIME), MAX(RESP_TIME), 0, 0" + + " FROM (" + + " SELECT EAISVCSERNO" + + " , MAX(EAISVCNAME) AS API_NAME, MAX(EAISEVRINSTNCNAME) AS GW_INSTANCE_ID" + + " , MAX(EAIBZWKDSTCD) AS BIZ_DIV_CODE, MAX(CLIENTID) AS CLIENT_ID" + + " , MAX(CLIENTNAME) AS CLIENT_NAME, MAX(ORGID) AS ORG_ID, MAX(ORGNAME) AS ORG_NAME" + + " , MAX(GSTATSYSADPTRBZWKGROUPNAME) AS INBOUND_ADAPTER" + + " , MAX(PSVSYSADPTRBZWKGROUPNAME) AS OUTBOUND_ADAPTER" + + " , MIN(MSGDPSTYMS) AS DT" + + " , MAX(MSGPRCSSYMS) - MIN(MSGDPSTYMS) AS RESP_TIME" + + " , MAX(CASE WHEN LOGPRCSSSERNO = '400' THEN LOGPRCSSSERNO ELSE '' END) AS E400" + + " , MAX(EAIERRCD) AS ERROR_CODE" + + " FROM " + logTableName + + " WHERE MSGDPSTYMS LIKE :searchDate || '%'" + + " GROUP BY EAISVCSERNO" + + " ) A LEFT OUTER JOIN " + schemaEMS + ".TSEAIRM28 B ON A.ERROR_CODE = B.CODE AND B.CODEGROUP = 'API_ERROR_CODE'" + + " GROUP BY SUBSTR(DT,1,10), API_NAME, GW_INSTANCE_ID, BIZ_DIV_CODE, NVL(A.CLIENT_ID, 'NONE')" + + " , INBOUND_ADAPTER, OUTBOUND_ADAPTER"; + + int savedCount = entityManager.createNativeQuery(sql) + .setParameter("searchDate", searchDate) + .executeUpdate(); + + long elapsedTime = System.currentTimeMillis() - startTime; + log.info("=== 집계 완료 === (처리 건수: {}, 소요 시간: {}ms)", savedCount, elapsedTime); + return savedCount; + } +}