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);