인덱스 API 통계 부분 실 데이터 적용
This commit is contained in:
@@ -1,33 +1,22 @@
|
|||||||
package com.eactive.apim.portal.apps.main.controller;
|
package com.eactive.apim.portal.apps.main.controller;
|
||||||
|
|
||||||
import com.eactive.apim.portal.apps.apis.dto.ApiSpecInfoDto;
|
|
||||||
import com.eactive.apim.portal.apps.apis.service.ApiSearchFacade;
|
|
||||||
import com.eactive.apim.portal.apps.apiservice.dto.ApiGroupSearch;
|
|
||||||
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceApiDTO;
|
|
||||||
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceDTO;
|
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceDTO;
|
||||||
import com.eactive.apim.portal.apps.community.notice.dto.PortalNoticeDTO;
|
import com.eactive.apim.portal.apps.main.dto.IndexStatisticsDTO;
|
||||||
import com.eactive.apim.portal.apps.community.notice.service.PortalNoticeFacade;
|
import com.eactive.apim.portal.apps.main.service.IndexStatisticsService;
|
||||||
import com.eactive.apim.portal.apps.main.service.MainApiFacade;
|
import com.eactive.apim.portal.apps.main.service.MainApiFacade;
|
||||||
import com.eactive.apim.portal.common.util.SecurityUtil;
|
import java.util.List;
|
||||||
|
|
||||||
import com.eactive.apim.portal.portalproperty.service.PortalPropertyService;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
|
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class IndexController {
|
public class IndexController {
|
||||||
|
|
||||||
private final ApiSearchFacade apiSearchFacade;
|
|
||||||
private final MainApiFacade mainApiFacade;
|
private final MainApiFacade mainApiFacade;
|
||||||
private final PortalPropertyService portalPropertyService;
|
private final IndexStatisticsService indexStatisticsService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 메인 페이지 API는 Portal Property 에 main.service.list 에 등록된 그룹을 기준으로 API를 조회함.
|
* 메인 페이지 API는 Portal Property 에 main.service.list 에 등록된 그룹을 기준으로 API를 조회함.
|
||||||
@@ -45,29 +34,23 @@ public class IndexController {
|
|||||||
List<ApiServiceDTO> services = mainApiFacade.getOpenApiServices();
|
List<ApiServiceDTO> services = mainApiFacade.getOpenApiServices();
|
||||||
List<String> hashTags = mainApiFacade.getHashTags();
|
List<String> hashTags = mainApiFacade.getHashTags();
|
||||||
|
|
||||||
ApiGroupSearch search = new ApiGroupSearch();
|
// 인덱스 페이지 하단 통계 조회 (캐싱 적용)
|
||||||
Map<String, Object> searchResult = apiSearchFacade.searchApis(search);
|
IndexStatisticsDTO statistics = indexStatisticsService.getStatistics();
|
||||||
|
|
||||||
int selectedApiCount = (int) searchResult.get("selectedApiCount");
|
addAttributesToModel(model, services, hashTags, statistics);
|
||||||
|
|
||||||
int serviceCount = ((List<?>) searchResult.get("services")).size();
|
|
||||||
|
|
||||||
// 승인된 기업 건수
|
|
||||||
int approvedOrgCount = mainApiFacade.getApprovedOrgCount();
|
|
||||||
|
|
||||||
addAttributesToModel(model, services, hashTags, serviceCount, approvedOrgCount, selectedApiCount);
|
|
||||||
|
|
||||||
return "apps/main/index";
|
return "apps/main/index";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addAttributesToModel(Model model, List<ApiServiceDTO> apiServices, List<String> hashTags, int serviceCount, int approvedOrgCount, int selectedApiCount) {
|
private void addAttributesToModel(Model model, List<ApiServiceDTO> apiServices, List<String> hashTags, IndexStatisticsDTO statistics) {
|
||||||
|
|
||||||
model.addAttribute("services", apiServices);
|
model.addAttribute("services", apiServices);
|
||||||
model.addAttribute("hashtags", hashTags);
|
model.addAttribute("hashtags", hashTags);
|
||||||
|
|
||||||
model.addAttribute("serviceCount", serviceCount);
|
// 인덱스 페이지 하단 통계
|
||||||
model.addAttribute("approvedOrgCount", approvedOrgCount);
|
model.addAttribute("approvedOrgCount", statistics.getApprovedOrgCount());
|
||||||
model.addAttribute("selectedApiCount", selectedApiCount);
|
model.addAttribute("serviceCount", statistics.getActiveAppCount());
|
||||||
|
model.addAttribute("totalApiCount", statistics.getTotalApiCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.eactive.apim.portal.apps.main.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 인덱스 페이지 하단 통계 DTO
|
||||||
|
* - API 활용 기업: 법인으로 등록된 수의 합계 (정상 상태)
|
||||||
|
* - 서비스 이용 수: 전체 법인이 생성한 앱의 합계 (이용 가능 상태)
|
||||||
|
* - API 이용 건수: 전체 법인이 생성한 앱의 API 수의 합계 (이용 가능 상태)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class IndexStatisticsDTO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API 활용 기업 수
|
||||||
|
* 법인으로 등록되고 정상 상태인 기업의 수
|
||||||
|
*/
|
||||||
|
private int approvedOrgCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 서비스 이용 수
|
||||||
|
* 정상 상태 법인이 생성한 앱 중 이용 가능 상태인 앱의 수
|
||||||
|
*/
|
||||||
|
private int activeAppCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API 이용 건수
|
||||||
|
* 정상 상태 법인이 생성한 이용 가능 앱에 연결된 API의 총 수
|
||||||
|
*/
|
||||||
|
private int totalApiCount;
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
package com.eactive.apim.portal.apps.main.service;
|
||||||
|
|
||||||
|
import com.eactive.apim.portal.app.entity.Credential;
|
||||||
|
import com.eactive.apim.portal.app.repository.CredentialRepository;
|
||||||
|
import com.eactive.apim.portal.apps.main.dto.IndexStatisticsDTO;
|
||||||
|
import com.eactive.apim.portal.apps.user.repository.PortalOrgRepository;
|
||||||
|
import com.eactive.apim.portal.portalorg.entity.PortalOrgEnums.ApprovalStatus;
|
||||||
|
import com.eactive.apim.portal.portalorg.entity.PortalOrgEnums.OrgStatus;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 인덱스 페이지 통계 서비스
|
||||||
|
* 메모리 캐싱을 통해 DB 조회를 최소화 (1시간 TTL)
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class IndexStatisticsService {
|
||||||
|
|
||||||
|
private static final long CACHE_TTL_HOURS = 1;
|
||||||
|
private static final String APP_STATUS_ACTIVE = "1";
|
||||||
|
|
||||||
|
private final PortalOrgRepository portalOrgRepository;
|
||||||
|
private final CredentialRepository credentialRepository;
|
||||||
|
|
||||||
|
// 캐시된 통계 데이터
|
||||||
|
private volatile IndexStatisticsDTO cachedStatistics;
|
||||||
|
// 마지막 집계 시간
|
||||||
|
private volatile LocalDateTime lastAggregatedTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 인덱스 페이지 통계 조회
|
||||||
|
* 캐시가 없거나 1시간이 경과한 경우 재집계
|
||||||
|
*/
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public IndexStatisticsDTO getStatistics() {
|
||||||
|
if (shouldRefreshCache()) {
|
||||||
|
synchronized (this) {
|
||||||
|
// Double-check locking
|
||||||
|
if (shouldRefreshCache()) {
|
||||||
|
refreshStatistics();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cachedStatistics;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 캐시 갱신이 필요한지 확인
|
||||||
|
*/
|
||||||
|
private boolean shouldRefreshCache() {
|
||||||
|
if (cachedStatistics == null || lastAggregatedTime == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return LocalDateTime.now().isAfter(lastAggregatedTime.plusHours(CACHE_TTL_HOURS));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 통계 재집계
|
||||||
|
*/
|
||||||
|
private void refreshStatistics() {
|
||||||
|
log.info("인덱스 페이지 통계 집계 시작");
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. API 활용 기업 수: 정상 상태이고 승인 완료된 기관 수
|
||||||
|
int approvedOrgCount = (int) portalOrgRepository.countByOrgStatusAndApprovalStatus(
|
||||||
|
OrgStatus.ACTIVE, ApprovalStatus.COMPLETED);
|
||||||
|
|
||||||
|
// 2. 정상 상태 기관의 ID 목록 조회
|
||||||
|
List<String> activeOrgIds = portalOrgRepository.findIdsByOrgStatusAndApprovalStatus(
|
||||||
|
OrgStatus.ACTIVE, ApprovalStatus.COMPLETED);
|
||||||
|
|
||||||
|
int activeAppCount = 0;
|
||||||
|
int totalApiCount = 0;
|
||||||
|
|
||||||
|
if (!activeOrgIds.isEmpty()) {
|
||||||
|
// 3. 서비스 이용 수: 정상 기관의 이용 가능 앱 수
|
||||||
|
activeAppCount = (int) credentialRepository.countActiveAppsByOrgIds(activeOrgIds);
|
||||||
|
|
||||||
|
// 4. API 이용 건수: 정상 기관의 이용 가능 앱에 연결된 API 수
|
||||||
|
List<Credential> activeApps = credentialRepository.findActiveAppsByOrgIds(activeOrgIds);
|
||||||
|
totalApiCount = activeApps.stream()
|
||||||
|
.mapToInt(credential -> credential.getApiList() != null ? credential.getApiList().size() : 0)
|
||||||
|
.sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedStatistics = IndexStatisticsDTO.builder()
|
||||||
|
.approvedOrgCount(approvedOrgCount)
|
||||||
|
.activeAppCount(activeAppCount)
|
||||||
|
.totalApiCount(totalApiCount)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
lastAggregatedTime = LocalDateTime.now();
|
||||||
|
|
||||||
|
long elapsed = System.currentTimeMillis() - startTime;
|
||||||
|
log.info("인덱스 페이지 통계 집계 완료 - 소요시간: {}ms, 기업: {}, 앱: {}, API: {}",
|
||||||
|
elapsed, approvedOrgCount, activeAppCount, totalApiCount);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("인덱스 페이지 통계 집계 실패", e);
|
||||||
|
// 기존 캐시가 없는 경우 기본값 설정
|
||||||
|
if (cachedStatistics == null) {
|
||||||
|
cachedStatistics = IndexStatisticsDTO.builder()
|
||||||
|
.approvedOrgCount(0)
|
||||||
|
.activeAppCount(0)
|
||||||
|
.totalApiCount(0)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 캐시 강제 갱신 (관리자용)
|
||||||
|
*/
|
||||||
|
public void forceRefresh() {
|
||||||
|
synchronized (this) {
|
||||||
|
lastAggregatedTime = null;
|
||||||
|
refreshStatistics();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 캐시 만료 시간 조회
|
||||||
|
*/
|
||||||
|
public LocalDateTime getCacheExpireTime() {
|
||||||
|
if (lastAggregatedTime == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return lastAggregatedTime.plusHours(CACHE_TTL_HOURS);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,10 @@ import com.eactive.apim.portal.portalorg.entity.PortalOrgEnums;
|
|||||||
import com.eactive.eai.data.jpa.BaseRepository;
|
import com.eactive.eai.data.jpa.BaseRepository;
|
||||||
import com.eactive.eai.rms.data.EMSDataSource;
|
import com.eactive.eai.rms.data.EMSDataSource;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
|
||||||
@EMSDataSource
|
@EMSDataSource
|
||||||
public interface PortalOrgRepository extends BaseRepository<PortalOrg, String> {
|
public interface PortalOrgRepository extends BaseRepository<PortalOrg, String> {
|
||||||
@@ -20,5 +23,13 @@ public interface PortalOrgRepository extends BaseRepository<PortalOrg, String> {
|
|||||||
long countByOrgStatusAndApprovalStatus(PortalOrgEnums.OrgStatus status, PortalOrgEnums.ApprovalStatus approvalStatus);
|
long countByOrgStatusAndApprovalStatus(PortalOrgEnums.OrgStatus status, PortalOrgEnums.ApprovalStatus approvalStatus);
|
||||||
|
|
||||||
Optional<PortalOrg> findById(String orgId);
|
Optional<PortalOrg> findById(String orgId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 정상 상태이고 승인 완료된 기관의 ID 목록 조회
|
||||||
|
*/
|
||||||
|
@Query("SELECT o.id FROM PortalOrg o WHERE o.orgStatus = :status AND o.approvalStatus = :approvalStatus")
|
||||||
|
List<String> findIdsByOrgStatusAndApprovalStatus(
|
||||||
|
@Param("status") PortalOrgEnums.OrgStatus status,
|
||||||
|
@Param("approvalStatus") PortalOrgEnums.ApprovalStatus approvalStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user