gw metric

This commit is contained in:
Rinjae
2025-12-05 17:29:30 +09:00
parent 6e1e077ac6
commit cfeda1b7af
10 changed files with 866 additions and 0 deletions
@@ -201,4 +201,7 @@
<!-- 로그추축 -->
<sqlMap resource="com/eactive/eai/apim/portalstatistics/EAILog-oracle.xml" />
<!-- ObpGwMetric 시간별 거래량 지표 -->
<sqlMap resource="com/eactive/eai/apim/obp/ObpGwMetric-oracle.xml" />
</sqlMapConfig>
@@ -0,0 +1,103 @@
package com.eactive.eai.rms.data.entity.onl.apim.obp;
import com.eactive.eai.rms.common.base.SqlMapClientTemplateDao;
import org.springframework.stereotype.Repository;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* ObpGwMetric DAO - iBATIS를 사용한 로그 테이블 집계
*/
@Repository
public class ObpGwMetricDAO extends SqlMapClientTemplateDao {
/**
* EAIBZWKDSTCD 코드 목록 조회
*/
@SuppressWarnings("unchecked")
public List<String> selectAllBzwkCodes(String schemaId) {
HashMap<String, Object> paramMap = new HashMap<>();
paramMap.put("schemaId", schemaId);
return this.template.queryForList("ObpGwMetric.selectAllBzwkCodes", paramMap);
}
/**
* EAIBZWKDSTCD별 시간대 로그 집계
*/
@SuppressWarnings("unchecked")
public List<ObpGwMetricVO> selectHourlyMetricsByBzwkCode(HashMap<String, Object> paramMap) {
return this.template.queryForList("ObpGwMetric.selectHourlyMetricsByBzwkCode", paramMap);
}
/**
* API 이름 조회 (TSEAIHE01)
*/
@SuppressWarnings("unchecked")
public Map<String, String> selectApiNames(String schemaId, Set<String> apiIds) {
if (apiIds == null || apiIds.isEmpty()) {
return new HashMap<>();
}
HashMap<String, Object> paramMap = new HashMap<>();
paramMap.put("schemaId", schemaId);
paramMap.put("apiIds", apiIds);
List<Map<String, String>> resultList = this.template.queryForList("ObpGwMetric.selectApiNames", paramMap);
return resultList.stream()
.collect(Collectors.toMap(
m -> m.get("API_ID"),
m -> m.get("API_NAME"),
(v1, v2) -> v1
));
}
/**
* URI 조회 (TSEAIHS04)
*/
@SuppressWarnings("unchecked")
public Map<String, String> selectUris(String schemaId, Set<String> apiIds) {
if (apiIds == null || apiIds.isEmpty()) {
return new HashMap<>();
}
HashMap<String, Object> paramMap = new HashMap<>();
paramMap.put("schemaId", schemaId);
paramMap.put("apiIds", apiIds);
List<Map<String, String>> resultList = this.template.queryForList("ObpGwMetric.selectUris", paramMap);
return resultList.stream()
.collect(Collectors.toMap(
m -> m.get("API_ID"),
m -> m.get("URI"),
(v1, v2) -> v1
));
}
/**
* AppId 조회 (ptl_app_request) - org_id로 조회
*/
@SuppressWarnings("unchecked")
public Map<String, String> selectAppIdsByOrgIds(Set<String> orgIds) {
if (orgIds == null || orgIds.isEmpty()) {
return new HashMap<>();
}
HashMap<String, Object> paramMap = new HashMap<>();
paramMap.put("orgIds", orgIds);
List<Map<String, String>> resultList = this.template.queryForList("ObpGwMetric.selectAppIdsByOrgIds", paramMap);
return resultList.stream()
.collect(Collectors.toMap(
m -> m.get("ORG_ID"),
m -> m.get("APP_ID"),
(v1, v2) -> v1
));
}
}
@@ -0,0 +1,39 @@
package com.eactive.eai.rms.data.entity.onl.apim.obp;
import com.eactive.apim.portal.obp.entity.ObpGwMetric;
import com.eactive.eai.data.jpa.BaseRepository;
import com.eactive.eai.rms.data.EMSDataSource;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
/**
* ObpGwMetric Repository (eapim-admin 위치)
* API Gateway 시간별 거래량 지표 조회/저장을 위한 Spring Data JPA Repository
*/
@EMSDataSource
public interface ObpGwMetricRepository extends BaseRepository<ObpGwMetric, String> {
/**
* UPSERT 판단을 위한 기존 데이터 조회
* 동일한 timeslice + apiId + clientId + hostname + orgId 조합이 존재하는지 확인
*/
Optional<ObpGwMetric> findByTimesliceAndApiIdAndClientIdAndHostnameAndOrgId(
LocalDateTime timeslice,
String apiId,
String clientId,
String hostname,
String orgId
);
/**
* 특정 시간대의 모든 GwMetric 데이터 조회
*/
List<ObpGwMetric> findByTimeslice(LocalDateTime timeslice);
/**
* 특정 시간 범위의 GwMetric 데이터 조회
*/
List<ObpGwMetric> findByTimesliceBetween(LocalDateTime startTime, LocalDateTime endTime);
}
@@ -0,0 +1,54 @@
package com.eactive.eai.rms.data.entity.onl.apim.obp;
import com.eactive.apim.portal.obp.entity.ObpGwMetric;
import com.eactive.eai.rms.data.jpa.AbstractEMSDataSerivce;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
/**
* ObpGwMetric Service - 엔티티 CRUD 서비스
*/
@Service
@Transactional(transactionManager = "transactionManagerForEMS")
public class ObpGwMetricService extends AbstractEMSDataSerivce<ObpGwMetric, String, ObpGwMetricRepository> {
/**
* UPSERT 판단을 위한 기존 데이터 조회
*/
public Optional<ObpGwMetric> findByUniqueKey(LocalDateTime timeslice, String apiId, String clientId, String hostname, String orgId) {
return getRepository().findByTimesliceAndApiIdAndClientIdAndHostnameAndOrgId(
timeslice, apiId, clientId, hostname, orgId);
}
/**
* 특정 시간대의 모든 GwMetric 데이터 조회
*/
public List<ObpGwMetric> findByTimeslice(LocalDateTime timeslice) {
return getRepository().findByTimeslice(timeslice);
}
/**
* 특정 시간 범위의 GwMetric 데이터 조회
*/
public List<ObpGwMetric> findByTimesliceBetween(LocalDateTime startTime, LocalDateTime endTime) {
return getRepository().findByTimesliceBetween(startTime, endTime);
}
/**
* 엔티티 저장 (INSERT 또는 UPDATE)
*/
public ObpGwMetric save(ObpGwMetric entity) {
return getRepository().save(entity);
}
/**
* 엔티티 목록 저장
*/
public List<ObpGwMetric> saveAll(List<ObpGwMetric> entities) {
return getRepository().saveAll(entities);
}
}
@@ -0,0 +1,68 @@
package com.eactive.eai.rms.data.entity.onl.apim.obp;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* ObpGwMetric VO - 로그 집계 결과 매핑용
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ObpGwMetricVO {
private static final DateTimeFormatter TIMESLICE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHH0000000");
/** API ID (EAISVCNAME) */
private String apiId;
/** 클라이언트 ID (CLIENTID) */
private String clientId;
/** 호스트 이름 (EAISEVRINSTNCNAME) */
private String hostname;
/** 기관 ID (ORGID) */
private String orgId;
/** 타임슬라이스 문자열 (yyyyMMddHH0000000) */
private String timeslice;
/** 시도 횟수 (LOGPRCSSSERNO='100' 건수) */
private Long attemptedCount;
/** 완료 횟수 (LOGPRCSSSERNO='400' AND 성공 조건 건수) */
private Long completedCount;
/**
* timeslice 문자열을 LocalDateTime으로 변환
*/
public LocalDateTime getTimesliceAsLocalDateTime() {
if (timeslice == null || timeslice.isEmpty()) {
return null;
}
// yyyyMMddHH0000000 형식에서 앞 10자리(yyyyMMddHH)만 사용
String dateTimeStr = timeslice.substring(0, 10) + "0000";
return LocalDateTime.parse(dateTimeStr, DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
}
/**
* 집계 키 생성 (apiId + clientId + hostname + orgId)
*/
public String getAggregationKey() {
return String.join("|",
nullSafe(apiId),
nullSafe(clientId),
nullSafe(hostname),
nullSafe(orgId)
);
}
private String nullSafe(String value) {
return value != null ? value : "";
}
}
@@ -0,0 +1,97 @@
package com.eactive.eai.rms.onl.apim.obp;
import com.eactive.eai.rms.common.interceptor.InterceptorSkipController;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
/**
* ObpGwMetric REST Controller
* API Gateway 시간별 거래량 지표 조회 API
*
* <p>세션 체크를 우회하기 위해 InterceptorSkipController 인터페이스 구현</p>
*/
@Controller
public class ObpGwMetricController implements InterceptorSkipController {
private static final Logger log = LoggerFactory.getLogger(ObpGwMetricController.class);
private static final DateTimeFormatter PATH_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd_HH:mm:ss");
private final ObpGwMetricManService obpGwMetricManService;
public ObpGwMetricController(ObpGwMetricManService obpGwMetricManService) {
this.obpGwMetricManService = obpGwMetricManService;
}
/**
* 특정 시간대의 GwMetric 데이터 조회
*
* @param datetime 조회 시간대 (형식: yyyy-MM-dd_HH:mm:ss)
* @return JSON Array
*/
@RequestMapping(
value = "/kjb/gw-metrics/{datetime}",
produces = "application/json;charset=utf-8"
)
@ResponseBody
public String getGwMetrics(@PathVariable("datetime") String datetime) {
log.debug("GwMetric 조회 요청: datetime={}", datetime);
long startTime = System.currentTimeMillis();
try {
// Path 파라미터 파싱
LocalDateTime targetTime = LocalDateTime.parse(datetime, PATH_FORMATTER);
LocalDateTime timeslice = targetTime.withMinute(0).withSecond(0).withNano(0);
// 조회
List<ObpGwMetricDTO> metrics = obpGwMetricManService.findByTimeslice(timeslice);
// JSON 변환 (GSON)
Gson gson = new GsonBuilder()
.serializeNulls()
.create();
String json = gson.toJson(metrics);
// 응답 크기 계산 (UTF-8 기준)
int jsonBytes = json.getBytes(StandardCharsets.UTF_8).length;
String jsonSize = formatByteSize(jsonBytes);
long elapsed = System.currentTimeMillis() - startTime;
log.info("GwMetric 조회 완료: datetime={}, count={}, size={}, elapsed={}ms",
datetime, metrics.size(), jsonSize, elapsed);
return json;
} catch (Exception e) {
log.error("GwMetric 조회 실패: datetime={}", datetime, e);
throw e;
}
}
/**
* 바이트 크기를 읽기 쉬운 형식으로 변환
* 예: 1024 -> "1.0 KB", 1048576 -> "1.0 MB"
*/
private String formatByteSize(long bytes) {
if (bytes < 1024) {
return bytes + " B";
} else if (bytes < 1024 * 1024) {
return String.format("%.1f KB", bytes / 1024.0);
} else {
return String.format("%.1f MB", bytes / (1024.0 * 1024.0));
}
}
}
@@ -0,0 +1,98 @@
package com.eactive.eai.rms.onl.apim.obp;
import com.eactive.apim.portal.obp.entity.ObpGwMetric;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* ObpGwMetric JSON 응답 DTO
* Entity 주석에 명시된 JSON Key 사용
*/
@Data
public class ObpGwMetricDTO {
private static final DateTimeFormatter ISO8601_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'+0000'");
@SerializedName("appKey")
private String clientId;
@SerializedName("clusterHostname")
private String hostname;
@SerializedName("timeslice")
private String timeslice;
@SerializedName("apiName")
private String apiName;
@SerializedName("apiRoutingUri")
private String uri;
@SerializedName("attemptedCount")
private Long attemptedCount;
@SerializedName("completedCount")
private Long completedCount;
// EAPIM ID
@SerializedName("apiIdByEapim")
private String apiId;
@SerializedName("appIdByEapim")
private String appId;
@SerializedName("orgIdByEapim")
private String orgId;
// AS-IS 호환용 필드
@SerializedName("appId")
private String appIdByCaPortal;
@SerializedName("portalOrgId")
private String orgIdByCaPortal;
@SerializedName("portalApiId")
private String apiIdByCaPortal;
@SerializedName("partnerCode")
private String partnerCode;
@SerializedName("apiId")
private String apiIdByCa;
/**
* Entity → DTO 변환
*/
public static ObpGwMetricDTO from(ObpGwMetric entity) {
ObpGwMetricDTO dto = new ObpGwMetricDTO();
dto.setClientId(entity.getClientId());
dto.setHostname(entity.getHostname());
dto.setTimeslice(formatTimeslice(entity.getTimeslice()));
dto.setApiName(entity.getApiName());
dto.setUri(entity.getUri());
dto.setAttemptedCount(entity.getAttemptedCount());
dto.setCompletedCount(entity.getCompletedCount());
dto.setApiId(entity.getApiId());
dto.setAppId(entity.getAppId());
dto.setOrgId(entity.getOrgId());
dto.setAppIdByCaPortal(entity.getAppIdByCaPortal());
dto.setOrgIdByCaPortal(entity.getOrgIdByCaPortal());
dto.setApiIdByCaPortal(entity.getApiIdByCaPortal());
dto.setPartnerCode(entity.getPartnerCode());
dto.setApiIdByCa(entity.getApiIdByCa());
return dto;
}
/**
* timeslice를 ISO8601 형식 + UTC 시간대로 변환
* 예: 2025-12-02T01:00:00+0000
*/
private static String formatTimeslice(LocalDateTime timeslice) {
if (timeslice == null) return null;
return timeslice.format(ISO8601_FORMATTER);
}
}
@@ -0,0 +1,257 @@
package com.eactive.eai.rms.onl.apim.obp;
import com.eactive.apim.portal.obp.entity.ObpGwMetric;
import com.eactive.eai.rms.common.base.BaseService;
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.apim.obp.ObpGwMetricDAO;
import com.eactive.eai.rms.data.entity.onl.apim.obp.ObpGwMetricService;
import com.eactive.eai.rms.data.entity.onl.apim.obp.ObpGwMetricVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;
/**
* ObpGwMetric Man Service - 비즈니스 로직
* API Gateway 로그 데이터를 집계하여 ObpGwMetric 엔티티로 저장
*/
@Service
@Transactional(transactionManager = "transactionManagerForEMS")
public class ObpGwMetricManService extends BaseService {
private static final Logger log = LoggerFactory.getLogger(ObpGwMetricManService.class);
/**
* 집계 범위 시간 (단위: 시간)
* - 1: 직전 1시간만 집계
* - 5: 직전 5시간까지 각각 1시간 단위로 집계
*/
private static final int AGGREGATION_HOUR_RANGE = 1;
private static final DateTimeFormatter TIMESLICE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHH0000000");
private static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
private static final DateTimeFormatter YYYYMMDD_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
private final ObpGwMetricDAO obpGwMetricDAO;
private final ObpGwMetricService obpGwMetricService;
public ObpGwMetricManService(ObpGwMetricDAO obpGwMetricDAO, ObpGwMetricService obpGwMetricService) {
this.obpGwMetricDAO = obpGwMetricDAO;
this.obpGwMetricService = obpGwMetricService;
}
/**
* 다중 시간대 집계 실행 (Job에서 호출)
*/
public void executeHourlyAggregation() {
LocalDateTime now = LocalDateTime.now();
LocalDateTime baseHour = now.truncatedTo(ChronoUnit.HOURS); // 현재 시간의 정각
log.info("========== ObpGwMetric 집계 시작 ==========");
log.info("실행 시간: {}, AGGREGATION_HOUR_RANGE: {}", now, AGGREGATION_HOUR_RANGE);
for (int i = 1; i <= AGGREGATION_HOUR_RANGE; i++) {
LocalDateTime targetHour = baseHour.minusHours(i);
log.info("집계 대상 시간대: {}", targetHour);
createHourlyData(targetHour);
}
log.info("========== ObpGwMetric 집계 완료 ==========");
}
/**
* 1시간 단위 ObpGwMetric 데이터 생성 (UPSERT)
*/
public void createHourlyData(LocalDateTime targetHour) {
long jobStartTime = System.currentTimeMillis();
log.info("ObpGwMetric 집계 시작: targetHour={}", targetHour);
try {
String timeslice = targetHour.format(TIMESLICE_FORMATTER);
String yyyymmdd = targetHour.format(YYYYMMDD_FORMATTER);
String tableName = CommonUtil.getLogTable(yyyymmdd, true);
String startTime = targetHour.format(TIMESTAMP_FORMATTER);
String endTime = targetHour.plusHours(1).minusNanos(1).format(TIMESTAMP_FORMATTER);
// ═══════════════════════════════════════════════════════════════════
// 1단계: GW DB에서 EAIBZWKDSTCD 코드 목록 조회
// ═══════════════════════════════════════════════════════════════════
long step1Start = System.currentTimeMillis();
DataSourceType apigwDataSourceType = DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.APIGW);
DataSourceContextHolder.setDataSourceType(apigwDataSourceType);
List<String> bzwkCodes = obpGwMetricDAO.selectAllBzwkCodes(apigwDataSourceType.getSchema());
log.info("[1단계] EAIBZWKDSTCD 코드 조회 완료: count={}, elapsed={}ms",
bzwkCodes.size(), System.currentTimeMillis() - step1Start);
// ═══════════════════════════════════════════════════════════════════
// 2단계: 코드별 개별 쿼리 실행 후 결과 병합
// ═══════════════════════════════════════════════════════════════════
long step2Start = System.currentTimeMillis();
Map<String, ObpGwMetricVO> aggregatedMap = new HashMap<>();
int totalLogCount = 0;
for (String bzwkCode : bzwkCodes) {
long codeStart = System.currentTimeMillis();
HashMap<String, Object> paramMap = new HashMap<>();
paramMap.put("schemaId", apigwDataSourceType.getSchema());
paramMap.put("tableName", tableName);
paramMap.put("bzwkCode", bzwkCode);
paramMap.put("startTime", startTime);
paramMap.put("endTime", endTime);
paramMap.put("timeslice", timeslice);
List<ObpGwMetricVO> partialResult = obpGwMetricDAO.selectHourlyMetricsByBzwkCode(paramMap);
for (ObpGwMetricVO vo : partialResult) {
String key = vo.getAggregationKey();
aggregatedMap.merge(key, vo, this::mergeMetricVO);
}
if (!partialResult.isEmpty()) {
log.debug("[2단계] bzwkCode={}, 집계건수={}, elapsed={}ms",
bzwkCode, partialResult.size(), System.currentTimeMillis() - codeStart);
}
totalLogCount += partialResult.size();
}
List<ObpGwMetricVO> aggregatedList = new ArrayList<>(aggregatedMap.values());
log.info("[2단계] 로그 집계 완료: 원본건수={}, 병합후건수={}, elapsed={}ms",
totalLogCount, aggregatedList.size(), System.currentTimeMillis() - step2Start);
if (aggregatedList.isEmpty()) {
log.warn("집계 대상 데이터 없음: targetHour={}", targetHour);
return;
}
// ═══════════════════════════════════════════════════════════════════
// 3단계: 부가 정보 조회 (apiName, uri) - GW DB
// ═══════════════════════════════════════════════════════════════════
long step3Start = System.currentTimeMillis();
Set<String> apiIds = aggregatedList.stream()
.map(ObpGwMetricVO::getApiId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
Map<String, String> apiNameMap = obpGwMetricDAO.selectApiNames(apigwDataSourceType.getSchema(), apiIds);
Map<String, String> uriMap = obpGwMetricDAO.selectUris(apigwDataSourceType.getSchema(), apiIds);
log.info("[3단계] 부가정보 조회 완료: apiCount={}, elapsed={}ms",
apiIds.size(), System.currentTimeMillis() - step3Start);
// ═══════════════════════════════════════════════════════════════════
// 4단계: 지표 DB로 전환 후 appId 조회 및 UPSERT
// ═══════════════════════════════════════════════════════════════════
long step4Start = System.currentTimeMillis();
DataSourceContextHolder.setDataSourceType(
DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.MONITORING));
Set<String> orgIds = aggregatedList.stream()
.map(ObpGwMetricVO::getOrgId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
Map<String, String> appIdMap = obpGwMetricDAO.selectAppIdsByOrgIds(orgIds);
int insertCount = 0;
int updateCount = 0;
for (ObpGwMetricVO vo : aggregatedList) {
LocalDateTime timesliceDateTime = vo.getTimesliceAsLocalDateTime();
Optional<ObpGwMetric> existing = obpGwMetricService.findByUniqueKey(
timesliceDateTime,
vo.getApiId(),
vo.getClientId(),
vo.getHostname(),
vo.getOrgId()
);
if (existing.isPresent()) {
// UPDATE: 기존 데이터 갱신
ObpGwMetric entity = existing.get();
entity.setAttemptedCount(vo.getAttemptedCount());
entity.setCompletedCount(vo.getCompletedCount());
entity.setApiName(apiNameMap.get(vo.getApiId()));
entity.setUri(uriMap.get(vo.getApiId()));
entity.setAppId(appIdMap.get(vo.getOrgId()));
entity.setUpdateCount(entity.getUpdateCount() + 1); // 보정 횟수 증가
obpGwMetricService.save(entity);
updateCount++;
} else {
// INSERT: 신규 데이터 생성
ObpGwMetric entity = convertToEntity(vo, apiNameMap, uriMap, appIdMap);
obpGwMetricService.save(entity);
insertCount++;
}
}
log.info("[4단계] UPSERT 완료: INSERT={}, UPDATE={}, elapsed={}ms",
insertCount, updateCount, System.currentTimeMillis() - step4Start);
long totalElapsed = System.currentTimeMillis() - jobStartTime;
log.info("ObpGwMetric 집계 완료: targetHour={}, 총 소요시간={}ms ({}초)",
targetHour, totalElapsed, totalElapsed / 1000.0);
} catch (Exception e) {
log.error("ObpGwMetric 집계 실패: targetHour={}", targetHour, e);
throw e;
}
}
/**
* ObpGwMetricVO 병합 (count 누적)
*/
private ObpGwMetricVO mergeMetricVO(ObpGwMetricVO existing, ObpGwMetricVO incoming) {
existing.setAttemptedCount(
(existing.getAttemptedCount() != null ? existing.getAttemptedCount() : 0L) +
(incoming.getAttemptedCount() != null ? incoming.getAttemptedCount() : 0L)
);
existing.setCompletedCount(
(existing.getCompletedCount() != null ? existing.getCompletedCount() : 0L) +
(incoming.getCompletedCount() != null ? incoming.getCompletedCount() : 0L)
);
return existing;
}
/**
* VO → Entity 변환
*/
private ObpGwMetric convertToEntity(ObpGwMetricVO vo, Map<String, String> apiNameMap,
Map<String, String> uriMap, Map<String, String> appIdMap) {
ObpGwMetric entity = new ObpGwMetric();
entity.setClientId(vo.getClientId());
entity.setHostname(vo.getHostname());
entity.setTimeslice(vo.getTimesliceAsLocalDateTime());
entity.setApiId(vo.getApiId());
entity.setOrgId(vo.getOrgId());
entity.setAttemptedCount(vo.getAttemptedCount());
entity.setCompletedCount(vo.getCompletedCount());
entity.setApiName(apiNameMap.get(vo.getApiId()));
entity.setUri(uriMap.get(vo.getApiId()));
entity.setAppId(appIdMap.get(vo.getOrgId()));
entity.setUpdateCount(0);
return entity;
}
/**
* 특정 시간대의 GwMetric 데이터 조회 (REST API용)
*/
public List<ObpGwMetricDTO> findByTimeslice(LocalDateTime timeslice) {
DataSourceContextHolder.setDataSourceType(
DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.MONITORING));
List<ObpGwMetric> entities = obpGwMetricService.findByTimeslice(timeslice);
return entities.stream()
.map(ObpGwMetricDTO::from)
.collect(Collectors.toList());
}
}
@@ -0,0 +1,56 @@
package com.eactive.eai.rms.onl.common.service;
import com.eactive.eai.rms.common.context.MonitoringContext;
import com.eactive.eai.rms.common.util.CommonUtil;
import com.eactive.eai.rms.onl.apim.obp.ObpGwMetricManService;
import com.eactive.eai.rms.onl.common.util.DateUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.SchedulerException;
import org.springframework.context.ApplicationContext;
/**
* ObpGwMetric Hour Job - Quartz Job
* API Gateway 로그 데이터를 1시간 단위로 집계하여 ObpGwMetric 테이블에 저장
*
* <p>Cron Schedule 권장:</p>
* <ul>
* <li>기본: 0 5 * * * ? (매시 05분 1회 실행)</li>
* <li>보정 (15분): 0 5,20,35,50 * * * ? (15분 간격 4회 실행)</li>
* <li>보정 (10분): 0 5,15,25,35,45,55 * * * ? (10분 간격 6회 실행)</li>
* </ul>
*/
public class ObpGwMetricHourJob implements Job {
private static final Logger log = LoggerFactory.getLogger(ObpGwMetricHourJob.class);
private transient MonitoringContext monitoringContext;
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
log.info("*** START ObpGwMetricHourJob run({})", CommonUtil.getToday("yyyy-MM-dd HH:mm"));
ApplicationContext appContext = null;
try {
appContext = (ApplicationContext) context.getScheduler().getContext().get("applicationContext");
} catch (SchedulerException e) {
log.error("applicationContext get module error", e);
return;
}
monitoringContext = (MonitoringContext) appContext.getBean("monitoringContext");
ObpGwMetricManService obpGwMetricManService = appContext.getBean(ObpGwMetricManService.class);
try {
obpGwMetricManService.executeHourlyAggregation();
} catch (Exception e) {
log.error("ObpGwMetricHourJob execution failed", e);
throw new JobExecutionException(e);
}
log.info("*** END ObpGwMetricHourJob run({})", DateUtil.getDateTime("yyyy-MM-dd HH:mm"));
}
}
@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd">
<sqlMap namespace="ObpGwMetric">
<!-- EAIBZWKDSTCD 코드 목록 조회 (TSEAICM01) -->
<statement id="selectAllBzwkCodes" parameterClass="java.util.HashMap" resultClass="java.lang.String">
SELECT EAIBZWKDSTCD
FROM $schemaId$.TSEAICM01
ORDER BY EAIBZWKDSTCD
</statement>
<!-- 로그 집계 결과 매핑 -->
<resultMap id="obpGwMetricResult" class="com.eactive.eai.rms.data.entity.onl.apim.obp.ObpGwMetricVO">
<result property="apiId" column="API_ID"/>
<result property="clientId" column="CLIENT_ID"/>
<result property="hostname" column="HOSTNAME"/>
<result property="orgId" column="ORG_ID"/>
<result property="timeslice" column="TIMESLICE"/>
<result property="attemptedCount" column="ATTEMPTED_COUNT"/>
<result property="completedCount" column="COMPLETED_COUNT"/>
</resultMap>
<!--
EAIBZWKDSTCD별 시간대 로그 집계 (Index Range Scan 활용)
에러 판단 기준 (DailyBackup-oracle.xml 참고):
- 에러 조건: RSPNSERRCDNAME LIKE 'RE%' OR RSPNSERRCDNAME LIKE 'RF%'
- 성공 조건: 위 조건의 반대 (NULL 또는 RE%/RF%가 아닌 경우)
성능 최적화:
- PK 인덱스 (EAIBZWKDSTCD, MSGDPSTYMS, EAISVCSERNO, LOGPRCSSSERNO) 활용
- EAIBZWKDSTCD를 고정하여 Index Range Scan 보장
-->
<statement id="selectHourlyMetricsByBzwkCode" parameterClass="java.util.HashMap" resultMap="obpGwMetricResult">
SELECT
EAISVCNAME AS API_ID,
CLIENTID AS CLIENT_ID,
EAISEVRINSTNCNAME AS HOSTNAME,
ORGID AS ORG_ID,
'$timeslice$' AS TIMESLICE,
SUM(CASE WHEN LOGPRCSSSERNO = '100' THEN 1 ELSE 0 END) AS ATTEMPTED_COUNT,
SUM(CASE WHEN LOGPRCSSSERNO = '400'
AND (RSPNSERRCDNAME IS NULL
OR (RSPNSERRCDNAME NOT LIKE 'RE%' AND RSPNSERRCDNAME NOT LIKE 'RF%'))
THEN 1 ELSE 0 END) AS COMPLETED_COUNT
FROM $schemaId$.$tableName$
WHERE EAIBZWKDSTCD = #bzwkCode#
AND MSGDPSTYMS BETWEEN #startTime# AND #endTime#
GROUP BY EAISVCNAME, CLIENTID, EAISEVRINSTNCNAME, ORGID
HAVING SUM(CASE WHEN LOGPRCSSSERNO = '100' THEN 1 ELSE 0 END) > 0
OR SUM(CASE WHEN LOGPRCSSSERNO = '400' THEN 1 ELSE 0 END) > 0
</statement>
<!-- API 이름 조회 (TSEAIHE01) -->
<statement id="selectApiNames" parameterClass="java.util.HashMap" resultClass="java.util.HashMap">
SELECT
EAISVCNAME AS API_ID,
EAISVCDESC AS API_NAME
FROM $schemaId$.TSEAIHE01
WHERE EAISVCNAME IN
<iterate property="apiIds" open="(" close=")" conjunction=",">
#apiIds[]#
</iterate>
</statement>
<!-- URI 조회 (TSEAIHS04) -->
<statement id="selectUris" parameterClass="java.util.HashMap" resultClass="java.util.HashMap">
SELECT
EAISVCNAME AS API_ID,
BZWKSVCKEYNAME AS URI
FROM $schemaId$.TSEAIHS04
WHERE EAISVCNAME IN
<iterate property="apiIds" open="(" close=")" conjunction=",">
#apiIds[]#
</iterate>
</statement>
<!-- AppId 조회 (ptl_app_request) - org_id로 조회 -->
<statement id="selectAppIdsByOrgIds" parameterClass="java.util.HashMap" resultClass="java.util.HashMap">
SELECT
ORG_ID,
ID AS APP_ID
FROM PTL_APP_REQUEST
WHERE ORG_ID IN
<iterate property="orgIds" open="(" close=")" conjunction=",">
#orgIds[]#
</iterate>
</statement>
</sqlMap>