Merge branch 'master' of https://git.eactive.synology.me:8090/eapim/djbank/eapim-admin.git
eapim-admin CI / build (push) Has been cancelled

This commit is contained in:
eastargh
2026-08-10 09:14:16 +09:00
6 changed files with 118 additions and 14 deletions
@@ -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<EAIMessageEntity, String, EAIMessageRepository> {
/** 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<Tuple> selectList(EAIMessageUISearch eaiMessageUISearch, Pageable pageable, String sortname, String sortorder) {
QEAIMessageEntity qeaiMessageEntity = QEAIMessageEntity.eAIMessageEntity;
@@ -188,10 +200,52 @@ public class EAIMessageService extends AbstractDataService<EAIMessageEntity, Str
predicate
.and(qStdInfo.apifullpath.contains(eaiMessageUISearch.getSearchApiFullPath()));
}
if ("Y".equals(eaiMessageUISearch.getSearchApiSpecYn())) {
predicate.and(apiSpecRegisteredExpression(qeaiMessageEntity.eaisvcname));
}
if ("Y".equals(eaiMessageUISearch.getSearchPortalDisplayYn())) {
predicate.and(qeaiMessageEntity.eaisvcname.in(displayedGroupApiIdSubQuery()));
}
return predicate;
}
/**
* OpenAPI Spec 등록 여부 조건.
*
* <p>PTL_API_SPEC_INFO 는 EMSAPP 스키마라 TSEAIHE01 쿼리에 조인하면 ORA-00942 다. 스펙이 있는
* API ID 를 EMS 커넥션에서 따로 읽어 IN 절로 넘긴다.</p>
*/
private BooleanExpression apiSpecRegisteredExpression(StringPath eaiSvcNamePath) {
List<String> 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<String> 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<RoutingEaiMsgDto> findAll(Pageable pageable, RoutingEaiMsgSearch routingEaiMsgSearch) {
List<RoutingEaiMsgDto> result = routingEaiMsgDtoList(pageable, routingEaiMsgSearch);
@@ -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;
}
@@ -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·명칭 노출을 막는다.
*
* <p>판정 조건 두 개가 서로 다른 스키마에 있어 조회를 나눈다 - API 그룹 편성은 AGWAPP,
* 스펙 존재는 EMSAPP(PTL_API_SPEC_INFO). 한 쿼리로 조인하면 tenant=APIGW 커넥션이
* EMSAPP 를 못 봐서 ORA-00942 가 난다.</p>
*/
private ApiSplit splitPublished(List<String> apiIds) {
if (apiIds == null || apiIds.isEmpty()) {
return new ApiSplit(Collections.emptyList(), 0);
}
Set<String> publishedIds;
try {
publishedIds = new HashSet<>(apiStatusRepository.findPublishedApiIds(apiIds));
List<String> groupedIds = apiStatusRepository.findDisplayedGroupApiIds(apiIds);
publishedIds = groupedIds.isEmpty()
? Collections.emptySet()
: new HashSet<>(apiSpecInfoRepository.findApiIdsIn(groupedIds));
} catch (Exception e) {
// 실패 시 노출하는 쪽으로 기울면 내부 인터페이스가 고객 화면에 새어 나간다
log.warn("게시 API 판정 실패 - 전부 GW 인터페이스로 묶어 표기", e);
@@ -147,22 +147,25 @@ public interface ApiStatusRepository extends BaseRepository<ApiStatus, String> {
List<ApiNameRow> findApiNames(@Param("apiIds") Collection<String> apiIds);
/**
* 개발자포탈에 게시되는 인터페이스(= API) 만 추린다.
* 노출 중인 API 그룹에 편성된 인터페이스 ID.
*
* <p>GW 인터페이스는 전부 탐지 대상이지만 개발자포탈에 노출되는 것은 그 일부다.
* 자동 초안(제목·공지 본문·타임라인)에 게시되지 않은 인터페이스 ID 가 그대로 박히면
* 고객 화면에 내부 인터페이스가 드러나므로, 여기서 걸러 나머지는 건수로만 표기한다.</p>
*
* <p>판정 기준은 포털 "오픈 API" 목록과 같은 두 조건이다 - 노출 중인 API 그룹에 편성돼 있고
* PTL_API_SPEC_INFO 에 스펙이 있을 것. 역할·소속에 따른 사용자별 공개 범위는 admin 에
* 사용자 컨텍스트가 없어 적용하지 않는다(= 포털 화면이 한 번 더 좁힌다).</p>
* <p>게시 판정은 포털 "오픈 API" 목록과 같은 두 조건이다 - 노출 중인 API 그룹에 편성돼 있고
* PTL_API_SPEC_INFO 에 스펙이 있을 것. 이 쿼리는 앞 조건(AGWAPP)만 본다. 뒤 조건은
* EMSAPP 스키마라 {@code ApiSpecInfoRepository.findApiIdsIn} 으로 따로 조회해 교집합을 잡는다
* - 이 리포지토리는 tenant=APIGW(AGWAPP) 커넥션으로 도는 탓에 한 쿼리로 조인하면 ORA-00942 다.</p>
*
* <p>역할·소속에 따른 사용자별 공개 범위는 admin 에 사용자 컨텍스트가 없어 적용하지 않는다
* (= 포털 화면이 한 번 더 좁힌다).</p>
*/
@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<String> findPublishedApiIds(@Param("apiIds") Collection<String> apiIds);
+ " AND G.DISPLAY_YN = '1'")
List<String> findDisplayedGroupApiIds(@Param("apiIds") Collection<String> apiIds);
}