Merge remote-tracking branch 'origin/master'
eapim-admin CI / build (push) Has been cancelled

This commit is contained in:
Rinjae
2026-08-24 13:16:45 +09:00
4 changed files with 133 additions and 102 deletions
@@ -15,6 +15,12 @@
<jsp:include page="/jsp/common/include/css.jsp"/> <jsp:include page="/jsp/common/include/css.jsp"/>
<jsp:include page="/jsp/common/include/script.jsp"/> <jsp:include page="/jsp/common/include/script.jsp"/>
<script src="<c:url value="/addon/echarts/echarts.min.js"/>"></script> <script src="<c:url value="/addon/echarts/echarts.min.js"/>"></script>
<style>
/* 합계(footer) 행 높이를 본문 행 높이(23px)와 동일하게 맞춤 */
#gbox_grid .ui-jqgrid-ftable tr.footrow td {
height: 23px;
}
</style>
<script language="javascript"> <script language="javascript">
var url = '<c:url value="/onl/kjb/statistics/apiUseStatsMan.json"/>'; var url = '<c:url value="/onl/kjb/statistics/apiUseStatsMan.json"/>';
var url_view = '<c:url value="/onl/kjb/statistics/apiUseStatsMan.view"/>'; var url_view = '<c:url value="/onl/kjb/statistics/apiUseStatsMan.view"/>';
@@ -63,7 +69,7 @@
function search() { function search() {
var postData = getPostData("cmd", "LIST"); var postData = getPostData("cmd", "LIST");
if (postData) { if (postData) {
$("#grid").setGridParam({ url: url, postData: postData, page: 1 }).trigger("reloadGrid"); $("#grid").setGridParam({ url: url, postData: postData }).trigger("reloadGrid");
} }
} }
@@ -198,19 +204,17 @@
{ name: 'maxRespTime', align: 'right', width: '90', formatter: numberFormatter, sortable: false } { name: 'maxRespTime', align: 'right', width: '90', formatter: numberFormatter, sortable: false }
], ],
jsonReader: { repeatitems: false }, jsonReader: { repeatitems: false },
pager: $('#pager'), height: 500,
page: '${param.page}',
rowNum: '${rmsDefaultRowNum}',
autoheight: true,
height: 'auto',
autowidth: true, autowidth: true,
viewrecords: true, footerrow: true,
rowList: eval('[${rmsDefaultRowList}]'), userDataOnFooter: true,
loadComplete: function(d) { loadComplete: function(d) {
var colModel = $(this).getGridParam("colModel"); var colModel = $(this).getGridParam("colModel");
for (var i = 0; i < colModel.length; i++) { for (var i = 0; i < colModel.length; i++) {
$(this).setColProp(colModel[i].name, { sortable: false }); $(this).setColProp(colModel[i].name, { sortable: false });
} }
$(".ui-jqgrid-ftable td").css({ "font-weight": "bold", "background-color": "#f5f5f5" });
} }
}); });
} }
@@ -308,7 +312,6 @@
<div> <div>
<table id="grid"></table> <table id="grid"></table>
<div id="pager"></div>
</div> </div>
@@ -1,6 +1,7 @@
package com.eactive.eai.rms.common.vo; package com.eactive.eai.rms.common.vo;
import java.util.List; import java.util.List;
import java.util.Map;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
@@ -10,7 +11,9 @@ public class GridResponse<T> {
private Integer page; private Integer page;
private Long records; private Long records;
private List<T> rows; private List<T> rows;
/** jqGrid footerrow + userDataOnFooter 용 하단 합계행 데이터 (colModel name -> value) */
private Map<String, Object> userdata;
public GridResponse() { public GridResponse() {
} }
@@ -53,7 +56,15 @@ public class GridResponse<T> {
public void setRows(List<T> rows) { public void setRows(List<T> rows) {
this.rows = rows; this.rows = rows;
} }
public Map<String, Object> getUserdata() {
return userdata;
}
public void setUserdata(Map<String, Object> userdata) {
this.userdata = userdata;
}
public GridResponse(List<T> rows) { public GridResponse(List<T> rows) {
this.rows = rows; this.rows = rows;
this.total = 1; this.total = 1;
@@ -3,12 +3,12 @@ package com.eactive.eai.rms.ext.djb.statistics;
import java.io.IOException; import java.io.IOException;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@@ -35,9 +35,30 @@ public class ApiUseStatsController {
} }
@PostMapping(value = "/onl/kjb/statistics/apiUseStatsMan.json", params = "cmd=LIST") @PostMapping(value = "/onl/kjb/statistics/apiUseStatsMan.json", params = "cmd=LIST")
public ResponseEntity<GridResponse<ApiStatsUI>> selectList(ApiStatsSearch search, Pageable pageable) { public ResponseEntity<GridResponse<ApiStatsUI>> selectList(ApiStatsSearch search) {
Page<ApiStatsUI> page = service.selectList(search, pageable); List<ApiStatsUI> list = service.selectList(search);
return ResponseEntity.ok(new GridResponse<>(page));
GridResponse<ApiStatsUI> response = new GridResponse<>(list);
ApiStatsUI total = service.selectTotal(search);
if (total != null) {
response.setUserdata(toFooterData(total));
}
return ResponseEntity.ok(response);
}
private Map<String, Object> toFooterData(ApiStatsUI total) {
Map<String, Object> footer = new LinkedHashMap<>();
footer.put("orgName", "합계");
footer.put("totalCnt", total.getTotalCnt());
footer.put("successCnt", total.getSuccessCnt());
footer.put("successRate", total.getSuccessRate());
footer.put("failRate", total.getFailRate());
footer.put("timeoutCnt", total.getTimeoutCnt());
footer.put("systemErrCnt", total.getSystemErrCnt());
footer.put("avgRespTime", total.getAvgRespTime());
footer.put("minRespTime", total.getMinRespTime());
footer.put("maxRespTime", total.getMaxRespTime());
return footer;
} }
@PostMapping(value = "/onl/kjb/statistics/apiUseStatsMan.json", params = "cmd=EXCEL_EXPORT") @PostMapping(value = "/onl/kjb/statistics/apiUseStatsMan.json", params = "cmd=EXCEL_EXPORT")
@@ -57,6 +78,12 @@ public class ApiUseStatsController {
return; return;
} }
ApiStatsUI total = service.selectTotal(search);
if (total != null) {
total.setOrgName("합계");
uiList.add(total);
}
String fileName = generateFileName(search); String fileName = generateFileName(search);
log.info("Generating Excel file: {}", fileName); log.info("Generating Excel file: {}", fileName);
@@ -10,9 +10,6 @@ import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext; import javax.persistence.PersistenceContext;
import javax.persistence.Query; import javax.persistence.Query;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -33,23 +30,6 @@ public class ApiUseStatsService
@PersistenceContext @PersistenceContext
private EntityManager entityManager; private EntityManager entityManager;
@SuppressWarnings("unchecked")
public Page<ApiStatsUI> selectList(ApiStatsSearch search, Pageable pageable) {
Query dataQuery = getDataQuery(search);
dataQuery.setFirstResult((int) pageable.getOffset());
dataQuery.setMaxResults(pageable.getPageSize());
List<Object[]> rows = dataQuery.getResultList();
List<ApiStatsUI> results = rows.stream()
.map(this::toVO)
.collect(Collectors.toList());
long records = rows.size() > 0 ? StringUtils.toLong(rows.get(0)[0]) : 0;
return new PageImpl<>(results, pageable, records);
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public List<ApiStatsUI> selectList(ApiStatsSearch search) { public List<ApiStatsUI> selectList(ApiStatsSearch search) {
Query dataQuery = getDataQuery(search); Query dataQuery = getDataQuery(search);
@@ -59,15 +39,65 @@ public class ApiUseStatsService
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
/**
* 하단 합계(Total)행 조회 (grid footer 용, 그룹핑 없이 전체 집계)
*/
@SuppressWarnings("unchecked")
public ApiStatsUI selectTotal(ApiStatsSearch search) {
Query totalQuery = getTotalQuery(search);
List<Object[]> rows = totalQuery.getResultList();
return rows.isEmpty() ? null : toVO(rows.get(0));
}
private Query getDataQuery(ApiStatsSearch search) { private Query getDataQuery(ApiStatsSearch search) {
StringBuilder where = buildNativeWhere(search); StringBuilder where = buildNativeWhere(search);
String dataSql = ""; String dataSql = "";
if ("ORG".equals(search.getSearchType())) { if ("ORG".equals(search.getSearchType())) {
dataSql = "SELECT COUNT(*) OVER () AS RECORDS " dataSql = "SELECT NVL(B.ORGNAME, D.ORG_NAME) AS ORGNAME";
+ ", NVL(B.ORGNAME, D.ORG_NAME) AS ORGNAME" dataSql += getCommonQuery(search);
+ ", SUM(A.TOTAL_CNT)" dataSql += " GROUP BY NVL(B.ORGNAME, D.ORG_NAME)";
dataSql += " ORDER BY ORGNAME";
}
else if ("API".equals(search.getSearchType())) {
dataSql = "SELECT A.API_NAME ";
dataSql += getCommonQuery(search);
dataSql += " GROUP BY A.API_NAME";
dataSql += " ORDER BY API_NAME";
}
else if ("DATE".equals(search.getSearchType())) {
dataSql = "SELECT TO_CHAR(STAT_TIME,'YYYY-MM-DD') AS STAT_TIME ";
dataSql += getCommonQuery(search);
dataSql += " GROUP BY TO_CHAR(STAT_TIME,'YYYY-MM-DD')";
dataSql += " ORDER BY STAT_TIME";
}
Query dataQuery = entityManager.createNativeQuery(dataSql);
bindParams(dataQuery, search);
return dataQuery;
}
private Query getTotalQuery(ApiStatsSearch search) {
StringBuilder where = buildNativeWhere(search);
String totalSql;
totalSql = "SELECT '합계' AS ORGNAME ";
totalSql += getCommonQuery(search);
Query totalQuery = entityManager.createNativeQuery(totalSql);
bindParams(totalQuery, search);
return totalQuery;
}
private String getCommonQuery(ApiStatsSearch search) {
String dataSql = ", SUM(A.TOTAL_CNT)"
+ ", SUM(A.SUCCESS_CNT)" + ", SUM(A.SUCCESS_CNT)"
+ ", CASE WHEN SUM(A.TOTAL_CNT) > 0 THEN SUM(A.SUCCESS_CNT) * 100 / SUM(A.TOTAL_CNT) ELSE NULL END" + ", CASE WHEN SUM(A.TOTAL_CNT) > 0 THEN SUM(A.SUCCESS_CNT) * 100 / SUM(A.TOTAL_CNT) ELSE NULL END"
+ ", CASE WHEN SUM(A.TOTAL_CNT) > 0 THEN (SUM(A.TOTAL_CNT) - SUM(A.SUCCESS_CNT)) * 100 / SUM(A.TOTAL_CNT) ELSE NULL END" + ", CASE WHEN SUM(A.TOTAL_CNT) > 0 THEN (SUM(A.TOTAL_CNT) - SUM(A.SUCCESS_CNT)) * 100 / SUM(A.TOTAL_CNT) ELSE NULL END"
@@ -87,7 +117,7 @@ public class ApiUseStatsService
+ " , A.SYSTEM_ERR_CNT" + " , A.SYSTEM_ERR_CNT"
+ " , A.MIN_RESP_TIME" + " , A.MIN_RESP_TIME"
+ " , A.MAX_RESP_TIME" + " , A.MAX_RESP_TIME"
+ " , CASE WHEN API_NAME LIKE '%1' THEN SUBSTR(INBOUND_ADAPTER,2,3) ELSE SUBSTR(OUTBOUND_ADAPTER,2,3) END AS ORG_CODE" + " , CASE WHEN API_NAME LIKE '%1' THEN SUBSTR(OUTBOUND_ADAPTER,2,3) ELSE SUBSTR(INBOUND_ADAPTER,2,3) END AS ORG_CODE"
+ " FROM API_STATS_DAY A " + " FROM API_STATS_DAY A "
+ " WHERE A.STAT_TIME >= :searchStartDate " + " WHERE A.STAT_TIME >= :searchStartDate "
+ " AND A.STAT_TIME <= :searchEndDate "; + " AND A.STAT_TIME <= :searchEndDate ";
@@ -105,80 +135,40 @@ public class ApiUseStatsService
if (StringUtils.isNotBlank(search.getSearchOrgName())) { if (StringUtils.isNotBlank(search.getSearchOrgName())) {
dataSql += " AND UPPER(NVL(B.ORGNAME, D.ORG_NAME)) LIKE :searchOrgName "; dataSql += " AND UPPER(NVL(B.ORGNAME, D.ORG_NAME)) LIKE :searchOrgName ";
} }
dataSql += " GROUP BY NVL(B.ORGNAME, D.ORG_NAME)" return dataSql;
+ " ORDER BY ORGNAME"; }
}
else if ("API".equals(search.getSearchType())) {
dataSql = "SELECT COUNT(*) OVER () AS RECORDS " private void bindParams(Query query, ApiStatsSearch search) {
+ ", A.API_NAME" query.setParameter("searchStartDate", search.getParsedStartDate());
+ ", SUM(A.TOTAL_CNT)" query.setParameter("searchEndDate", search.getParsedEndDate());
+ ", SUM(A.SUCCESS_CNT)"
+ ", CASE WHEN SUM(A.TOTAL_CNT) > 0 THEN SUM(A.SUCCESS_CNT) * 100 / SUM(A.TOTAL_CNT) ELSE NULL END"
+ ", CASE WHEN SUM(A.TOTAL_CNT) > 0 THEN (SUM(A.TOTAL_CNT) - SUM(A.SUCCESS_CNT)) * 100 / SUM(A.TOTAL_CNT) ELSE NULL END"
+ ", SUM(A.TIMEOUT_CNT)"
+ ", SUM(A.SYSTEM_ERR_CNT)"
+ ", CASE WHEN SUM(A.TOTAL_CNT) > 0 THEN TRUNC(SUM(A.TOTAL_CNT * A.AVG_RESP_TIME) / SUM(A.TOTAL_CNT)) ELSE NULL END"
+ ", MIN(A.MIN_RESP_TIME)"
+ ", MAX(A.MAX_RESP_TIME)"
+ " FROM API_STATS_DAY A "
+ " LEFT OUTER JOIN TSEAIAU01 B ON A.CLIENT_ID = B.CLIENTID"
+ where
+ " GROUP BY A.API_NAME"
+ " ORDER BY API_NAME";
}
else if ("DATE".equals(search.getSearchType())) {
dataSql = "SELECT COUNT(*) OVER () AS RECORDS "
+ ", TO_CHAR(STAT_TIME,'YYYY-MM-DD') AS STAT_TIME "
+ ", SUM(A.TOTAL_CNT)"
+ ", SUM(A.SUCCESS_CNT)"
+ ", CASE WHEN SUM(A.TOTAL_CNT) > 0 THEN SUM(A.SUCCESS_CNT) * 100 / SUM(A.TOTAL_CNT) ELSE NULL END"
+ ", CASE WHEN SUM(A.TOTAL_CNT) > 0 THEN (SUM(A.TOTAL_CNT) - SUM(A.SUCCESS_CNT)) * 100 / SUM(A.TOTAL_CNT) ELSE NULL END"
+ ", SUM(A.TIMEOUT_CNT)"
+ ", SUM(A.SYSTEM_ERR_CNT)"
+ ", CASE WHEN SUM(A.TOTAL_CNT) > 0 THEN TRUNC(SUM(A.TOTAL_CNT * A.AVG_RESP_TIME) / SUM(A.TOTAL_CNT)) ELSE NULL END"
+ ", MIN(A.MIN_RESP_TIME)"
+ ", MAX(A.MAX_RESP_TIME)"
+ " FROM API_STATS_DAY A "
+ " LEFT OUTER JOIN TSEAIAU01 B ON A.CLIENT_ID = B.CLIENTID"
+ where
+ " GROUP BY TO_CHAR(STAT_TIME,'YYYY-MM-DD')"
+ " ORDER BY STAT_TIME";
}
Query dataQuery = entityManager.createNativeQuery(dataSql);
dataQuery.setParameter("searchStartDate", search.getParsedStartDate());
dataQuery.setParameter("searchEndDate", search.getParsedEndDate());
if (StringUtils.isNotBlank(search.getSearchApiName())) { if (StringUtils.isNotBlank(search.getSearchApiName())) {
dataQuery.setParameter("searchApiName", "%" + search.getSearchApiName().toUpperCase() + "%"); query.setParameter("searchApiName", "%" + search.getSearchApiName().toUpperCase() + "%");
} }
if (StringUtils.isNotBlank(search.getSearchOrgName())) { if (StringUtils.isNotBlank(search.getSearchOrgName())) {
dataQuery.setParameter("searchOrgName", "%" + search.getSearchOrgName().toUpperCase() + "%"); query.setParameter("searchOrgName", "%" + search.getSearchOrgName().toUpperCase() + "%");
} }
return dataQuery;
} }
/** /**
* Native Query 결과를 ApiStatsUI로 변환 * Native Query 결과를 ApiStatsUI로 변환
*/ */
private ApiStatsUI toVO(Object[] row) { private ApiStatsUI toVO(Object[] row) {
ApiStatsUI vo = new ApiStatsUI(); ApiStatsUI vo = new ApiStatsUI();
vo.setOrgName(StringUtils.toString(row[1])); vo.setOrgName(StringUtils.toString(row[0]));
vo.setTotalCnt(StringUtils.toLong(row[2])); vo.setTotalCnt(StringUtils.toLong(row[1]));
vo.setSuccessCnt(StringUtils.toLong(row[3])); vo.setSuccessCnt(StringUtils.toLong(row[2]));
vo.setSuccessRate(StringUtils.toDecimal(row[4]).setScale(2, RoundingMode.HALF_UP)); vo.setSuccessRate(StringUtils.toDecimal(row[3]).setScale(2, RoundingMode.HALF_UP));
vo.setFailRate(StringUtils.toDecimal(row[5]).setScale(2, RoundingMode.HALF_UP)); vo.setFailRate(StringUtils.toDecimal(row[4]).setScale(2, RoundingMode.HALF_UP));
vo.setTimeoutCnt(StringUtils.toLong(row[6])); vo.setTimeoutCnt(StringUtils.toLong(row[5]));
vo.setSystemErrCnt(StringUtils.toLong(row[7])); vo.setSystemErrCnt(StringUtils.toLong(row[6]));
vo.setAvgRespTime(StringUtils.toDecimal(row[8])); vo.setAvgRespTime(StringUtils.toDecimal(row[7]));
vo.setMinRespTime(StringUtils.toDecimal(row[9])); vo.setMinRespTime(StringUtils.toDecimal(row[8]));
vo.setMaxRespTime(StringUtils.toDecimal(row[10])); vo.setMaxRespTime(StringUtils.toDecimal(row[9]));
return vo; return vo;
} }