diff --git a/WebContent/WEB-INF/springapp-servlet.xml b/WebContent/WEB-INF/springapp-servlet.xml
index 3bef9a1..89c338a 100644
--- a/WebContent/WEB-INF/springapp-servlet.xml
+++ b/WebContent/WEB-INF/springapp-servlet.xml
@@ -152,4 +152,9 @@
+
+
+
+
+
diff --git a/WebContent/jsp/onl/admin/adapter/adapterManDetail.jsp b/WebContent/jsp/onl/admin/adapter/adapterManDetail.jsp
index 66fd2f1..04c907b 100644
--- a/WebContent/jsp/onl/admin/adapter/adapterManDetail.jsp
+++ b/WebContent/jsp/onl/admin/adapter/adapterManDetail.jsp
@@ -513,7 +513,36 @@ function gridRendering(){
colModel:[
{ name : 'PRPTYGROUPNAME' , align:'left' , hidden: true },
{ name : 'PRPTYNAME' , width: 70 , align:'left' },
- { name : 'PRPTY2VAL' , width: 70 , align:'left' , title:false, editable: true, formatter:mltplFormatter, unformatter:mltplUnFormatter},
+ {
+ name : 'PRPTY2VAL',
+ width: 70,
+ align:'left',
+ title:false,
+ editable: true,
+ formatter:mltplFormatter,
+ unformatter:mltplUnFormatter,
+ editoptions: {
+ dataEvents: [
+ {
+ type: 'keydown',
+ fn: function(e) {
+ if (e.which === 13) {
+ var val = $(this).val();
+ $(this).val($.trim(val));
+ }
+ }
+ },
+ {
+ type: 'blur',
+ fn: function(e) {
+ var val = $(this).val();
+ $(this).val($.trim(val));
+ }
+ }
+ ]
+ }
+ // [여기까지 추가]
+ },
{ name : 'PRPTYDESC' , width: 300 , align:'left'},
{ name : 'DMNKND' , hidden:true},
{ name : 'DOMAINTYPE' , hidden:true, formatter:nullFormatter}
@@ -942,6 +971,7 @@ $(document).ready(function() {
buttonControl(isDetail);
titleControl(isDetail);
+
});
// clientManPopup.jsp에서 호출될 함수 (전역 스코프에 정의)
diff --git a/WebContent/jsp/onl/kjb/statistics/apiStatsAggregationMan.jsp b/WebContent/jsp/onl/kjb/statistics/apiStatsAggregationMan.jsp
new file mode 100644
index 0000000..8322d71
--- /dev/null
+++ b/WebContent/jsp/onl/kjb/statistics/apiStatsAggregationMan.jsp
@@ -0,0 +1,353 @@
+<%@ page language="java" contentType="text/html; charset=utf-8"%>
+<%@ page import="java.io.*"%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
+<%@ include file="/jsp/common/include/localemessage.jsp" %>
+<%
+ response.setHeader("Pragma", "No-cache");
+ response.setHeader("Cache-Control", "no-cache");
+ response.setHeader("Expires", "0");
+ request.setCharacterEncoding("UTF-8");
+%>
+
+
+ API 통계 집계 실행
+
+
+
+
+
+
+
+
+
+
+
API 통계 집계 수동 실행
+
+
+
⚠️ 주의사항
+
+ - 통계 집계는 스케줄러에 의해 자동으로 실행됩니다.
+ - 수동 실행은 누락된 기간이나 재집계가 필요한 경우에만 사용하세요.
+ - 집계 대상 기간의 원본 데이터가 존재해야 합니다.
+ - 대상 날짜/월/연도를 반드시 입력해야 합니다.
+
+
+
+
+
+
1. 시간별 → 일별 통계 집계
+
+
+
+ (예: 20250120)
+
+
+
+
+
+
+
+
2. 일별 → 월별 통계 집계
+
+
+
+ (예: 202501)
+
+
+
+
+
+
+
+
3. 월별 → 연별 통계 집계
+
+
+
+ (예: 2024)
+
+
+
+
+
+
+
+
diff --git a/WebContent/jsp/onl/kjb/statistics/apiStatsDayMan.jsp b/WebContent/jsp/onl/kjb/statistics/apiStatsDayMan.jsp
new file mode 100644
index 0000000..ee499a4
--- /dev/null
+++ b/WebContent/jsp/onl/kjb/statistics/apiStatsDayMan.jsp
@@ -0,0 +1,695 @@
+<%@ page language="java" contentType="text/html; charset=utf-8"%>
+<%@ page import="java.io.*"%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
+<%@ include file="/jsp/common/include/localemessage.jsp" %>
+<%
+ response.setHeader("Pragma", "No-cache");
+ response.setHeader("Cache-Control", "no-cache");
+ response.setHeader("Expires", "0");
+ request.setCharacterEncoding("UTF-8");
+%>
+
+
+ 일별 통계 조회
+
+
+
+
+ ">
+
+
+
+
+
+
+
+
+
+
+
API 일별 통계
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WebContent/jsp/onl/kjb/statistics/apiStatsHourMan.jsp b/WebContent/jsp/onl/kjb/statistics/apiStatsHourMan.jsp
index 0169457..0fabaf4 100644
--- a/WebContent/jsp/onl/kjb/statistics/apiStatsHourMan.jsp
+++ b/WebContent/jsp/onl/kjb/statistics/apiStatsHourMan.jsp
@@ -10,7 +10,7 @@
%>
- API 시간별 통계
+ 시간별 통계 조회
">
@@ -33,7 +41,7 @@
var url_view = '';
var url_minute_view = '';
- var callChart, respChart;
+ var totalDonutChart, seq900DonutChart, callChart, respChart;
function numberFormatter(cellvalue, options, rowObject) {
if (cellvalue == null || cellvalue == '') return '0';
@@ -46,9 +54,131 @@
}
function initCharts() {
+ totalDonutChart = echarts.init(document.getElementById('totalDonutChart'));
+ seq900DonutChart = echarts.init(document.getElementById('seq900DonutChart'));
callChart = echarts.init(document.getElementById('callChart'));
respChart = echarts.init(document.getElementById('respChart'));
+ // 총건수 도넛 차트
+ var totalDonutOption = {
+ title: {
+ text: '총 건수 분포',
+ left: 'center',
+ top: 10,
+ textStyle: { fontSize: 14 }
+ },
+ tooltip: {
+ trigger: 'item',
+ formatter: '{b}: {c} ({d}%)'
+ },
+ legend: {
+ orient: 'horizontal',
+ bottom: 10,
+ data: ['성공', 'Timeout', '시스템오류', '업무오류']
+ },
+ series: [{
+ name: '총건수',
+ type: 'pie',
+ radius: ['40%', '70%'],
+ center: ['50%', '50%'],
+ avoidLabelOverlap: true,
+ label: {
+ show: true,
+ formatter: '{b}: {c}'
+ },
+ emphasis: {
+ label: {
+ show: true,
+ fontSize: 16,
+ fontWeight: 'bold'
+ }
+ },
+ labelLine: {
+ show: true
+ },
+ data: [
+ { value: 0, name: '성공', itemStyle: { color: '#91CC75' } },
+ { value: 0, name: 'Timeout', itemStyle: { color: '#FAC858' } },
+ { value: 0, name: '시스템오류', itemStyle: { color: '#EE6666' } },
+ { value: 0, name: '업무오류', itemStyle: { color: '#FC8452' } }
+ ]
+ }],
+ graphic: [{
+ type: 'text',
+ left: 'center',
+ top: 'center',
+ style: {
+ text: '0',
+ textAlign: 'center',
+ fill: '#333',
+ fontSize: 24,
+ fontWeight: 'bold'
+ }
+ }]
+ };
+
+ totalDonutChart.setOption(totalDonutOption);
+
+ // Seq900 도넛 차트
+ var seq900DonutOption = {
+ title: {
+ text: 'Seq900 에러 분포',
+ left: 'center',
+ top: 10,
+ textStyle: { fontSize: 14 }
+ },
+ tooltip: {
+ trigger: 'item',
+ formatter: '{b}: {c} ({d}%)'
+ },
+ legend: {
+ orient: 'horizontal',
+ bottom: 10,
+ data: ['Timeout', '시스템오류', '업무오류']
+ },
+ series: [{
+ name: 'Seq900 에러',
+ type: 'pie',
+ radius: ['40%', '70%'],
+ center: ['50%', '50%'],
+ avoidLabelOverlap: true,
+ label: {
+ show: true,
+ formatter: '{b}: {c}'
+ },
+ emphasis: {
+ label: {
+ show: true,
+ fontSize: 16,
+ fontWeight: 'bold'
+ }
+ },
+ labelLine: {
+ show: true
+ },
+ data: [
+ { value: 0, name: 'Timeout', itemStyle: { color: '#FAC858' } },
+ { value: 0, name: '시스템오류', itemStyle: { color: '#EE6666' } },
+ { value: 0, name: '업무오류', itemStyle: { color: '#FC8452' } }
+ ]
+ }],
+ graphic: [{
+ type: 'text',
+ left: 'center',
+ top: 'center',
+ style: {
+ text: '0',
+ textAlign: 'center',
+ fill: '#333',
+ fontSize: 24,
+ fontWeight: 'bold'
+ }
+ }]
+ };
+
+ seq900DonutChart.setOption(seq900DonutOption);
+
+ // 호출량 차트
var callOption = {
title: { text: '호출량 추이', left: 'center', textStyle: { fontSize: 14 } },
tooltip: {
@@ -60,13 +190,16 @@
xAxis: { type: 'category', boundaryGap: false, data: [] },
yAxis: { type: 'value', minInterval: 1 },
series: [
- { name: '성공', type: 'line', stack: 'Total', areaStyle: { opacity: 0.5 }, itemStyle: { color: '#5470C6' }, data: [] },
- { name: 'Timeout', type: 'line', stack: 'Total', areaStyle: { opacity: 0.5 }, itemStyle: { color: '#FAC858' }, data: [] },
- { name: '시스템오류', type: 'line', stack: 'Total', areaStyle: { opacity: 0.5 }, itemStyle: { color: '#EE6666' }, data: [] },
- { name: '업무오류', type: 'line', stack: 'Total', areaStyle: { opacity: 0.5 }, itemStyle: { color: '#FC8452' }, data: [] }
+ { name: '성공', type: 'line', stack: 'Total', smooth: true, areaStyle: { opacity: 0.5 }, itemStyle: { color: '#5470C6' }, data: [] },
+ { name: 'Timeout', type: 'line', stack: 'Total', smooth: true, areaStyle: { opacity: 0.5 }, itemStyle: { color: '#FAC858' }, data: [] },
+ { name: '시스템오류', type: 'line', stack: 'Total', smooth: true, areaStyle: { opacity: 0.5 }, itemStyle: { color: '#EE6666' }, data: [] },
+ { name: '업무오류', type: 'line', stack: 'Total', smooth: true, areaStyle: { opacity: 0.5 }, itemStyle: { color: '#FC8452' }, data: [] }
]
};
+ callChart.setOption(callOption);
+
+ // 응답시간 차트
var respOption = {
title: { text: '응답시간 추이 (ms)', left: 'center', textStyle: { fontSize: 14 } },
tooltip: {
@@ -78,13 +211,12 @@
xAxis: { type: 'category', boundaryGap: false, data: [] },
yAxis: { type: 'value' },
series: [
- { name: 'P95', type: 'line', itemStyle: { color: '#EE6666' }, lineStyle: { type: 'dashed' }, data: [] },
- { name: 'P50', type: 'line', itemStyle: { color: '#5470C6' }, data: [] },
- { name: '평균', type: 'line', itemStyle: { color: '#91CC75' }, data: [] }
+ { name: 'P95', type: 'line', smooth: true, itemStyle: { color: '#EE6666' }, lineStyle: { type: 'dashed' }, data: [] },
+ { name: 'P50', type: 'line', smooth: true, itemStyle: { color: '#5470C6' }, data: [] },
+ { name: '평균', type: 'line', smooth: true, itemStyle: { color: '#91CC75' }, data: [] }
]
};
- callChart.setOption(callOption);
respChart.setOption(respOption);
// 드릴다운 이벤트 (시간 클릭 시 분단위 화면으로 이동)
@@ -101,18 +233,34 @@
// rawData에서 실제 statTime 가져오기 (yyyyMMddHH00 형식)
var statTime = chart.rawData[dataIndex].statTime;
- var date = statTime.substring(0, 4) + '-' + statTime.substring(4, 6) + '-' + statTime.substring(6, 8);
- var hour = statTime.substring(8, 10);
- var startTime = hour + ':00';
- var endTime = hour + ':59';
+ var year = statTime.substring(0, 4);
+ var month = statTime.substring(4, 6);
+ var day = statTime.substring(6, 8);
+ var hour = parseInt(statTime.substring(8, 10));
- var params = '?searchStartDate=' + date + '&searchStartTime=' + startTime
- + '&searchEndDate=' + date + '&searchEndTime=' + endTime;
+ var startDate = year + '-' + month + '-' + day;
+ var startTime = (hour < 10 ? '0' + hour : hour) + ':00';
+
+ // 1시간 후 계산 (23시인 경우 다음날 00시로 자동 계산)
+ var d = new Date(parseInt(year), parseInt(month) - 1, parseInt(day), hour, 0, 0);
+ d.setHours(d.getHours() + 1);
+
+ var endDate = d.getFullYear() + '-' +
+ (d.getMonth() + 1 < 10 ? '0' : '') + (d.getMonth() + 1) + '-' +
+ (d.getDate() < 10 ? '0' : '') + d.getDate();
+ var endTime = (d.getHours() < 10 ? '0' : '') + d.getHours() + ':00';
+
+ var params = '?searchStartDate=' + startDate + '&searchStartTime=' + startTime
+ + '&searchEndDate=' + endDate + '&searchEndTime=' + endTime;
+ params += '&menuId='+'${param.menuId}';
+ params += '&cmd='+'LIST';
+ params += '&serviceType='+'APIGW';
location.href = url_minute_view + params;
}
function updateCharts(data) {
+ // 시계열 차트용 변수
var times = [];
var successData = [];
var timeoutData = [];
@@ -122,10 +270,19 @@
var p50RespData = [];
var p95RespData = [];
+ // 도넛 차트용 총계 변수
+ var totalSuccess = 0;
+ var totalTimeout = 0;
+ var totalSystemErr = 0;
+ var totalBizErr = 0;
+ var seq900Timeout = 0;
+ var seq900SystemErr = 0;
+ var seq900BizErr = 0;
+
data.forEach(function(item) {
- // statTime: yyyyMMddHH00 -> HH시 형식으로 표시
- var hour = item.statTime.substring(8, 10);
times.push(item.statTime);
+
+ // 시계열 차트 데이터
successData.push(item.successCnt);
timeoutData.push(item.timeoutCnt);
systemErrData.push(item.systemErrCnt);
@@ -133,8 +290,53 @@
avgRespData.push(item.avgRespTime);
p50RespData.push(item.p50RespTime);
p95RespData.push(item.p95RespTime);
+
+ // 도넛 차트용 합산
+ totalSuccess += (item.successCnt || 0);
+ totalTimeout += (item.timeoutCnt || 0);
+ totalSystemErr += (item.systemErrCnt || 0);
+ totalBizErr += (item.bizErrCnt || 0);
+ seq900Timeout += (item.seq900TimeoutCnt || 0);
+ seq900SystemErr += (item.seq900SystemErrCnt || 0);
+ seq900BizErr += (item.seq900BizErrCnt || 0);
});
+ // 총건수 도넛 차트 업데이트
+ var grandTotal = totalSuccess + totalTimeout + totalSystemErr + totalBizErr;
+ totalDonutChart.setOption({
+ series: [{
+ data: [
+ { value: totalSuccess, name: '성공' },
+ { value: totalTimeout, name: 'Timeout' },
+ { value: totalSystemErr, name: '시스템오류' },
+ { value: totalBizErr, name: '업무오류' }
+ ]
+ }],
+ graphic: [{
+ style: {
+ text: grandTotal.toLocaleString()
+ }
+ }]
+ });
+
+ // Seq900 도넛 차트 업데이트
+ var seq900Total = seq900Timeout + seq900SystemErr + seq900BizErr;
+ seq900DonutChart.setOption({
+ series: [{
+ data: [
+ { value: seq900Timeout, name: 'Timeout' },
+ { value: seq900SystemErr, name: '시스템오류' },
+ { value: seq900BizErr, name: '업무오류' }
+ ]
+ }],
+ graphic: [{
+ style: {
+ text: seq900Total.toLocaleString()
+ }
+ }]
+ });
+
+ // 호출량 차트 업데이트
callChart.setOption({
xAxis: { data: times.map(function(t) { return t.substring(8, 10) + '시'; }) },
series: [
@@ -145,6 +347,7 @@
]
});
+ // 응답시간 차트 업데이트
respChart.setOption({
xAxis: { data: times.map(function(t) { return t.substring(8, 10) + '시'; }) },
series: [
@@ -204,6 +407,124 @@
fetchChartData();
}
+ function exportToExcel() {
+ console.log('[Excel Export] Starting...');
+
+ var postData = {
+ cmd: 'EXCEL_EXPORT',
+ searchApiName: $("input[name=searchApiName]").val(),
+ searchGwInstanceId: $("input[name=searchGwInstanceId]").val(),
+ searchBizDivCode: $("input[name=searchBizDivCode]").val(),
+ searchClientId: $("input[name=searchClientId]").val(),
+ searchInboundAdapter: $("input[name=searchInboundAdapter]").val(),
+ searchOutboundAdapter: $("input[name=searchOutboundAdapter]").val(),
+ serviceType: '${param.serviceType}'
+ };
+
+ var searchDate = $("input[name=searchDate]").val().replace(/-/g, "");
+ if (searchDate) {
+ postData.searchStartDateTime = searchDate + "00";
+ postData.searchEndDateTime = searchDate + "23";
+ }
+
+ console.log('[Excel Export] Request data:', postData);
+
+ var xhr = new XMLHttpRequest();
+ xhr.open('POST', url, true);
+ xhr.responseType = 'blob';
+ xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
+
+ xhr.onload = function() {
+ console.log('[Excel Export] Response received - Status:', xhr.status);
+
+ if (xhr.status === 200) {
+ var blob = xhr.response;
+ var contentType = xhr.getResponseHeader('Content-Type');
+ console.log('[Excel Export] Blob size:', blob.size, 'Content-Type:', contentType);
+
+ // JSON 에러 응답인지 확인
+ if (contentType && contentType.indexOf('application/json') !== -1) {
+ var reader = new FileReader();
+ reader.onload = function() {
+ try {
+ var errorObj = JSON.parse(reader.result);
+ alert(errorObj.message || 'Excel 파일 생성에 실패했습니다.');
+ } catch (e) {
+ alert('Excel 파일 생성에 실패했습니다.');
+ }
+ };
+ reader.readAsText(blob);
+ return;
+ }
+
+ // Blob 크기가 0이면 에러
+ if (blob.size === 0) {
+ alert('Excel 파일 생성에 실패했습니다. (빈 응답)');
+ return;
+ }
+
+ // 파일명 추출
+ var filename = 'api_stats.xlsx';
+ var disposition = xhr.getResponseHeader('Content-Disposition');
+ console.log('[Excel Export] Content-Disposition:', disposition);
+
+ if (disposition && disposition.indexOf('filename=') !== -1) {
+ var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
+ var matches = filenameRegex.exec(disposition);
+ if (matches != null && matches[1]) {
+ filename = matches[1].replace(/['"]/g, '');
+ filename = decodeURIComponent(filename);
+ }
+ }
+
+ console.log('[Excel Export] Downloading as:', filename);
+
+ // 파일 다운로드
+ var link = document.createElement('a');
+ link.href = window.URL.createObjectURL(blob);
+ link.download = filename;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ window.URL.revokeObjectURL(link.href);
+
+ console.log('[Excel Export] Download triggered');
+
+ } else if (xhr.status === 204) {
+ alert('조회된 데이터가 없습니다.');
+ } else {
+ // 에러 응답 처리
+ var reader = new FileReader();
+ reader.onload = function() {
+ var message = 'Excel 다운로드 중 오류가 발생했습니다.';
+ try {
+ var errorObj = JSON.parse(reader.result);
+ if (errorObj.message) message = errorObj.message;
+ } catch (e) {
+ console.error('[Excel Export] Error parsing error response:', e);
+ }
+ alert(message);
+ };
+ reader.readAsText(xhr.response);
+ }
+ };
+
+ xhr.onerror = function() {
+ console.error('[Excel Export] Network error');
+ alert('네트워크 오류가 발생했습니다.');
+ };
+
+ // Form data 생성
+ var formData = [];
+ for (var key in postData) {
+ if (postData.hasOwnProperty(key) && postData[key] != null && postData[key] !== '') {
+ formData.push(encodeURIComponent(key) + '=' + encodeURIComponent(postData[key]));
+ }
+ }
+
+ xhr.send(formData.join('&'));
+ }
+
function list() {
var gridPostData = getSearchForJqgrid("cmd", "LIST");
$('#grid').jqGrid({
@@ -214,6 +535,7 @@
'통계시간', 'API명', '인스턴스', '업무구분', '클라이언트ID',
'Inbound Adapter', 'Outbound Adapter',
'총건수', '성공', 'Timeout', '시스템오류', '업무오류',
+ 'Seq900 Timeout', 'Seq900 시스템오류', 'Seq900 업무오류',
'평균응답(ms)', '최소응답(ms)', '최대응답(ms)', 'P50(ms)', 'P95(ms)'
],
colModel: [
@@ -229,6 +551,9 @@
{ name: 'timeoutCnt', align: 'right', width: '70', formatter: numberFormatter, sortable: false },
{ name: 'systemErrCnt', align: 'right', width: '70', formatter: numberFormatter, sortable: false },
{ name: 'bizErrCnt', align: 'right', width: '70', formatter: numberFormatter, sortable: false },
+ { name: 'seq900TimeoutCnt', align: 'right', width: '70', formatter: numberFormatter, sortable: false },
+ { name: 'seq900SystemErrCnt', align: 'right', width: '70', formatter: numberFormatter, sortable: false },
+ { name: 'seq900BizErrCnt', align: 'right', width: '70', formatter: numberFormatter, sortable: false },
{ name: 'avgRespTime', align: 'right', width: '80', formatter: decimalFormatter, sortable: false },
{ name: 'minRespTime', align: 'right', width: '80', formatter: decimalFormatter, sortable: false },
{ name: 'maxRespTime', align: 'right', width: '80', formatter: decimalFormatter, sortable: false },
@@ -273,6 +598,10 @@
search();
});
+ $("#btn_excel_export").click(function() {
+ exportToExcel();
+ });
+
$("input[name^=search]").keydown(function(key) {
if (key.keyCode == 13) {
$("#btn_search").click();
@@ -281,6 +610,8 @@
// 윈도우 리사이즈 시 차트 리사이즈
$(window).resize(function() {
+ if (totalDonutChart) totalDonutChart.resize();
+ if (seq900DonutChart) seq900DonutChart.resize();
if (callChart) callChart.resize();
if (respChart) respChart.resize();
});
@@ -301,6 +632,9 @@
+
API 시간별 통계
+
+
+
diff --git a/WebContent/jsp/onl/kjb/statistics/apiStatsMinuteMan.jsp b/WebContent/jsp/onl/kjb/statistics/apiStatsMinuteMan.jsp
index 7013212..b94b52b 100644
--- a/WebContent/jsp/onl/kjb/statistics/apiStatsMinuteMan.jsp
+++ b/WebContent/jsp/onl/kjb/statistics/apiStatsMinuteMan.jsp
@@ -10,7 +10,7 @@
%>
-
API 분단위 통계
+
분별 통계 조회
+
+ ">
+
+
+
+
+
+
+
+
+
+
+
API 월별 통계
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WebContent/jsp/onl/kjb/statistics/apiStatsYearMan.jsp b/WebContent/jsp/onl/kjb/statistics/apiStatsYearMan.jsp
new file mode 100644
index 0000000..7e01a46
--- /dev/null
+++ b/WebContent/jsp/onl/kjb/statistics/apiStatsYearMan.jsp
@@ -0,0 +1,670 @@
+<%@ page language="java" contentType="text/html; charset=utf-8"%>
+<%@ page import="java.io.*"%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
+<%@ include file="/jsp/common/include/localemessage.jsp" %>
+<%
+ response.setHeader("Pragma", "No-cache");
+ response.setHeader("Cache-Control", "no-cache");
+ response.setHeader("Expires", "0");
+ request.setCharacterEncoding("UTF-8");
+%>
+
+
+
연간 통계 조회
+
+
+
+
+ ">
+
+
+
+
+
+
+
+
+
+
+
API 연간 통계
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WebContent/jsp/onl/transaction/tracking/trackingMan.jsp b/WebContent/jsp/onl/transaction/tracking/trackingMan.jsp
index c9bccd1..773f804 100644
--- a/WebContent/jsp/onl/transaction/tracking/trackingMan.jsp
+++ b/WebContent/jsp/onl/transaction/tracking/trackingMan.jsp
@@ -303,6 +303,12 @@
return cellvalue;
}
+ function trimFormatter(cellvalue, options, rowObject) {
+
+ return cellvalue ? String(cellvalue).trim() : '';
+
+ }
+
$(document).ready(function () {
console.log(roleString);
@@ -373,7 +379,7 @@
{name: 'TRACKASISKEY1CTNT', hidden: true},
{name: 'TRACKASISKEY2CTNT', hidden: true},
- {name: 'EAISVCSERNO_TMP', align: 'left', width: '220'},
+ {name: 'EAISVCSERNO_TMP', align: 'left', width: '220', formatter: trimFormatter},
{name: 'KEYMGTMSGCTNT', align: 'left', width: '240'},
{name: 'LOGPRCSSSERNO_TMP', align: 'center', width: '40'},
{name: 'TELGMRECVTRANCD', align: 'left', width: '135', hidden: true},
diff --git a/WebContent/jsp/onl/transaction/tracking/trackingManDetail.jsp b/WebContent/jsp/onl/transaction/tracking/trackingManDetail.jsp
index ca13dc6..2f5db38 100644
--- a/WebContent/jsp/onl/transaction/tracking/trackingManDetail.jsp
+++ b/WebContent/jsp/onl/transaction/tracking/trackingManDetail.jsp
@@ -216,7 +216,7 @@
$(tag+"[name="+name+"]").val(data[name.toUpperCase()]);
//업체코드.업체명
- if(name=="companyName"){
+ if(name=="eaiSvcSerno"){
if(data["COMPANYCODE"] !=null && data["COMPANYCODE"].trim() != ''){
$("input[name=companyName]").val("["+data["COMPANYCODE"]+"] "+data[name.toUpperCase()]);
}
@@ -233,6 +233,13 @@
}
});
+ // 거래고유번호 trim
+ var $svcSernoInput = $("input[name=eaiSvcSerno]");
+
+ if($svcSernoInput.val()) {
+ $svcSernoInput.val( $svcSernoInput.val().trim() );
+ }
+
$("input[name=gstatSysAdptrBzwkGroupName],input[name=psvSysAdptrBzwkGroupName]").each(function(){
var name = $(this).attr("name");
if ("${rmsMenuAuth}" =="W"){ //2020.07.08 admin 만 버튼보이게 수정
diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsDay.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsDay.java
new file mode 100644
index 0000000..28e7d4f
--- /dev/null
+++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsDay.java
@@ -0,0 +1,93 @@
+package com.eactive.eai.rms.data.entity.onl.kjb.statistics;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.Table;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * API Gateway 일별 처리 통계
+ */
+@Getter
+@Setter
+@Entity
+@Table(name = "API_STATS_DAY")
+@IdClass(ApiStatsDayId.class)
+public class ApiStatsDay implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @Id
+ @Column(name = "STAT_TIME", nullable = false)
+ private LocalDate statTime;
+
+ @Id
+ @Column(name = "API_NAME", nullable = false, length = 100)
+ private String apiName;
+
+ @Id
+ @Column(name = "GW_INSTANCE_ID", nullable = false, length = 50)
+ private String gwInstanceId;
+
+ @Id
+ @Column(name = "BIZ_DIV_CODE", nullable = false, length = 50)
+ private String bizDivCode = "NONE";
+
+ @Id
+ @Column(name = "CLIENT_ID", nullable = false, length = 100)
+ private String clientId = "NONE";
+
+ @Id
+ @Column(name = "INBOUND_ADAPTER", nullable = false, length = 100)
+ private String inboundAdapter = "NONE";
+
+ @Id
+ @Column(name = "OUTBOUND_ADAPTER", nullable = false, length = 100)
+ private String outboundAdapter = "NONE";
+
+ @Column(name = "TOTAL_CNT", nullable = false)
+ private Long totalCnt = 0L;
+
+ @Column(name = "SUCCESS_CNT", nullable = false)
+ private Long successCnt = 0L;
+
+ @Column(name = "TIMEOUT_CNT", nullable = false)
+ private Long timeoutCnt = 0L;
+
+ @Column(name = "SYSTEM_ERR_CNT", nullable = false)
+ private Long systemErrCnt = 0L;
+
+ @Column(name = "BIZ_ERR_CNT", nullable = false)
+ private Long bizErrCnt = 0L;
+
+ @Column(name = "SEQ900_TIMEOUT_CNT", nullable = false)
+ private Long seq900TimeoutCnt = 0L;
+
+ @Column(name = "SEQ900_SYSTEM_ERR_CNT", nullable = false)
+ private Long seq900SystemErrCnt = 0L;
+
+ @Column(name = "SEQ900_BIZ_ERR_CNT", nullable = false)
+ private Long seq900BizErrCnt = 0L;
+
+ @Column(name = "AVG_RESP_TIME", precision = 10, scale = 3)
+ private BigDecimal avgRespTime = BigDecimal.ZERO;
+
+ @Column(name = "MIN_RESP_TIME", precision = 10, scale = 3)
+ private BigDecimal minRespTime = BigDecimal.ZERO;
+
+ @Column(name = "MAX_RESP_TIME", precision = 10, scale = 3)
+ private BigDecimal maxRespTime = BigDecimal.ZERO;
+
+ @Column(name = "P50_RESP_TIME", precision = 10, scale = 3)
+ private BigDecimal p50RespTime = BigDecimal.ZERO;
+
+ @Column(name = "P95_RESP_TIME", precision = 10, scale = 3)
+ private BigDecimal p95RespTime = BigDecimal.ZERO;
+}
diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsDayId.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsDayId.java
new file mode 100644
index 0000000..8b50d29
--- /dev/null
+++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsDayId.java
@@ -0,0 +1,26 @@
+package com.eactive.eai.rms.data.entity.onl.kjb.statistics;
+
+import java.io.Serializable;
+import java.time.LocalDate;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * API 통계 일별 복합키
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class ApiStatsDayId implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private LocalDate statTime;
+ private String apiName;
+ private String gwInstanceId;
+ private String bizDivCode;
+ private String clientId;
+ private String inboundAdapter;
+ private String outboundAdapter;
+}
diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsDayRepository.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsDayRepository.java
new file mode 100644
index 0000000..206215a
--- /dev/null
+++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsDayRepository.java
@@ -0,0 +1,10 @@
+package com.eactive.eai.rms.data.entity.onl.kjb.statistics;
+
+import com.eactive.eai.data.jpa.BaseRepository;
+
+/**
+ * API 통계 일별 Repository
+ */
+interface ApiStatsDayRepository extends BaseRepository
{
+
+}
diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsDayService.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsDayService.java
new file mode 100644
index 0000000..251b1b4
--- /dev/null
+++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsDayService.java
@@ -0,0 +1,152 @@
+package com.eactive.eai.rms.data.entity.onl.kjb.statistics;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.eactive.eai.data.jpa.AbstractDataService;
+import com.eactive.ext.kjb.statistics.ui.ApiStatsChartUI;
+import com.eactive.ext.kjb.statistics.ui.ApiStatsSearch;
+import com.querydsl.core.BooleanBuilder;
+import com.querydsl.core.types.Projections;
+
+@Service
+@Transactional
+public class ApiStatsDayService
+ extends AbstractDataService {
+
+ private static final int MAX_DAYS = 31;
+
+ public Page selectList(ApiStatsSearch search, Pageable pageable) {
+ QApiStatsDay q = QApiStatsDay.apiStatsDay;
+ BooleanBuilder builder = buildSearchConditions(q, search);
+ return repository.findAll(builder, pageable);
+ }
+
+ public List selectListForExcel(ApiStatsSearch search) {
+ QApiStatsDay q = QApiStatsDay.apiStatsDay;
+ BooleanBuilder builder = buildSearchConditions(q, search);
+
+ return getJPAQueryFactory()
+ .selectFrom(q)
+ .where(builder)
+ .orderBy(q.statTime.desc())
+ .fetch();
+ }
+
+ public List selectChartData(ApiStatsSearch search) {
+ QApiStatsDay q = QApiStatsDay.apiStatsDay;
+ BooleanBuilder builder = new BooleanBuilder();
+
+ calculateDateRange(search);
+ if (search.getParsedStartDate() != null) {
+ builder.and(q.statTime.goe(search.getParsedStartDate()));
+ builder.and(q.statTime.loe(search.getParsedEndDate()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchApiName())) {
+ builder.and(q.apiName.containsIgnoreCase(search.getSearchApiName()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchGwInstanceId())) {
+ builder.and(q.gwInstanceId.containsIgnoreCase(search.getSearchGwInstanceId()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchBizDivCode())) {
+ builder.and(q.bizDivCode.containsIgnoreCase(search.getSearchBizDivCode()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchClientId())) {
+ builder.and(q.clientId.containsIgnoreCase(search.getSearchClientId()));
+ }
+
+ return getJPAQueryFactory()
+ .select(Projections.constructor(ApiStatsChartUI.class,
+ q.statTime,
+ q.totalCnt.sum(),
+ q.successCnt.sum(),
+ q.timeoutCnt.sum(),
+ q.systemErrCnt.sum(),
+ q.bizErrCnt.sum(),
+ q.seq900TimeoutCnt.sum(),
+ q.seq900SystemErrCnt.sum(),
+ q.seq900BizErrCnt.sum(),
+ q.avgRespTime.avg(),
+ q.p50RespTime.avg(),
+ q.p95RespTime.avg()))
+ .from(q)
+ .where(builder)
+ .groupBy(q.statTime)
+ .orderBy(q.statTime.asc())
+ .fetch();
+ }
+
+ private BooleanBuilder buildSearchConditions(QApiStatsDay q, ApiStatsSearch search) {
+ BooleanBuilder builder = new BooleanBuilder();
+
+ calculateDateRange(search);
+ if (search.getParsedStartDate() != null) {
+ builder.and(q.statTime.goe(search.getParsedStartDate()));
+ builder.and(q.statTime.loe(search.getParsedEndDate()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchApiName())) {
+ builder.and(q.apiName.containsIgnoreCase(search.getSearchApiName()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchGwInstanceId())) {
+ builder.and(q.gwInstanceId.containsIgnoreCase(search.getSearchGwInstanceId()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchBizDivCode())) {
+ builder.and(q.bizDivCode.containsIgnoreCase(search.getSearchBizDivCode()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchClientId())) {
+ builder.and(q.clientId.containsIgnoreCase(search.getSearchClientId()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchInboundAdapter())) {
+ builder.and(q.inboundAdapter.containsIgnoreCase(search.getSearchInboundAdapter()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchOutboundAdapter())) {
+ builder.and(q.outboundAdapter.containsIgnoreCase(search.getSearchOutboundAdapter()));
+ }
+
+ return builder;
+ }
+
+ /**
+ * 조회 기간 계산 (최대 31일 제한)
+ */
+ private void calculateDateRange(ApiStatsSearch search) {
+ if (StringUtils.isBlank(search.getSearchStartDateTime())) {
+ return;
+ }
+
+ LocalDate startDate = LocalDate.parse(search.getSearchStartDateTime(),
+ DateTimeFormatter.ofPattern("yyyyMMdd"));
+ LocalDate endDate;
+
+ if (StringUtils.isNotBlank(search.getSearchEndDateTime())) {
+ endDate = LocalDate.parse(search.getSearchEndDateTime(),
+ DateTimeFormatter.ofPattern("yyyyMMdd"));
+ // 31일 초과 시 시작날짜 기준 31일 후로 강제 설정
+ if (java.time.Period.between(startDate, endDate).getDays() > MAX_DAYS) {
+ endDate = startDate.plusDays(MAX_DAYS);
+ }
+ } else {
+ endDate = startDate.plusDays(MAX_DAYS);
+ }
+
+ search.setParsedStartDate(startDate);
+ search.setParsedEndDate(endDate);
+ }
+}
diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsHourService.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsHourService.java
index 714e783..74816c8 100644
--- a/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsHourService.java
+++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsHourService.java
@@ -60,6 +60,47 @@ public class ApiStatsHourService
return repository.findAll(builder, pageable);
}
+ public List selectListForExcel(ApiStatsSearch search) {
+ QApiStatsHour q = QApiStatsHour.apiStatsHour;
+ BooleanBuilder builder = new BooleanBuilder();
+
+ calculateDateRange(search);
+ if (search.getParsedStartDateTime() != null) {
+ builder.and(q.statTime.goe(search.getParsedStartDateTime()));
+ builder.and(q.statTime.loe(search.getParsedEndDateTime()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchApiName())) {
+ builder.and(q.apiName.containsIgnoreCase(search.getSearchApiName()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchGwInstanceId())) {
+ builder.and(q.gwInstanceId.containsIgnoreCase(search.getSearchGwInstanceId()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchBizDivCode())) {
+ builder.and(q.bizDivCode.containsIgnoreCase(search.getSearchBizDivCode()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchClientId())) {
+ builder.and(q.clientId.containsIgnoreCase(search.getSearchClientId()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchInboundAdapter())) {
+ builder.and(q.inboundAdapter.containsIgnoreCase(search.getSearchInboundAdapter()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchOutboundAdapter())) {
+ builder.and(q.outboundAdapter.containsIgnoreCase(search.getSearchOutboundAdapter()));
+ }
+
+ return getJPAQueryFactory()
+ .selectFrom(q)
+ .where(builder)
+ .orderBy(q.statTime.desc())
+ .fetch();
+ }
+
public List selectChartData(ApiStatsSearch search) {
QApiStatsHour q = QApiStatsHour.apiStatsHour;
BooleanBuilder builder = new BooleanBuilder();
@@ -94,6 +135,9 @@ public class ApiStatsHourService
q.timeoutCnt.sum(),
q.systemErrCnt.sum(),
q.bizErrCnt.sum(),
+ q.seq900TimeoutCnt.sum(),
+ q.seq900SystemErrCnt.sum(),
+ q.seq900BizErrCnt.sum(),
q.avgRespTime.avg(),
q.p50RespTime.avg(),
q.p95RespTime.avg()))
diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsMinuteService.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsMinuteService.java
index 4570b34..9a35b35 100644
--- a/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsMinuteService.java
+++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsMinuteService.java
@@ -62,6 +62,47 @@ public class ApiStatsMinuteService
return repository.findAll(builder, pageable);
}
+ public List selectListForExcel(ApiStatsSearch search) {
+ QApiStatsMinute q = QApiStatsMinute.apiStatsMinute;
+ BooleanBuilder builder = new BooleanBuilder();
+
+ calculateDateRange(search);
+ if (search.getParsedStartDateTime() != null) {
+ builder.and(q.statTime.goe(search.getParsedStartDateTime()));
+ builder.and(q.statTime.loe(search.getParsedEndDateTime()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchApiName())) {
+ builder.and(q.apiName.containsIgnoreCase(search.getSearchApiName()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchGwInstanceId())) {
+ builder.and(q.gwInstanceId.containsIgnoreCase(search.getSearchGwInstanceId()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchBizDivCode())) {
+ builder.and(q.bizDivCode.containsIgnoreCase(search.getSearchBizDivCode()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchClientId())) {
+ builder.and(q.clientId.containsIgnoreCase(search.getSearchClientId()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchInboundAdapter())) {
+ builder.and(q.inboundAdapter.containsIgnoreCase(search.getSearchInboundAdapter()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchOutboundAdapter())) {
+ builder.and(q.outboundAdapter.containsIgnoreCase(search.getSearchOutboundAdapter()));
+ }
+
+ return getJPAQueryFactory()
+ .selectFrom(q)
+ .where(builder)
+ .orderBy(q.statTime.desc())
+ .fetch();
+ }
+
public List selectChartData(ApiStatsSearch search) {
QApiStatsMinute q = QApiStatsMinute.apiStatsMinute;
BooleanBuilder builder = new BooleanBuilder();
@@ -96,6 +137,9 @@ public class ApiStatsMinuteService
q.timeoutCnt.sum(),
q.systemErrCnt.sum(),
q.bizErrCnt.sum(),
+ q.seq900TimeoutCnt.sum(),
+ q.seq900SystemErrCnt.sum(),
+ q.seq900BizErrCnt.sum(),
q.avgRespTime.avg(),
q.p50RespTime.avg(),
q.p95RespTime.avg()))
diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsMonth.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsMonth.java
new file mode 100644
index 0000000..944595d
--- /dev/null
+++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsMonth.java
@@ -0,0 +1,92 @@
+package com.eactive.eai.rms.data.entity.onl.kjb.statistics;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.Table;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * API Gateway 월별 처리 통계
+ */
+@Getter
+@Setter
+@Entity
+@Table(name = "API_STATS_MONTH")
+@IdClass(ApiStatsMonthId.class)
+public class ApiStatsMonth implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @Id
+ @Column(name = "STAT_TIME", nullable = false, length = 6)
+ private String statTime; // YYYYMM format
+
+ @Id
+ @Column(name = "API_NAME", nullable = false, length = 100)
+ private String apiName;
+
+ @Id
+ @Column(name = "GW_INSTANCE_ID", nullable = false, length = 50)
+ private String gwInstanceId;
+
+ @Id
+ @Column(name = "BIZ_DIV_CODE", nullable = false, length = 50)
+ private String bizDivCode = "NONE";
+
+ @Id
+ @Column(name = "CLIENT_ID", nullable = false, length = 100)
+ private String clientId = "NONE";
+
+ @Id
+ @Column(name = "INBOUND_ADAPTER", nullable = false, length = 100)
+ private String inboundAdapter = "NONE";
+
+ @Id
+ @Column(name = "OUTBOUND_ADAPTER", nullable = false, length = 100)
+ private String outboundAdapter = "NONE";
+
+ @Column(name = "TOTAL_CNT", nullable = false)
+ private Long totalCnt = 0L;
+
+ @Column(name = "SUCCESS_CNT", nullable = false)
+ private Long successCnt = 0L;
+
+ @Column(name = "TIMEOUT_CNT", nullable = false)
+ private Long timeoutCnt = 0L;
+
+ @Column(name = "SYSTEM_ERR_CNT", nullable = false)
+ private Long systemErrCnt = 0L;
+
+ @Column(name = "BIZ_ERR_CNT", nullable = false)
+ private Long bizErrCnt = 0L;
+
+ @Column(name = "SEQ900_TIMEOUT_CNT", nullable = false)
+ private Long seq900TimeoutCnt = 0L;
+
+ @Column(name = "SEQ900_SYSTEM_ERR_CNT", nullable = false)
+ private Long seq900SystemErrCnt = 0L;
+
+ @Column(name = "SEQ900_BIZ_ERR_CNT", nullable = false)
+ private Long seq900BizErrCnt = 0L;
+
+ @Column(name = "AVG_RESP_TIME", precision = 10, scale = 3)
+ private BigDecimal avgRespTime = BigDecimal.ZERO;
+
+ @Column(name = "MIN_RESP_TIME", precision = 10, scale = 3)
+ private BigDecimal minRespTime = BigDecimal.ZERO;
+
+ @Column(name = "MAX_RESP_TIME", precision = 10, scale = 3)
+ private BigDecimal maxRespTime = BigDecimal.ZERO;
+
+ @Column(name = "P50_RESP_TIME", precision = 10, scale = 3)
+ private BigDecimal p50RespTime = BigDecimal.ZERO;
+
+ @Column(name = "P95_RESP_TIME", precision = 10, scale = 3)
+ private BigDecimal p95RespTime = BigDecimal.ZERO;
+}
diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsMonthId.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsMonthId.java
new file mode 100644
index 0000000..91366ec
--- /dev/null
+++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsMonthId.java
@@ -0,0 +1,25 @@
+package com.eactive.eai.rms.data.entity.onl.kjb.statistics;
+
+import java.io.Serializable;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * API 통계 월별 복합키
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class ApiStatsMonthId implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private String statTime; // YYYYMM format
+ private String apiName;
+ private String gwInstanceId;
+ private String bizDivCode;
+ private String clientId;
+ private String inboundAdapter;
+ private String outboundAdapter;
+}
diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsMonthRepository.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsMonthRepository.java
new file mode 100644
index 0000000..6942bde
--- /dev/null
+++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsMonthRepository.java
@@ -0,0 +1,10 @@
+package com.eactive.eai.rms.data.entity.onl.kjb.statistics;
+
+import com.eactive.eai.data.jpa.BaseRepository;
+
+/**
+ * API 통계 월별 Repository
+ */
+interface ApiStatsMonthRepository extends BaseRepository {
+
+}
diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsMonthService.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsMonthService.java
new file mode 100644
index 0000000..928ce4a
--- /dev/null
+++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsMonthService.java
@@ -0,0 +1,153 @@
+package com.eactive.eai.rms.data.entity.onl.kjb.statistics;
+
+import java.time.YearMonth;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.eactive.eai.data.jpa.AbstractDataService;
+import com.eactive.ext.kjb.statistics.ui.ApiStatsChartUI;
+import com.eactive.ext.kjb.statistics.ui.ApiStatsSearch;
+import com.querydsl.core.BooleanBuilder;
+import com.querydsl.core.types.Projections;
+
+@Service
+@Transactional
+public class ApiStatsMonthService
+ extends AbstractDataService {
+
+ private static final int MAX_MONTHS = 24;
+
+ public Page selectList(ApiStatsSearch search, Pageable pageable) {
+ QApiStatsMonth q = QApiStatsMonth.apiStatsMonth;
+ BooleanBuilder builder = buildSearchConditions(q, search);
+ return repository.findAll(builder, pageable);
+ }
+
+ public List selectListForExcel(ApiStatsSearch search) {
+ QApiStatsMonth q = QApiStatsMonth.apiStatsMonth;
+ BooleanBuilder builder = buildSearchConditions(q, search);
+
+ return getJPAQueryFactory()
+ .selectFrom(q)
+ .where(builder)
+ .orderBy(q.statTime.desc())
+ .fetch();
+ }
+
+ public List selectChartData(ApiStatsSearch search) {
+ QApiStatsMonth q = QApiStatsMonth.apiStatsMonth;
+ BooleanBuilder builder = new BooleanBuilder();
+
+ calculateDateRange(search);
+ if (search.getParsedStartMonth() != null) {
+ builder.and(q.statTime.goe(search.getParsedStartMonth()));
+ builder.and(q.statTime.loe(search.getParsedEndMonth()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchApiName())) {
+ builder.and(q.apiName.containsIgnoreCase(search.getSearchApiName()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchGwInstanceId())) {
+ builder.and(q.gwInstanceId.containsIgnoreCase(search.getSearchGwInstanceId()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchBizDivCode())) {
+ builder.and(q.bizDivCode.containsIgnoreCase(search.getSearchBizDivCode()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchClientId())) {
+ builder.and(q.clientId.containsIgnoreCase(search.getSearchClientId()));
+ }
+
+ return getJPAQueryFactory()
+ .select(Projections.constructor(ApiStatsChartUI.class,
+ q.statTime,
+ q.totalCnt.sum(),
+ q.successCnt.sum(),
+ q.timeoutCnt.sum(),
+ q.systemErrCnt.sum(),
+ q.bizErrCnt.sum(),
+ q.seq900TimeoutCnt.sum(),
+ q.seq900SystemErrCnt.sum(),
+ q.seq900BizErrCnt.sum(),
+ q.avgRespTime.avg(),
+ q.p50RespTime.avg(),
+ q.p95RespTime.avg()))
+ .from(q)
+ .where(builder)
+ .groupBy(q.statTime)
+ .orderBy(q.statTime.asc())
+ .fetch();
+ }
+
+ private BooleanBuilder buildSearchConditions(QApiStatsMonth q, ApiStatsSearch search) {
+ BooleanBuilder builder = new BooleanBuilder();
+
+ calculateDateRange(search);
+ if (search.getParsedStartMonth() != null) {
+ builder.and(q.statTime.goe(search.getParsedStartMonth()));
+ builder.and(q.statTime.loe(search.getParsedEndMonth()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchApiName())) {
+ builder.and(q.apiName.containsIgnoreCase(search.getSearchApiName()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchGwInstanceId())) {
+ builder.and(q.gwInstanceId.containsIgnoreCase(search.getSearchGwInstanceId()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchBizDivCode())) {
+ builder.and(q.bizDivCode.containsIgnoreCase(search.getSearchBizDivCode()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchClientId())) {
+ builder.and(q.clientId.containsIgnoreCase(search.getSearchClientId()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchInboundAdapter())) {
+ builder.and(q.inboundAdapter.containsIgnoreCase(search.getSearchInboundAdapter()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchOutboundAdapter())) {
+ builder.and(q.outboundAdapter.containsIgnoreCase(search.getSearchOutboundAdapter()));
+ }
+
+ return builder;
+ }
+
+ /**
+ * 조회 기간 계산 (최대 24개월 제한)
+ */
+ private void calculateDateRange(ApiStatsSearch search) {
+ if (StringUtils.isBlank(search.getSearchStartDateTime())) {
+ return;
+ }
+
+ YearMonth startMonth = YearMonth.parse(search.getSearchStartDateTime(),
+ DateTimeFormatter.ofPattern("yyyyMM"));
+ YearMonth endMonth;
+
+ if (StringUtils.isNotBlank(search.getSearchEndDateTime())) {
+ endMonth = YearMonth.parse(search.getSearchEndDateTime(),
+ DateTimeFormatter.ofPattern("yyyyMM"));
+ // 12개월 초과 시 시작월 기준 12개월 후로 강제 설정
+ long monthsBetween = java.time.temporal.ChronoUnit.MONTHS.between(startMonth, endMonth);
+ if (monthsBetween > MAX_MONTHS) {
+ endMonth = startMonth.plusMonths(MAX_MONTHS);
+ }
+ } else {
+ endMonth = startMonth.plusMonths(MAX_MONTHS);
+ }
+
+ search.setParsedStartMonth(startMonth.format(DateTimeFormatter.ofPattern("yyyyMM")));
+ search.setParsedEndMonth(endMonth.format(DateTimeFormatter.ofPattern("yyyyMM")));
+ }
+}
diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsYear.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsYear.java
new file mode 100644
index 0000000..bd5f6ba
--- /dev/null
+++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsYear.java
@@ -0,0 +1,92 @@
+package com.eactive.eai.rms.data.entity.onl.kjb.statistics;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.Table;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * API Gateway 연간 처리 통계
+ */
+@Getter
+@Setter
+@Entity
+@Table(name = "API_STATS_YEAR")
+@IdClass(ApiStatsYearId.class)
+public class ApiStatsYear implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @Id
+ @Column(name = "STAT_TIME", nullable = false, length = 4)
+ private String statTime; // YYYY format
+
+ @Id
+ @Column(name = "API_NAME", nullable = false, length = 100)
+ private String apiName;
+
+ @Id
+ @Column(name = "GW_INSTANCE_ID", nullable = false, length = 50)
+ private String gwInstanceId;
+
+ @Id
+ @Column(name = "BIZ_DIV_CODE", nullable = false, length = 50)
+ private String bizDivCode = "NONE";
+
+ @Id
+ @Column(name = "CLIENT_ID", nullable = false, length = 100)
+ private String clientId = "NONE";
+
+ @Id
+ @Column(name = "INBOUND_ADAPTER", nullable = false, length = 100)
+ private String inboundAdapter = "NONE";
+
+ @Id
+ @Column(name = "OUTBOUND_ADAPTER", nullable = false, length = 100)
+ private String outboundAdapter = "NONE";
+
+ @Column(name = "TOTAL_CNT", nullable = false)
+ private Long totalCnt = 0L;
+
+ @Column(name = "SUCCESS_CNT", nullable = false)
+ private Long successCnt = 0L;
+
+ @Column(name = "TIMEOUT_CNT", nullable = false)
+ private Long timeoutCnt = 0L;
+
+ @Column(name = "SYSTEM_ERR_CNT", nullable = false)
+ private Long systemErrCnt = 0L;
+
+ @Column(name = "BIZ_ERR_CNT", nullable = false)
+ private Long bizErrCnt = 0L;
+
+ @Column(name = "SEQ900_TIMEOUT_CNT", nullable = false)
+ private Long seq900TimeoutCnt = 0L;
+
+ @Column(name = "SEQ900_SYSTEM_ERR_CNT", nullable = false)
+ private Long seq900SystemErrCnt = 0L;
+
+ @Column(name = "SEQ900_BIZ_ERR_CNT", nullable = false)
+ private Long seq900BizErrCnt = 0L;
+
+ @Column(name = "AVG_RESP_TIME", precision = 10, scale = 3)
+ private BigDecimal avgRespTime = BigDecimal.ZERO;
+
+ @Column(name = "MIN_RESP_TIME", precision = 10, scale = 3)
+ private BigDecimal minRespTime = BigDecimal.ZERO;
+
+ @Column(name = "MAX_RESP_TIME", precision = 10, scale = 3)
+ private BigDecimal maxRespTime = BigDecimal.ZERO;
+
+ @Column(name = "P50_RESP_TIME", precision = 10, scale = 3)
+ private BigDecimal p50RespTime = BigDecimal.ZERO;
+
+ @Column(name = "P95_RESP_TIME", precision = 10, scale = 3)
+ private BigDecimal p95RespTime = BigDecimal.ZERO;
+}
diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsYearId.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsYearId.java
new file mode 100644
index 0000000..a37a29f
--- /dev/null
+++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsYearId.java
@@ -0,0 +1,25 @@
+package com.eactive.eai.rms.data.entity.onl.kjb.statistics;
+
+import java.io.Serializable;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * API 통계 연간 복합키
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class ApiStatsYearId implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private String statTime; // YYYY format
+ private String apiName;
+ private String gwInstanceId;
+ private String bizDivCode;
+ private String clientId;
+ private String inboundAdapter;
+ private String outboundAdapter;
+}
diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsYearRepository.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsYearRepository.java
new file mode 100644
index 0000000..5961d89
--- /dev/null
+++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsYearRepository.java
@@ -0,0 +1,10 @@
+package com.eactive.eai.rms.data.entity.onl.kjb.statistics;
+
+import com.eactive.eai.data.jpa.BaseRepository;
+
+/**
+ * API 통계 연간 Repository
+ */
+interface ApiStatsYearRepository extends BaseRepository {
+
+}
diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsYearService.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsYearService.java
new file mode 100644
index 0000000..64145eb
--- /dev/null
+++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsYearService.java
@@ -0,0 +1,143 @@
+package com.eactive.eai.rms.data.entity.onl.kjb.statistics;
+
+import java.time.Year;
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.eactive.eai.data.jpa.AbstractDataService;
+import com.eactive.ext.kjb.statistics.ui.ApiStatsChartUI;
+import com.eactive.ext.kjb.statistics.ui.ApiStatsSearch;
+import com.querydsl.core.BooleanBuilder;
+import com.querydsl.core.types.Projections;
+
+@Service
+@Transactional
+public class ApiStatsYearService
+ extends AbstractDataService {
+
+ public Page selectList(ApiStatsSearch search, Pageable pageable) {
+ QApiStatsYear q = QApiStatsYear.apiStatsYear;
+ BooleanBuilder builder = buildSearchConditions(q, search);
+ return repository.findAll(builder, pageable);
+ }
+
+ public List selectListForExcel(ApiStatsSearch search) {
+ QApiStatsYear q = QApiStatsYear.apiStatsYear;
+ BooleanBuilder builder = buildSearchConditions(q, search);
+
+ return getJPAQueryFactory()
+ .selectFrom(q)
+ .where(builder)
+ .orderBy(q.statTime.desc())
+ .fetch();
+ }
+
+ public List selectChartData(ApiStatsSearch search) {
+ QApiStatsYear q = QApiStatsYear.apiStatsYear;
+ BooleanBuilder builder = new BooleanBuilder();
+
+ calculateDateRange(search);
+ if (search.getParsedStartYear() != null) {
+ builder.and(q.statTime.goe(search.getParsedStartYear()));
+ builder.and(q.statTime.loe(search.getParsedEndYear()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchApiName())) {
+ builder.and(q.apiName.containsIgnoreCase(search.getSearchApiName()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchGwInstanceId())) {
+ builder.and(q.gwInstanceId.containsIgnoreCase(search.getSearchGwInstanceId()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchBizDivCode())) {
+ builder.and(q.bizDivCode.containsIgnoreCase(search.getSearchBizDivCode()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchClientId())) {
+ builder.and(q.clientId.containsIgnoreCase(search.getSearchClientId()));
+ }
+
+ return getJPAQueryFactory()
+ .select(Projections.constructor(ApiStatsChartUI.class,
+ q.statTime,
+ q.totalCnt.sum(),
+ q.successCnt.sum(),
+ q.timeoutCnt.sum(),
+ q.systemErrCnt.sum(),
+ q.bizErrCnt.sum(),
+ q.seq900TimeoutCnt.sum(),
+ q.seq900SystemErrCnt.sum(),
+ q.seq900BizErrCnt.sum(),
+ q.avgRespTime.avg(),
+ q.p50RespTime.avg(),
+ q.p95RespTime.avg()))
+ .from(q)
+ .where(builder)
+ .groupBy(q.statTime)
+ .orderBy(q.statTime.asc())
+ .fetch();
+ }
+
+ private BooleanBuilder buildSearchConditions(QApiStatsYear q, ApiStatsSearch search) {
+ BooleanBuilder builder = new BooleanBuilder();
+
+ calculateDateRange(search);
+ if (search.getParsedStartYear() != null) {
+ builder.and(q.statTime.goe(search.getParsedStartYear()));
+ builder.and(q.statTime.loe(search.getParsedEndYear()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchApiName())) {
+ builder.and(q.apiName.containsIgnoreCase(search.getSearchApiName()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchGwInstanceId())) {
+ builder.and(q.gwInstanceId.containsIgnoreCase(search.getSearchGwInstanceId()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchBizDivCode())) {
+ builder.and(q.bizDivCode.containsIgnoreCase(search.getSearchBizDivCode()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchClientId())) {
+ builder.and(q.clientId.containsIgnoreCase(search.getSearchClientId()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchInboundAdapter())) {
+ builder.and(q.inboundAdapter.containsIgnoreCase(search.getSearchInboundAdapter()));
+ }
+
+ if (StringUtils.isNotBlank(search.getSearchOutboundAdapter())) {
+ builder.and(q.outboundAdapter.containsIgnoreCase(search.getSearchOutboundAdapter()));
+ }
+
+ return builder;
+ }
+
+ /**
+ * 조회 기간 계산
+ */
+ private void calculateDateRange(ApiStatsSearch search) {
+ if (StringUtils.isBlank(search.getSearchStartDateTime())) {
+ return;
+ }
+
+ Year startYear = Year.parse(search.getSearchStartDateTime());
+ Year endYear;
+
+ if (StringUtils.isNotBlank(search.getSearchEndDateTime())) {
+ endYear = Year.parse(search.getSearchEndDateTime());
+ } else {
+ endYear = Year.now();
+ }
+
+ search.setParsedStartYear(String.valueOf(startYear.getValue()));
+ search.setParsedEndYear(String.valueOf(endYear.getValue()));
+ }
+}
diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/logger/EAILogRepositoryImpl.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/logger/EAILogRepositoryImpl.java
index aad5b34..53f4852 100644
--- a/src/main/java/com/eactive/eai/rms/data/entity/onl/logger/EAILogRepositoryImpl.java
+++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/logger/EAILogRepositoryImpl.java
@@ -44,38 +44,47 @@ public class EAILogRepositoryImpl implements EAILogRepository {
QEAIMessageEntity qeaiMessageEntity = QEAIMessageEntity.eAIMessageEntity;
- JPAQuery> query = new JPAQueryFactory(em).from(entityPathBase);
- appendConditions(eaiLogSearch, qeaiLog, entityPathBase, query);
+ JPAQuery> query = new JPAQueryFactory(em)
+ .from(entityPathBase)
+ .leftJoin(qeaiMessageEntity)
+ .on(qeaiLog.eaisvcname.eq(qeaiMessageEntity.eaisvcname));
+
+ appendConditions(eaiLogSearch, qeaiLog, entityPathBase, query, qeaiMessageEntity);
long totalCount = query.select(qeaiLog.id.msgdpstyms.count()).fetchOne();
- if (totalCount == 0) {
- return new PageImpl<>(new ArrayList<>(), pageable, 0);
- }
+ if (totalCount == 0) {
+ return new PageImpl<>(new ArrayList<>(), pageable, 0);
+ }
- List list = query
-// .select(Projections.constructor(EAILogDto.class, qeaiLog, qeaiMessageEntity.eaisvcdesc))
- .select(Projections.constructor(EAILogDto.class,
- qeaiLog.id, qeaiLog.trackasiskey1ctnt, qeaiLog.trackasiskey2ctnt, qeaiLog.keymgtmsgctnt,
- qeaiLog.eaisvcname, qeaiLog.extbizcd, qeaiLog.refkey, qeaiLog.clientname,
- qeaiLog.orgname, qeaiLog.msgprcssyms, qeaiLog.companycode, qeaiLog.eaierrctnt,
- qeaiMessageEntity.eaisvcdesc))
- .from(entityPathBase)
- .leftJoin(qeaiMessageEntity)
- .on(qeaiLog.eaisvcname.eq(qeaiMessageEntity.eaisvcname))
- .orderBy(qeaiLog.id.msgdpstyms.desc(), qeaiLog.id.eaisvcserno.asc(), qeaiLog.id.logprcssserno.asc())
- .limit(1000)
- .fetch();
+ List list = query
+// .select(Projections.constructor(EAILogDto.class, qeaiLog, qeaiMessageEntity.eaisvcdesc))
+ .select(Projections.constructor(EAILogDto.class,
+ qeaiLog.id, qeaiLog.trackasiskey1ctnt, qeaiLog.trackasiskey2ctnt, qeaiLog.keymgtmsgctnt,
+ qeaiLog.eaisvcname, qeaiLog.extbizcd, qeaiLog.refkey, qeaiLog.clientname,
+ qeaiLog.orgname, qeaiLog.msgprcssyms, qeaiLog.companycode, qeaiLog.eaierrctnt,
+ qeaiMessageEntity.eaisvcdesc))
+ // .from(...) --> 이미 위에서 선언했으므로 생략
+ // .leftJoin(...) --> 이미 위에서 선언했으므로 생략
+ .orderBy(qeaiLog.id.msgdpstyms.desc(), qeaiLog.id.eaisvcserno.asc(), qeaiLog.id.logprcssserno.asc())
+ .limit(1000)
+ .fetch();
- return new PageImpl<>(list, pageable, totalCount);
+ return new PageImpl<>(list, pageable, totalCount);
}
public Map countDetailedTransactions(Class extends EAILog> clazz, EAILogSearch eaiLogSearch) {
QEAILog qeaiLog = QEAILog.eAILog;
EntityPathBase> entityPathBase = new EntityPathBase<>(clazz, qeaiLog.getMetadata().getName());
+
+ QEAIMessageEntity qeaiMessageEntity = QEAIMessageEntity.eAIMessageEntity;
- JPAQuery> query = new JPAQueryFactory(em).from(entityPathBase);
- appendConditions(eaiLogSearch, qeaiLog, entityPathBase, query);
+ JPAQuery> query = new JPAQueryFactory(em)
+ .from(entityPathBase)
+ .leftJoin(qeaiMessageEntity)
+ .on(qeaiLog.eaisvcname.eq(qeaiMessageEntity.eaisvcname));
+
+ appendConditions(eaiLogSearch, qeaiLog, entityPathBase, query, qeaiMessageEntity);
List results = query.select(
qeaiLog.id.logprcssserno, // 로그 처리 상태
@@ -91,7 +100,7 @@ public class EAILogRepositoryImpl implements EAILogRepository {
return reult;
}
- private void appendConditions(EAILogSearch eaiLogSearch, QEAILog qeaiLog, EntityPathBase> entityPathBase, JPAQuery> query) {
+ private void appendConditions(EAILogSearch eaiLogSearch, QEAILog qeaiLog, EntityPathBase> entityPathBase, JPAQuery> query, QEAIMessageEntity qeaiMessageEntity) {
if (StringUtils.equalsIgnoreCase(eaiLogSearch.getAuthChk(), "Y")) {
QUserBusiness qUserBusiness = QUserBusiness.userBusiness;
@@ -127,6 +136,10 @@ public class EAILogRepositoryImpl implements EAILogRepository {
if (StringUtils.isNotBlank(eaiLogSearch.getSearchEaiSvcName())) {
query.where(qeaiLog.eaisvcname.containsIgnoreCase(eaiLogSearch.getSearchEaiSvcName()));
}
+
+ if (StringUtils.isNotBlank(eaiLogSearch.getSearchEaiSvcDesc())) {
+ query.where(qeaiMessageEntity.eaisvcdesc.containsIgnoreCase(eaiLogSearch.getSearchEaiSvcDesc()));
+ }
if (StringUtils.isNotBlank(eaiLogSearch.getSearchKeyMgtMsgCtnt())) {
query.where(qeaiLog.keymgtmsgctnt.contains(eaiLogSearch.getSearchKeyMgtMsgCtnt()));
diff --git a/src/main/java/com/eactive/ext/kjb/statistics/ApiStatsAggregationController.java b/src/main/java/com/eactive/ext/kjb/statistics/ApiStatsAggregationController.java
new file mode 100644
index 0000000..a8b1df0
--- /dev/null
+++ b/src/main/java/com/eactive/ext/kjb/statistics/ApiStatsAggregationController.java
@@ -0,0 +1,185 @@
+package com.eactive.ext.kjb.statistics;
+
+import java.time.LocalDate;
+import java.time.Year;
+import java.time.YearMonth;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+
+import com.eactive.ext.kjb.statistics.job.DailyToMonthlyAggregationJob;
+import com.eactive.ext.kjb.statistics.job.HourlyToDailyAggregationJob;
+import com.eactive.ext.kjb.statistics.job.MonthlyToYearlyAggregationJob;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * API 통계 집계 수동 실행 Controller
+ */
+@Slf4j
+@Controller
+@RequiredArgsConstructor
+public class ApiStatsAggregationController {
+
+ private final HourlyToDailyAggregationJob hourlyToDailyAggregationJob;
+ private final DailyToMonthlyAggregationJob dailyToMonthlyAggregationJob;
+ private final MonthlyToYearlyAggregationJob monthlyToYearlyAggregationJob;
+
+ @GetMapping(value = "/onl/kjb/statistics/apiStatsAggregationMan.view")
+ public String view() {
+ return "/onl/kjb/statistics/apiStatsAggregationMan";
+ }
+
+ /**
+ * 시간별→일별 통계 집계 수동 실행
+ * @param targetDate 집계 대상 날짜 (yyyyMMdd 형식, 예: 20250120)
+ * @return 처리 결과
+ */
+ @PostMapping(value = "/onl/kjb/statistics/apiStatsAggregationMan.json", params = "cmd=AGGREGATION_DAY")
+ public ResponseEntity