From 7b8646ab945e67b0a91265004420e39ea0fd2567 Mon Sep 17 00:00:00 2001
From: Rinjae
Date: Wed, 19 Aug 2026 14:31:12 +0900
Subject: [PATCH] =?UTF-8?q?=EC=9E=90=EB=8F=99=20=ED=83=90=EC=A7=80=20?=
=?UTF-8?q?=EB=AF=B8=EA=B2=8C=EC=8B=9C=20=ED=8F=AC=ED=95=A8=20=EC=84=A4?=
=?UTF-8?q?=EC=A0=95=20=EC=B6=94=EA=B0=80=20-=20=EC=84=A4=EC=A0=95=20?=
=?UTF-8?q?=ED=82=A4=20=ED=99=95=EC=9D=B8=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?=
=?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20=EB=AF=B8=EA=B2=8C=EC=8B=9C=20=EC=9D=B8?=
=?UTF-8?q?=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EC=B2=98=EB=A6=AC=20?=
=?UTF-8?q?=EC=A0=81=EC=9A=A9=20-=20=EA=B2=8C=EC=8B=9C/=EB=AF=B8=EA=B2=8C?=
=?UTF-8?q?=EC=8B=9C=20API=20=ED=95=84=ED=84=B0=EB=A7=81=20=EB=A1=9C?=
=?UTF-8?q?=EC=A7=81=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20=EC=98=A4=EB=A5=98?=
=?UTF-8?q?=20=EC=B2=98=EB=A6=AC=20=EA=B0=95=ED=99=94?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../apistatus/ApiStatusDetectionService.java | 80 +++++++++++++++++--
1 file changed, 75 insertions(+), 5 deletions(-)
diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusDetectionService.java b/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusDetectionService.java
index bac9dd9..a897496 100644
--- a/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusDetectionService.java
+++ b/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusDetectionService.java
@@ -49,6 +49,12 @@ import com.eactive.eai.rms.data.entity.onl.djb.apistatus.ApiNameRow;
* 검수·게시해야 개발자포탈에 노출되며, {@code PUBLISH}면 탐지 즉시 노출된다.
* {@code OFF} 여도 이미 열려있는 장애의 복구 처리(ERROR_END/DELAY_END)는 계속 동작한다.
*
+ * 개발자포탈 미게시 인터페이스 포함 여부는 {@code djb.apistatus.auto-incident-include-unpublished}
+ * (기본 {@code false}) 로 정한다. 기본값이면 게시 API 만 이슈에 담고,
+ * 게시 API 가 하나도 없는 탐지는 기록하지 않는다. {@code true}면 미게시 인터페이스도
+ * 포함해 건수로 집계된다. 복구 처리는 이 설정과 무관하게
+ * 이미 이슈에 담긴 API 를 대상으로 동작한다.
+ *
* 점검(CONTROL_*) 은 관리자가 공지사항으로 등록한 점검 일정이 원천이다
* ({@code ApiStatusRepository.findApiStatusEvents} 가 INCIDENT 기간을 읽어 CTRL_YN 을 판정).
* 따라서 CONTROL 이벤트를 다시 기록하면 순환이 되므로 기록하지 않는다.
@@ -96,6 +102,12 @@ public class ApiStatusDetectionService {
/** 오탐이 그대로 고객에게 노출되지 않도록 기본은 검수(DRAFT) */
private static final String DEFAULT_AUTO_INCIDENT_MODE = MODE_DRAFT;
+ /** 자동 탐지 이슈에 개발자포탈 미게시 인터페이스도 포함할지 여부 - true / false */
+ public static final String KEY_INCLUDE_UNPUBLISHED = "djb.apistatus.auto-incident-include-unpublished";
+
+ /** 포탈 게시 API 장애만 고객 공지 대상이 되도록 기본은 제외(false) */
+ private static final String DEFAULT_INCLUDE_UNPUBLISHED = "false";
+
@Autowired
private DjbApistatusIncidentRepository incidentRepository;
@@ -208,6 +220,19 @@ public class ApiStatusDetectionService {
}
boolean autoPublish = MODE_PUBLISH.equals(mode);
+ // 미게시 인터페이스 제외 설정(false)이면 게시 API 만 남기고, 하나도 없으면 기록하지 않는다
+ if (!resolveIncludeUnpublished()) {
+ List published = filterPublished(apiIds);
+ if (published.isEmpty()) {
+ log.info("게시 API 없음 - 미게시 인터페이스 제외 설정으로 기록하지 않음: {} {}", event, apiIds);
+ return;
+ }
+ if (published.size() < apiIds.size()) {
+ log.info("미게시 인터페이스 제외: {} → {}", apiIds, published);
+ }
+ apiIds = published;
+ }
+
LocalDateTime now = LocalDateTime.now();
String interfaceId = makeInterfaceId(event, apiIds, now);
@@ -314,6 +339,27 @@ public class ApiStatusDetectionService {
}
}
+ /**
+ * 자동 탐지 이슈에 개발자포탈 미게시 인터페이스도 포함할지 여부.
+ *
+ * PTL_PROPERTY({@code Portal} / {@code djb.apistatus.auto-incident-include-unpublished}) 를 읽는다.
+ * 최초 접근 시 기본값으로 row 가 자동 생성된다. 기본 {@code false} - 게시 API 만 이슈에 담는다.
+ * 명시적으로 {@code true}/{@code Y} 를 설정한 경우에만 미게시 인터페이스를 포함하며,
+ * 알 수 없는 값·조회 실패는 기본값 쪽(false)으로 떨어뜨린다.
+ */
+ private boolean resolveIncludeUnpublished() {
+ try {
+ String value = portalPropertyService.getOrCreateProperty(
+ PROPERTY_GROUP, KEY_INCLUDE_UNPUBLISHED, DEFAULT_INCLUDE_UNPUBLISHED,
+ "API 상태 자동 탐지 이슈에 포탈 미게시 인터페이스 포함 여부(true/false)");
+ String normalized = StringUtils.trimToEmpty(value);
+ return "true".equalsIgnoreCase(normalized) || "Y".equalsIgnoreCase(normalized);
+ } catch (Exception e) {
+ log.warn("미게시 인터페이스 포함 여부 조회 실패 - 기본값 {} 적용", DEFAULT_INCLUDE_UNPUBLISHED, e);
+ return false;
+ }
+ }
+
private void mergeIntoOpenIncident(DjbApistatusIncident incident, String event, IncidentKind kind,
List apiIds, LocalDateTime now, boolean autoPublish) {
Long incidentId = incident.getIncidentId();
@@ -540,22 +586,46 @@ public class ApiStatusDetectionService {
}
/**
- * 게시 인터페이스 판정. 조회에 실패하면 전부 비게시로 보아 ID·명칭 노출을 막는다.
+ * 게시 인터페이스 ID 집합 조회. 실패 처리는 호출부 정책에 맡긴다.
*
* 판정 조건 두 개가 서로 다른 스키마에 있어 조회를 나눈다 - API 그룹 편성은 AGWAPP,
* 스펙 존재는 EMSAPP(PTL_API_SPEC_INFO). 한 쿼리로 조인하면 tenant=APIGW 커넥션이
* EMSAPP 를 못 봐서 ORA-00942 가 난다.
*/
+ private Set lookupPublishedApiIds(List apiIds) {
+ List groupedIds = apiStatusRepository.findDisplayedGroupApiIds(apiIds);
+ return groupedIds.isEmpty()
+ ? Collections.emptySet()
+ : new HashSet<>(apiSpecInfoRepository.findApiIdsIn(groupedIds));
+ }
+
+ /**
+ * 게시 API 만 남긴다 (미게시 인터페이스 제외 설정용). 판정에 실패하면 전체를 유지한다 -
+ * 제외 판단이 안 되는 상황에서 진짜 장애 기록까지 버리는 쪽이 더 위험하고,
+ * 명칭 노출은 표기 단계({@link #splitPublished})가 따로 막는다.
+ */
+ private List filterPublished(List apiIds) {
+ try {
+ Set publishedIds = lookupPublishedApiIds(apiIds);
+ return apiIds.stream()
+ .filter(publishedIds::contains)
+ .collect(Collectors.toList());
+ } catch (Exception e) {
+ log.warn("게시 API 판정 실패 - 미게시 인터페이스 제외 없이 전체 포함", e);
+ return apiIds;
+ }
+ }
+
+ /**
+ * 게시 인터페이스 판정. 조회에 실패하면 전부 비게시로 보아 ID·명칭 노출을 막는다.
+ */
private ApiSplit splitPublished(List apiIds) {
if (apiIds == null || apiIds.isEmpty()) {
return new ApiSplit(Collections.emptyList(), 0);
}
Set publishedIds;
try {
- List groupedIds = apiStatusRepository.findDisplayedGroupApiIds(apiIds);
- publishedIds = groupedIds.isEmpty()
- ? Collections.emptySet()
- : new HashSet<>(apiSpecInfoRepository.findApiIdsIn(groupedIds));
+ publishedIds = lookupPublishedApiIds(apiIds);
} catch (Exception e) {
// 실패 시 노출하는 쪽으로 기울면 내부 인터페이스가 고객 화면에 새어 나간다
log.warn("게시 API 판정 실패 - 전부 GW 인터페이스로 묶어 표기", e);