diff --git a/WebContent/jsp/onl/apim/apigroup/apiGroupManPopup.jsp b/WebContent/jsp/onl/apim/apigroup/apiGroupManPopup.jsp index 24e579b..c286573 100644 --- a/WebContent/jsp/onl/apim/apigroup/apiGroupManPopup.jsp +++ b/WebContent/jsp/onl/apim/apigroup/apiGroupManPopup.jsp @@ -72,13 +72,22 @@ }); } - function detail(){ + // 체크박스는 name 을 search* 로 두면 getSearchForJqgrid 가 해제 상태에서도 value 를 담아버린다 + // (input[type!=radio][name^=search] 를 checked 여부와 무관하게 수집) → 여기서 직접 붙인다 + function getPopupSearchData() { var postData = getSearchForJqgrid("cmd","LIST"); //jqgrid에서는 object 로 + postData.searchApiSpecYn = $("#chkApiSpec").is(":checked") ? "Y" : ""; + postData.searchPortalDisplayYn = $("#chkPortalDisplay").is(":checked") ? "Y" : ""; + return postData; + } + + function detail(){ + var postData = getPopupSearchData(); $("#grid").setGridParam({ url:url,postData: postData }).trigger("reloadGrid"); } function search(){ - var postData = getSearchForJqgrid("cmd","LIST"); //jqgrid에서는 object 로 + var postData = getPopupSearchData(); $("#grid").setGridParam({ url:url,postData: postData ,page:1 }).trigger("reloadGrid"); } function ioFormatter(cellvalue,options,rowObject){ @@ -97,7 +106,7 @@ function list(){ detail() - var gridPostData = getSearchForJqgrid("cmd","LIST"); //jqgrid에서는 object 로 + var gridPostData = getPopupSearchData(); // gridPostData.searchApiEnableYn = 'Y'; var urlParams = new URLSearchParams(window.location.search); var selectedApiIds = urlParams.get("selectedApiIds"); @@ -189,6 +198,9 @@ $("select[name=searchEaiBzwkDstcd]").change(function() { search(); }); + $("#chkApiSpec, #chkPortalDisplay").change(function() { + search(); + }); $("#btn_search").click(function(){ search(); }); @@ -247,6 +259,18 @@ + + 포탈 노출 + + +    + + +
diff --git a/WebContent/jsp/onl/apim/portalnotice/portalNoticeManDetail.jsp b/WebContent/jsp/onl/apim/portalnotice/portalNoticeManDetail.jsp index 6bee1a8..f47bc19 100644 --- a/WebContent/jsp/onl/apim/portalnotice/portalNoticeManDetail.jsp +++ b/WebContent/jsp/onl/apim/portalnotice/portalNoticeManDetail.jsp @@ -189,8 +189,11 @@ // AuthorizeInterceptor 가 정상적으로 권한 인식. window.open 직접 호출 시 권한 부족 발생. // 본 페이지는 summernote 본문 에디터 등 다른 iframe 이 함께 존재할 수 있어 // frames[0] 가정은 위험 → DOM 에서 .ui-dialog 내 iframe 중 src=apiGroupMan 인 것을 식별. + // 공지 영향 API 는 포탈에 실제 노출되는 것만 고르면 되므로 두 조건을 켠 채로 연다. + // (타 화면에서 여는 팝업은 파라미터가 없어 해제 상태 — 팝업 기본값은 건드리지 않는다) var selectedIds = affectedApis.map(function(a) { return a.apiId; }).join(','); - var url2 = apiPopupUrl + '?cmd=POPUP&selectedApiIds=' + encodeURIComponent(selectedIds); + var url2 = apiPopupUrl + '?cmd=POPUP&searchApiSpecYn=Y&searchPortalDisplayYn=Y' + + '&selectedApiIds=' + encodeURIComponent(selectedIds); var args = {}; showModal(url2, args, 1280, 860, function () { var doc = args.self && args.self.document; diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/eaimsg/EAIMessageService.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/eaimsg/EAIMessageService.java index 28dcfa5..b99beba 100644 --- a/src/main/java/com/eactive/eai/rms/data/entity/onl/eaimsg/EAIMessageService.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/eaimsg/EAIMessageService.java @@ -13,7 +13,10 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import com.eactive.apim.portal.apispec.repository.ApiSpecInfoRepository; import com.eactive.eai.data.entity.onl.adapter.QAdapter; +import com.eactive.eai.data.entity.onl.apim.apigroup.QApiGroup; +import com.eactive.eai.data.entity.onl.apim.apigroup.QApiGroupApi; import com.eactive.eai.data.entity.onl.authserver.QScopeEntity; import com.eactive.eai.data.entity.onl.logger.QStatLog; import com.eactive.eai.data.entity.onl.message.EAIMessageEntity; @@ -31,6 +34,9 @@ import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.core.types.dsl.StringPath; +import com.querydsl.jpa.JPAExpressions; +import com.querydsl.jpa.JPQLQuery; import com.querydsl.jpa.impl.JPAQuery; @Service @@ -38,8 +44,14 @@ import com.querydsl.jpa.impl.JPAQuery; @RequiredArgsConstructor public class EAIMessageService extends AbstractDataService { + /** Oracle IN 절 표현식 개수 상한. 넘기면 ORA-01795 라 나눠 OR 로 잇는다 */ + private static final int ORACLE_IN_LIMIT = 1000; + private final UserBusinessService userBusinessService; + /** EMSAPP(PTL_API_SPEC_INFO) 조회용. 다른 스키마라 EAI 메시지 쿼리에 조인할 수 없다 */ + private final ApiSpecInfoRepository apiSpecInfoRepository; + public Page selectList(EAIMessageUISearch eaiMessageUISearch, Pageable pageable, String sortname, String sortorder) { QEAIMessageEntity qeaiMessageEntity = QEAIMessageEntity.eAIMessageEntity; @@ -188,10 +200,52 @@ public class EAIMessageService extends AbstractDataServicePTL_API_SPEC_INFO 는 EMSAPP 스키마라 TSEAIHE01 쿼리에 조인하면 ORA-00942 다. 스펙이 있는 + * API ID 를 EMS 커넥션에서 따로 읽어 IN 절로 넘긴다.

+ */ + private BooleanExpression apiSpecRegisteredExpression(StringPath eaiSvcNamePath) { + List specApiIds = apiSpecInfoRepository.findAllApiIds(); + if (specApiIds.isEmpty()) { + // in(빈 리스트) 는 JPQL 이 깨지므로 항상 거짓인 조건으로 대체한다 + return Expressions.ONE.eq(Expressions.TWO); + } + + BooleanExpression expression = null; + for (int from = 0; from < specApiIds.size(); from += ORACLE_IN_LIMIT) { + int to = Math.min(from + ORACLE_IN_LIMIT, specApiIds.size()); + BooleanExpression chunk = eaiSvcNamePath.in(specApiIds.subList(from, to)); + expression = (expression == null) ? chunk : expression.or(chunk); + } + return expression; + } + + /** 노출 중인(display_yn='1') API 그룹에 편성된 API ID. API_GROUP 은 EAI 메시지와 같은 스키마다 */ + private JPQLQuery displayedGroupApiIdSubQuery() { + QApiGroupApi qApiGroupApi = QApiGroupApi.apiGroupApi; + QApiGroup qApiGroup = QApiGroup.apiGroup; + + return JPAExpressions + .select(qApiGroupApi.id.apiId) + .from(qApiGroupApi) + .join(qApiGroupApi.apiGroup, qApiGroup) + .where(qApiGroup.displayYn.eq("1")); + } + public Page findAll(Pageable pageable, RoutingEaiMsgSearch routingEaiMsgSearch) { List result = routingEaiMsgDtoList(pageable, routingEaiMsgSearch); diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/eaimsg/EAIMessageUISearch.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/eaimsg/EAIMessageUISearch.java index e15a116..9f38deb 100644 --- a/src/main/java/com/eactive/eai/rms/data/entity/onl/eaimsg/EAIMessageUISearch.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/eaimsg/EAIMessageUISearch.java @@ -20,4 +20,10 @@ public class EAIMessageUISearch { private String searchApiFullPath; + /** "Y" 면 PTL_API_SPEC_INFO 에 OpenAPI Spec 이 등록된 API 만 조회 */ + private String searchApiSpecYn; + + /** "Y" 면 노출 중인(display_yn='1') API 그룹에 편성된 API 만 조회 */ + private String searchPortalDisplayYn; + } 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 4233c68..bac9dd9 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 @@ -22,6 +22,7 @@ import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import com.eactive.apim.portal.apispec.repository.ApiSpecInfoRepository; import com.eactive.apim.portal.common.entity.Auditable; import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncident; import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncidentApi; @@ -110,6 +111,10 @@ public class ApiStatusDetectionService { @Autowired private ApiStatusRepository apiStatusRepository; + /** EMSAPP(PTL_API_SPEC_INFO) 조회용. AGWAPP 커넥션으로 도는 apiStatusRepository 로는 못 읽는다 */ + @Autowired + private ApiSpecInfoRepository apiSpecInfoRepository; + @Autowired private PortalPropertyService portalPropertyService; @@ -534,14 +539,23 @@ public class ApiStatusDetectionService { } } - /** 게시 인터페이스 판정. 조회에 실패하면 전부 비게시로 보아 ID·명칭 노출을 막는다. */ + /** + * 게시 인터페이스 판정. 조회에 실패하면 전부 비게시로 보아 ID·명칭 노출을 막는다. + * + *

판정 조건 두 개가 서로 다른 스키마에 있어 조회를 나눈다 - API 그룹 편성은 AGWAPP, + * 스펙 존재는 EMSAPP(PTL_API_SPEC_INFO). 한 쿼리로 조인하면 tenant=APIGW 커넥션이 + * EMSAPP 를 못 봐서 ORA-00942 가 난다.

+ */ private ApiSplit splitPublished(List apiIds) { if (apiIds == null || apiIds.isEmpty()) { return new ApiSplit(Collections.emptyList(), 0); } Set publishedIds; try { - publishedIds = new HashSet<>(apiStatusRepository.findPublishedApiIds(apiIds)); + List groupedIds = apiStatusRepository.findDisplayedGroupApiIds(apiIds); + publishedIds = groupedIds.isEmpty() + ? Collections.emptySet() + : new HashSet<>(apiSpecInfoRepository.findApiIdsIn(groupedIds)); } catch (Exception e) { // 실패 시 노출하는 쪽으로 기울면 내부 인터페이스가 고객 화면에 새어 나간다 log.warn("게시 API 판정 실패 - 전부 GW 인터페이스로 묶어 표기", e); diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusRepository.java b/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusRepository.java index f02b77b..fd9b611 100644 --- a/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusRepository.java +++ b/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusRepository.java @@ -147,22 +147,25 @@ public interface ApiStatusRepository extends BaseRepository { List findApiNames(@Param("apiIds") Collection apiIds); /** - * 개발자포탈에 게시되는 인터페이스(= API) 만 추린다. + * 노출 중인 API 그룹에 편성된 인터페이스 ID. * *

GW 인터페이스는 전부 탐지 대상이지만 개발자포탈에 노출되는 것은 그 일부다. * 자동 초안(제목·공지 본문·타임라인)에 게시되지 않은 인터페이스 ID 가 그대로 박히면 * 고객 화면에 내부 인터페이스가 드러나므로, 여기서 걸러 나머지는 건수로만 표기한다.

* - *

판정 기준은 포털 "오픈 API" 목록과 같은 두 조건이다 - 노출 중인 API 그룹에 편성돼 있고 - * PTL_API_SPEC_INFO 에 스펙이 있을 것. 역할·소속에 따른 사용자별 공개 범위는 admin 에 - * 사용자 컨텍스트가 없어 적용하지 않는다(= 포털 화면이 한 번 더 좁힌다).

+ *

게시 판정은 포털 "오픈 API" 목록과 같은 두 조건이다 - 노출 중인 API 그룹에 편성돼 있고 + * PTL_API_SPEC_INFO 에 스펙이 있을 것. 이 쿼리는 앞 조건(AGWAPP)만 본다. 뒤 조건은 + * EMSAPP 스키마라 {@code ApiSpecInfoRepository.findApiIdsIn} 으로 따로 조회해 교집합을 잡는다 + * - 이 리포지토리는 tenant=APIGW(AGWAPP) 커넥션으로 도는 탓에 한 쿼리로 조인하면 ORA-00942 다.

+ * + *

역할·소속에 따른 사용자별 공개 범위는 admin 에 사용자 컨텍스트가 없어 적용하지 않는다 + * (= 포털 화면이 한 번 더 좁힌다).

*/ @Query(nativeQuery = true, value = " SELECT DISTINCT GA.API_ID" + " FROM API_GROUP_API GA" + " JOIN API_GROUP G ON G.ID = GA.API_GROUP_ID" + " WHERE GA.API_ID IN (:apiIds)" - + " AND G.DISPLAY_YN = '1'" - + " AND EXISTS (SELECT 1 FROM EMSAPP.PTL_API_SPEC_INFO S WHERE S.API_ID = GA.API_ID)") - List findPublishedApiIds(@Param("apiIds") Collection apiIds); + + " AND G.DISPLAY_YN = '1'") + List findDisplayedGroupApiIds(@Param("apiIds") Collection apiIds); } \ No newline at end of file