diff --git a/src/main/java/com/eactive/apim/gateway/data/statistics/entity/GwApiStatus.java b/src/main/java/com/eactive/apim/gateway/data/statistics/entity/GwApiStatus.java new file mode 100644 index 0000000..0d18b29 --- /dev/null +++ b/src/main/java/com/eactive/apim/gateway/data/statistics/entity/GwApiStatus.java @@ -0,0 +1,36 @@ +package com.eactive.apim.gateway.data.statistics.entity; + +import lombok.Data; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import java.time.LocalDateTime; + +/** + * API 상태 모니터링 결과 (AGWAPP.API_STATUS). + * + *
eapim-admin 의 {@code ApiStatusMonitorJob}(Quartz, 매분)이 상태 변화가 있을 때만 upsert 한다. + * 포털은 읽기 전용으로 "마지막 상태 갱신 시각" 표시에 사용한다.
+ */ +@Entity +@Table(name = "API_STATUS") +@Data +public class GwApiStatus { + + /** EAI 서비스명 */ + @Id + @Column(name = "EAISVCNAME", length = 30) + private String eaisvcname; + + /** N 정상 / C 점검 / D 지연 / E 장애 */ + @Column(name = "STATUS_CODE", length = 1) + private String statusCode; + + @Column(name = "MODIFIED_BY", length = 20) + private String modifiedBy; + + @Column(name = "MODIFIED_DATE") + private LocalDateTime modifiedDate; +} diff --git a/src/main/java/com/eactive/apim/gateway/data/statistics/repository/GwApiStatusRepository.java b/src/main/java/com/eactive/apim/gateway/data/statistics/repository/GwApiStatusRepository.java new file mode 100644 index 0000000..5b2e994 --- /dev/null +++ b/src/main/java/com/eactive/apim/gateway/data/statistics/repository/GwApiStatusRepository.java @@ -0,0 +1,17 @@ +package com.eactive.apim.gateway.data.statistics.repository; + +import com.eactive.apim.gateway.data.statistics.entity.GwApiStatus; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.Repository; + +import java.time.LocalDateTime; +import java.util.Optional; + +public interface GwApiStatusRepository extends Repository장애/점검 데이터는 관리자 공지사항(PTL_NOTICE + DJB_APISTATUS_INCIDENT)과 + * 자동 탐지(eapim-admin ApiStatusDetectionService)가 채운다. 본 컨트롤러는 읽기 전용이다.
+ * + *비로그인 사용자도 모두 조회 가능하며, "내 API 현황"만 로그인을 요구한다.
+ */ +@Slf4j +@Controller +@RequestMapping("/apistatus") +@RequiredArgsConstructor +public class ApiStatusController { + + private static final int DEFAULT_RECENT_ISSUE_SIZE = 5; + private static final int MAX_PAGE_SIZE = 50; + + private final ApiStatusQueryService apiStatusQueryService; + private final ApiStatusUptimeService uptimeService; + private final ApiStatusIssueHistoryService issueHistoryService; + private final MyApiStatusQueryService myApiStatusQueryService; + private final ApiStatusCatalogService catalogService; + + /** P1 - API Status 메인 */ + @GetMapping + public ModelAndView index() { + ModelAndView mav = new ModelAndView("djb/apistatus/index"); + mav.addObject("windowDays", ApiStatusSupport.DEFAULT_WINDOW_DAYS); + mav.addObject("authenticated", SecurityUtil.isAuthenticated()); + mav.addObject("lastStatusUpdatedAt", catalogService.getLastStatusUpdatedAt()); + return mav; + } + + /** + * P8 - 전체 이슈 이력. 날짜를 지정하지 않으면 조회 기간(90일) 전체를 본다. + * 날짜 선택 가능 범위는 서버 기준 일자로 내려 클라이언트 timezone 차이를 없앤다. + */ + @GetMapping("/issues") + public ModelAndView issues( + @RequestParam(value = "date", required = false) + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date, + @RequestParam(value = "apiId", required = false) String apiId, + @RequestParam(value = "kind", required = false) String kind) { + + LocalDate today = ApiStatusSupport.now().toLocalDate(); + + ModelAndView mav = new ModelAndView("djb/apistatus/issues"); + mav.addObject("windowDays", ApiStatusSupport.DEFAULT_WINDOW_DAYS); + mav.addObject("today", today); + mav.addObject("minDate", today.minusDays(ApiStatusSupport.DEFAULT_WINDOW_DAYS - 1L)); + mav.addObject("selectedDate", date); + mav.addObject("selectedApiId", apiId); + mav.addObject("selectedKind", kind); + return mav; + } + + /** 이슈 이력 API 필터 - 현재 사용자가 조회 가능한 API 목록 */ + @GetMapping("/apis.json") + @ResponseBody + public ListPTL_NOTICE 와의 조인은 두 가지 역할을 한다. + * (1) 관리자가 미게시(USE_YN='N')한 장애/점검을 숨긴다. + * (2) 공지가 삭제된 고아 장애 행을 자연히 제외한다 (물리 FK 없음 - ADR-F10).
+ */ +public interface ApiStatusIncidentQueryRepository extends Repository"오픈 API" 목록 화면과 같은 조회 경로({@link ApiSearchFacade#searchApis})를 쓴다. + * 즉 API 그룹(AGWAPP.API_GROUP + API_GROUP_API)에 편성된 API 중 + * PTL_API_SPEC_INFO 에 스펙이 있고 현재 사용자에게 공개된 것만 나온다.
+ */ + @SuppressWarnings("unchecked") + public List이슈 건수가 연 1천 건 미만이므로 90일 구간을 메모리에서 합산해도 부담이 없다. + * 같은 시간대에 겹치는 이슈는 구간 합집합으로 계산해 중복 차감을 막는다.
+ */ +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ApiStatusUptimeService { + + private final ApiStatusIncidentQueryRepository incidentQueryRepository; + private final DjbApistatusIncidentApiRepository incidentApiRepository; + + /** P2 - 90일 가동률 */ + public List대상 API 는 소속 기관이 발급받은 앱(Credential)의 API 목록(ptl_credential_api)이다.
+ */ +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class MyApiStatusQueryService { + + private final CredentialRepository credentialRepository; + private final ApiStatusIncidentQueryRepository incidentQueryRepository; + private final DjbApistatusIncidentApiRepository incidentApiRepository; + private final ApiStatusUptimeService uptimeService; + + public List' + escapeHtml(issue.summary || '점검 안내') + '
'; + } else { + const items = (issue.timeline || []).map(function (entry) { + return '' + escapeHtml(entry.labelKo || '진행 상황') + '
' + + '' + escapeHtml(entry.body) + '
' + + '' + escapeHtml(formatDateTime(entry.eventAt)) + '
' + + '' + escapeHtml(issue.summary || '상세 진행 내역이 등록되지 않았습니다.') + '
'; + } + + const period = formatDateTime(issue.startedAt) + + (issue.endAt ? ' ~ ' + formatDateTime(issue.endAt) : ' ~ 진행 중') + + (issue.durationMinutes != null ? ' (' + formatDuration(issue.durationMinutes) + ')' : ''); + + return '모든 API 가 정상 동작 중입니다.
점검 완료
' + + (issue.summary ? '' + escapeHtml(issue.summary) + '
' : '') + + '' + escapeHtml(formatDateTime(issue.startedAt)) + + ' ~ ' + escapeHtml(formatDateTime(issue.endAt)) + '
' + + '' + escapeHtml(entry.labelKo || '진행 상황') + '
' + + '' + escapeHtml(entry.body) + '
' + + '' + escapeHtml(formatDateTime(entry.eventAt)) + '
' + + '' + escapeHtml(issue.summary || '상세 진행 내역이 등록되지 않았습니다.') + '
'; + } + + const duration = issue.durationMinutes != null ? ' · ' + formatDuration(issue.durationMinutes) : ''; + + return '막대 1개 = 하루 · 장애나 점검이 있던 날만 색으로 표시됩니다. + 막대를 클릭하면 그 날짜의 이슈 이력으로 이동합니다.
+이슈가 있던 날짜만 색으로 표시됩니다. 막대를 클릭하면 그 날짜로 필터링합니다.
+