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

This commit is contained in:
Rinjae
2026-09-01 18:26:47 +09:00
40 changed files with 956 additions and 316 deletions
+1 -1
View File
@@ -175,6 +175,6 @@
</jsp-config> </jsp-config>
<session-config> <session-config>
<session-timeout>30</session-timeout> <session-timeout>60</session-timeout>
</session-config> </session-config>
</web-app> </web-app>
+1 -1
View File
@@ -145,6 +145,6 @@
</jsp-config> </jsp-config>
<session-config> <session-config>
<session-timeout>30</session-timeout> <session-timeout>60</session-timeout>
</session-config> </session-config>
</web-app> </web-app>
+1 -1
View File
@@ -3,7 +3,7 @@
<context-root>monitoring</context-root> <context-root>monitoring</context-root>
<session-descriptor> <session-descriptor>
<timeout-secs>1800</timeout-secs> <timeout-secs>3600</timeout-secs>
<cookie-name>JSESSIONID_EMS</cookie-name> <cookie-name>JSESSIONID_EMS</cookie-name>
<persistent-store-type>replicated_if_clustered</persistent-store-type> <persistent-store-type>replicated_if_clustered</persistent-store-type>
</session-descriptor> </session-descriptor>
+19 -3
View File
@@ -299,8 +299,12 @@
if (response.smsAuthRequired) { if (response.smsAuthRequired) {
// 비밀번호는 아직 반영되지 않음 - SMS 인증 완료 시 최종 반영됨 // 비밀번호는 아직 반영되지 않음 - SMS 인증 완료 시 최종 반영됨
smsAuthPurpose = 'PASSWORD_CHANGE'; smsAuthPurpose = 'PASSWORD_CHANGE';
// 두 모달이 겹치면 이전 모달의 enforceFocus/backdrop 이 남아 입력이 막히므로
// #pwdChgModal 이 완전히 닫힌 뒤 SMS 인증 모달을 연다
$('#pwdChgModal').one('hidden.bs.modal', function() {
showSmsAuthModal(response);
});
$('#pwdChgModal').modal('hide'); $('#pwdChgModal').modal('hide');
showSmsAuthModal(response);
return; return;
} }
alert(response.message); alert(response.message);
@@ -369,6 +373,19 @@
verifySmsAuthCode(); verifySmsAuthCode();
} }
}); });
// 인증번호는 숫자만 허용 (타이핑/붙여넣기/드래그 공통, 백스페이스·방향키는 방해하지 않음)
$("#smsAuthCode").on("input", function(){
var digits = this.value.replace(/[^0-9]/g, '').slice(0, 6);
if (this.value !== digits) {
this.value = digits;
}
});
// SMS 인증 모달이 완전히 열린 뒤 인증번호 입력창에 포커스
$('#smsAuthModal').on('shown.bs.modal', function() {
$('#smsAuthCode').trigger('focus');
});
}); });
function fncSsoLogin() { function fncSsoLogin() {
@@ -487,8 +504,7 @@
<p>인증번호가 <strong><span id="smsAuthMaskedPhone"></span></strong>로 발송되었습니다.</p> <p>인증번호가 <strong><span id="smsAuthMaskedPhone"></span></strong>로 발송되었습니다.</p>
<div class="form-group"> <div class="form-group">
<input type="text" id="smsAuthCode" class="form-control" <input type="text" id="smsAuthCode" class="form-control"
placeholder="인증번호 6자리" maxlength="6" placeholder="인증번호 6자리" maxlength="6" inputmode="numeric" autocomplete="off">
onkeypress="return event.charCode >= 48 && event.charCode <= 57">
</div> </div>
<p id="smsAuthTestHint" class="text-warning" style="display:none;"></p> <p id="smsAuthTestHint" class="text-warning" style="display:none;"></p>
<p class="text-muted">남은 시간: <span id="smsAuthRemainingTime" class="text-danger font-weight-bold">60</span>초</p> <p class="text-muted">남은 시간: <span id="smsAuthRemainingTime" class="text-danger font-weight-bold">60</span>초</p>
+1 -1
View File
@@ -596,7 +596,7 @@
<ul> <ul>
<li style="width:240px;"> <li style="width:240px;">
<a style="width:240px; cursor: default" <a style="width:240px; cursor: default"
href="#"><span><%=SessionManager.getUserName(request) %>(<%=SessionManager.getUserId(request) %></span> href="#"><span><%=SessionManager.getUserName(request) %>(<%=SessionManager.getUserId(request) %>)</span>
<span onClick="javascript:openColorPopup();"><%=localeMessage.getString("screen.customer") %></span> <span onClick="javascript:openColorPopup();"><%=localeMessage.getString("screen.customer") %></span>
<% if (!"".equals(LastLoginYms.trim())) { %> <% if (!"".equals(LastLoginYms.trim())) { %>
<span style="display:block;font-size:10px;"><%=localeMessage.getString("screen.lastLogin") %> : <%=LastLoginYms%></span> <span style="display:block;font-size:10px;"><%=localeMessage.getString("screen.lastLogin") %> : <%=LastLoginYms%></span>
@@ -190,6 +190,8 @@
var key = "${param.clientId}"; var key = "${param.clientId}";
if (key != "" && key != "null") { if (key != "" && key != "null") {
isDetail = true; isDetail = true;
} else {
$('input[name="grantTypes_"]').prop('checked', true);
} }
init(key, detail); init(key, detail);
@@ -506,18 +508,17 @@
<td><input type="text" name="scope"/></td> <td><input type="text" name="scope"/></td>
</tr> </tr>
<tr> <tr>
<th><%= localeMessage.getString("clntManDtl.grntTp") %> <th><%= localeMessage.getString("clntManDtl.grntTp") %> <small>(*)</small></th>
(<%= localeMessage.getString("clntManDtl.sprt") %> ,) <small>(*)</small></th>
<td> <td>
<input type="checkbox" name="grantTypes_" value="client_credentials"> client_credentials <input type="checkbox" name="grantTypes_" value="client_credentials"> client_credentials
<%-- <input type="checkbox" name="grantTypes_" value="authorization_code"> authorization_code--%> <%-- <input type="checkbox" name="grantTypes_" value="authorization_code"> authorization_code--%>
<%-- <input type="checkbox" name="grantTypes_" value="password"> password--%> <%-- <input type="checkbox" name="grantTypes_" value="password"> password--%>
<input type="checkbox" name="grantTypes_" value="refresh_token"> refresh_token <!-- <input type="checkbox" name="grantTypes_" value="refresh_token"> refresh_token -->
</td> </td>
<input type="hidden" name="grantTypes"/> <input type="hidden" name="grantTypes"/>
</td> </td>
</tr> </tr>
<tr> <tr style="display: none;">
<th>REDIRECT URI</th> <th>REDIRECT URI</th>
<td><input type="text" name="redirectUri"/></td> <td><input type="text" name="redirectUri"/></td>
</tr> </tr>
@@ -538,7 +539,7 @@
</th> </th>
<td><input type="text" name="accessTokenValiditySeconds"/></td> <td><input type="text" name="accessTokenValiditySeconds"/></td>
</tr> </tr>
<tr> <tr style="display: none;">
<th><%= localeMessage.getString("clntManDtl.rfrshTknExprtnDt") %> <th><%= localeMessage.getString("clntManDtl.rfrshTknExprtnDt") %>
</th> </th>
<td><input type="text" name="refreshTokenValiditySeconds"/></td> <td><input type="text" name="refreshTokenValiditySeconds"/></td>
@@ -555,7 +556,7 @@
</th> </th>
<td><input type="text" name="allowedIps"/></td> <td><input type="text" name="allowedIps"/></td>
</tr> </tr>
<tr> <tr style="display: none;">
<th>Security Key</th> <th>Security Key</th>
<td><textarea name="securityKey" rows="3" style="width:100%;"></textarea></td> <td><textarea name="securityKey" rows="3" style="width:100%;"></textarea></td>
</tr> </tr>
@@ -47,7 +47,7 @@
mtype: 'POST', mtype: 'POST',
url: url, url: url,
postData: getSearchForJqgrid("cmd", "LIST"), postData: getSearchForJqgrid("cmd", "LIST"),
colNames: ['No', 'Service Name', 'Command', '레이아웃명', '연동시간', '결과', '결과메세지'], colNames: ['No', '서비스명', 'Command', '연동구분명', '연동시간', '결과', '결과메세지'],
colModel: [ colModel: [
{name: 'seqno', align: 'center', width: 50, sortable: false}, {name: 'seqno', align: 'center', width: 50, sortable: false},
{name: 'serviceName', align: 'center', width: 100, sortable: false}, {name: 'serviceName', align: 'center', width: 100, sortable: false},
@@ -123,8 +123,8 @@
<div class="search_wrap"> <div class="search_wrap">
<button type="button" class="cssbtn" id="btn_search" level="R"><i class="material-icons">search</i> <%= localeMessage.getString("button.search") %></button> <button type="button" class="cssbtn" id="btn_search" level="R"><i class="material-icons">search</i> <%= localeMessage.getString("button.search") %></button>
</div> </div>
<div class="title">전문레이아웃 연동 이력<span class="tooltip">IIM에서 연동한 인터페이스 및 전문 레이아웃 이력을 조회합니다.</span></div> <div class="title">연동 배포 이력<span class="tooltip">IIM 레이아웃 연동 이력과 eCams 인터페이스 배포 이력을 조회합니다.</span></div>
<form id="ajaxForm" onsubmit="return false;"> <form id="ajaxForm" onsubmit="return false;">
<table class="search_condition" cellspacing=0;> <table class="search_condition" cellspacing=0;>
<colgroup> <colgroup>
<col style="width:120px;"><col style="width:260px;"> <col style="width:120px;"><col style="width:260px;">
@@ -133,8 +133,6 @@
</colgroup> </colgroup>
<tbody> <tbody>
<tr> <tr>
<th>레이아웃명</th>
<td><input type="text" name="searchLoutName" autocomplete="off" style="width:95%;"></td>
<th>연동시간</th> <th>연동시간</th>
<td> <td>
<input type="text" name="searchStartYYYYMMDD" id="startDatepicker" readonly="readonly" value="" size="10" style="width:100px; border:1px solid #ebebec;"> <input type="text" name="searchStartYYYYMMDD" id="startDatepicker" readonly="readonly" value="" size="10" style="width:100px; border:1px solid #ebebec;">
@@ -143,6 +141,14 @@
<input type="hidden" name="searchStartDate" value=""> <input type="hidden" name="searchStartDate" value="">
<input type="hidden" name="searchEndDate" value=""> <input type="hidden" name="searchEndDate" value="">
</th> </th>
<th>서비스명</th>
<td><input type="text" name="searchServiceName" autocomplete="off" style="width:95%;"></td>
<th>Command</th>
<td><input type="text" name="searchCommand" autocomplete="off" style="width:95%;"></td>
</tr>
<tr>
<th>연동구분명</th>
<td><input type="text" name="searchLoutName" autocomplete="off" style="width:95%;"></td>
<th>결과</th> <th>결과</th>
<td> <td>
<div class="select-style" style="display:inline-block; margin-left:6px;"> <div class="select-style" style="display:inline-block; margin-left:6px;">
@@ -153,6 +159,8 @@
</select> </select>
</div> </div>
</td> </td>
<th></th>
<td></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -89,7 +89,7 @@
mtype: 'POST', mtype: 'POST',
url: url, url: url,
postData: getSearchForJqgrid("cmd", "LIST"), postData: getSearchForJqgrid("cmd", "LIST"),
colNames: ['id', 'No.', '기관명', '이벤트유형', 'Target URL', 'Proxy URL', 'Status', '성공여부', '발송서버', '발송일시'], colNames: ['id', 'No.', '기관명', '이벤트유형', 'Target URL', 'Proxy URL', 'Status', '성공여부', '재시도', '발송서버', '발송일시'],
colModel: [ colModel: [
{name: 'id', align: 'center', key: true, hidden: true}, {name: 'id', align: 'center', key: true, hidden: true},
{name: 'rowNum', align: 'center', width: 45, sortable: false}, {name: 'rowNum', align: 'center', width: 45, sortable: false},
@@ -99,11 +99,12 @@
{name: 'proxyUrl', align: 'left', width: 300, formatter: formatTargetUrl}, {name: 'proxyUrl', align: 'left', width: 300, formatter: formatTargetUrl},
{name: 'statusCode', align: 'center', width: 60}, {name: 'statusCode', align: 'center', width: 60},
{name: 'success', align: 'center', width: 70, formatter: formatSuccess}, {name: 'success', align: 'center', width: 70, formatter: formatSuccess},
{name: 'retryCount', align: 'center', width: 60},
{name: 'sendBy', align: 'center', width: 100}, {name: 'sendBy', align: 'center', width: 100},
{name: 'sentAt', align: 'center', width: 140} {name: 'sentAt', align: 'center', width: 140}
], ],
jsonReader: {repeatitems: false}, jsonReader: {repeatitems: false},
pager: $('#pager'), pager: $('#pager'),
page: '${param.page}', page: '${param.page}',
rowNum: '${rmsDefaultRowNum}', rowNum: '${rmsDefaultRowNum}',
autoheight: true, autoheight: true,
@@ -33,6 +33,18 @@
width: 100%; width: 100%;
height: 250px; height: 250px;
} }
/* 달력 팝업이 파이차트 가운데 숫자(z-index:10) 위로 오도록 */
#ui-datepicker-div {
z-index: 9999 !important;
}
.cssbtn.small {
height: 24px;
border-radius: 2px;
min-width: 24px;
box-shadow: none;
border-color: #ebebec;
font-size: 11px;
}
</style> </style>
<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>
@@ -286,6 +298,28 @@
fetchChartData(); fetchChartData();
} }
// 조회기간을 시작일 기준 이전/다음 달의 1일~말일로 변경 후 조회 (direction: -1 이전달, 1 다음달)
function shiftMonthRange(direction) {
var v = $("input[name=searchStartDateTime]").val().replace(/-/g, "");
if (!v || v.length < 6) return;
var year = parseInt(v.substring(0, 4));
var month = parseInt(v.substring(4, 6)) - 1; // 0-based
var first = new Date(year, month + direction, 1);
var last = new Date(year, month + direction + 1, 0); // 해당 달의 말일
function pad(n) { return String(n).padStart(2, '0'); }
function fmt(d) {
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate());
}
$("input[name=searchStartDateTime]").val(fmt(first));
$("input[name=searchEndDateTime]").val(fmt(last));
search();
}
function exportToExcel(isSummary) { function exportToExcel(isSummary) {
var cmdType = isSummary ? 'EXCEL_EXPORT_SUMMARY' : 'EXCEL_EXPORT'; var cmdType = isSummary ? 'EXCEL_EXPORT_SUMMARY' : 'EXCEL_EXPORT';
console.log('[Excel Export] Starting... (type: ' + cmdType + ')'); console.log('[Excel Export] Starting... (type: ' + cmdType + ')');
@@ -416,12 +450,13 @@
mtype: 'POST', mtype: 'POST',
postData: gridPostData, postData: gridPostData,
colNames: [ colNames: [
'API명', 'API ID', 'API명',
'총건수', '성공', 'Timeout', '시스템오류', '총건수', '성공', 'Timeout', '시스템오류',
'평균응답(ms)', '최소응답(ms)', '최대응답(ms)' '평균응답(ms)', '최소응답(ms)', '최대응답(ms)'
], ],
colModel: [ colModel: [
{ name: 'apiName', align: 'left', width: '250', sortable: false }, { name: 'apiName', align: 'left', width: '150', sortable: false },
{ name: 'apiDesc', align: 'left', width: '250', sortable: false },
{ name: 'totalCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false }, { name: 'totalCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false },
{ name: 'successCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false }, { name: 'successCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false },
{ name: 'timeoutCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false }, { name: 'timeoutCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false },
@@ -458,7 +493,7 @@
mtype: 'POST', mtype: 'POST',
postData: gridPostData, postData: gridPostData,
colNames: [ colNames: [
'통계시간', 'API', '인스턴스', '업무구분', '클라이언트ID', '통계시간', 'API ID', '인스턴스', '업무구분', '클라이언트ID',
'Inbound Adapter', 'Outbound Adapter', 'Inbound Adapter', 'Outbound Adapter',
'총건수', '성공', 'Timeout', '시스템오류', '총건수', '성공', 'Timeout', '시스템오류',
'평균응답(ms)', '최소응답(ms)', '최대응답(ms)' '평균응답(ms)', '최소응답(ms)', '최대응답(ms)'
@@ -505,6 +540,8 @@
$("input[name=searchStartDateTime]").inputmask("9999-99-99", { 'autoUnmask': true }); $("input[name=searchStartDateTime]").inputmask("9999-99-99", { 'autoUnmask': true });
$("input[name=searchEndDateTime]").inputmask("9999-99-99", { 'autoUnmask': true }); $("input[name=searchEndDateTime]").inputmask("9999-99-99", { 'autoUnmask': true });
$("input[name=searchStartDateTime], input[name=searchEndDateTime]").datepicker();
// 기본값 설정 (어제 기준 해당 월 1일 ~ 어제) // 기본값 설정 (어제 기준 해당 월 1일 ~ 어제)
var today = getToday(); var today = getToday();
var todayStr = today.replace(/-/g, ''); var todayStr = today.replace(/-/g, '');
@@ -544,6 +581,14 @@
search(); search();
}); });
$("#btnPrevMonth").click(function() {
shiftMonthRange(-1);
});
$("#btnNextMonth").click(function() {
shiftMonthRange(1);
});
$("#btn_excel_export_summary").click(function() { $("#btn_excel_export_summary").click(function() {
exportToExcel(true); exportToExcel(true);
}); });
@@ -598,11 +643,13 @@
<input type="text" name="searchStartDateTime" value="${param.searchStartDateTime}" style="width:100px;"> <input type="text" name="searchStartDateTime" value="${param.searchStartDateTime}" style="width:100px;">
~ ~
<input type="text" name="searchEndDateTime" value="${param.searchEndDateTime}" style="width:100px;"> <input type="text" name="searchEndDateTime" value="${param.searchEndDateTime}" style="width:100px;">
<button type="button" id="btnPrevMonth" class="cssbtn small">&lt;</button>
<button type="button" id="btnNextMonth" class="cssbtn small">&gt;</button>
<span style="color:#888; font-size:12px; margin-left:10px;">(최대 31일)</span> <span style="color:#888; font-size:12px; margin-left:10px;">(최대 31일)</span>
</td> </td>
</tr> </tr>
<tr> <tr>
<th>API</th> <th>API ID</th>
<td> <td>
<input type="text" name="searchApiName" value="${param.searchApiName}"> <input type="text" name="searchApiName" value="${param.searchApiName}">
</td> </td>
@@ -639,11 +686,11 @@
</div> </div>
<div id="callChart" class="chart"></div> <div id="callChart" class="chart"></div>
</div> </div>
<!-- 요약 그리드 --> <!-- 요약 그리드 -->
<div style="margin-top: 20px;"> <div style="margin-top: 20px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;"> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<div class="title" style="margin: 0;">API별 요약 통계</div> <div class="title" style="margin: 0;">API별 요약 통계</div>
<button type="button" class="cssbtn" id="btn_excel_export_summary" level="R"> <button type="button" class="cssbtn" id="btn_excel_export_summary" level="R">
<i class="material-icons">file_download</i> Excel 다운로드 (요약) <i class="material-icons">file_download</i> Excel 다운로드 (요약)
</button> </button>
@@ -33,6 +33,18 @@
width: 100%; width: 100%;
height: 250px; height: 250px;
} }
/* 달력 팝업이 파이차트 가운데 숫자(z-index:10) 위로 오도록 */
#ui-datepicker-div {
z-index: 9999 !important;
}
.cssbtn.small {
height: 24px;
border-radius: 2px;
min-width: 24px;
box-shadow: none;
border-color: #ebebec;
font-size: 11px;
}
</style> </style>
<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>
@@ -292,6 +304,25 @@
fetchChartData(); fetchChartData();
} }
// 조회일자를 하루 이동 후 조회 (direction: -1 이전날, 1 이후날)
function shiftSearchDate(direction) {
var v = $("input[name=searchDate]").val().replace(/-/g, "");
if (!v || v.length !== 8) return;
var d = new Date(
parseInt(v.substring(0, 4)),
parseInt(v.substring(4, 6)) - 1,
parseInt(v.substring(6, 8))
);
d.setDate(d.getDate() + direction);
function pad(n) { return String(n).padStart(2, '0'); }
$("input[name=searchDate]").val(
d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()));
search();
}
function exportToExcel(isSummary) { function exportToExcel(isSummary) {
var cmdType = isSummary ? 'EXCEL_EXPORT_SUMMARY' : 'EXCEL_EXPORT'; var cmdType = isSummary ? 'EXCEL_EXPORT_SUMMARY' : 'EXCEL_EXPORT';
console.log('[Excel Export] Starting... (type: ' + cmdType + ')'); console.log('[Excel Export] Starting... (type: ' + cmdType + ')');
@@ -418,12 +449,13 @@
mtype: 'POST', mtype: 'POST',
postData: gridPostData, postData: gridPostData,
colNames: [ colNames: [
'API명', 'API ID', 'API 명',
'총건수', '성공', 'Timeout', '시스템오류', '총건수', '성공', 'Timeout', '시스템오류',
'평균응답(ms)', '최소응답(ms)', '최대응답(ms)' '평균응답(ms)', '최소응답(ms)', '최대응답(ms)'
], ],
colModel: [ colModel: [
{ name: 'apiName', align: 'left', width: '250', sortable: false }, { name: 'apiName', align: 'left', width: '150', sortable: false },
{ name: 'apiDesc', align: 'left', width: '250', sortable: false },
{ name: 'totalCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false }, { name: 'totalCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false },
{ name: 'successCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false }, { name: 'successCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false },
{ name: 'timeoutCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false }, { name: 'timeoutCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false },
@@ -459,8 +491,8 @@
datatype: "json", datatype: "json",
mtype: 'POST', mtype: 'POST',
postData: gridPostData, postData: gridPostData,
colNames: [ colNames: [
'통계시간', 'API', '인스턴스', '업무구분', '클라이언트ID', '통계시간', 'API ID', '인스턴스', '업무구분', '클라이언트ID',
'Inbound Adapter', 'Outbound Adapter', 'Inbound Adapter', 'Outbound Adapter',
'총건수', '성공', 'Timeout', '시스템오류', '총건수', '성공', 'Timeout', '시스템오류',
'평균응답(ms)', '최소응답(ms)', '최대응답(ms)' '평균응답(ms)', '최소응답(ms)', '최대응답(ms)'
@@ -506,6 +538,8 @@
// 날짜 입력 마스크 // 날짜 입력 마스크
$("input[name=searchDate]").inputmask("9999-99-99", { 'autoUnmask': true }); $("input[name=searchDate]").inputmask("9999-99-99", { 'autoUnmask': true });
$("input[name=searchDate]").datepicker();
// 기본값 설정 (오늘) // 기본값 설정 (오늘)
var today = getToday(); var today = getToday();
if (!$("input[name=searchDate]").val()) { if (!$("input[name=searchDate]").val()) {
@@ -524,6 +558,14 @@
search(); search();
}); });
$("#btnPrevDate").click(function() {
shiftSearchDate(-1);
});
$("#btnNextDate").click(function() {
shiftSearchDate(1);
});
$("#btn_excel_export_summary").click(function() { $("#btn_excel_export_summary").click(function() {
exportToExcel(true); exportToExcel(true);
}); });
@@ -576,11 +618,13 @@
<th>조회일자</th> <th>조회일자</th>
<td colspan="5"> <td colspan="5">
<input type="text" name="searchDate" value="${param.searchDate}" style="width:100px;"> <input type="text" name="searchDate" value="${param.searchDate}" style="width:100px;">
<button type="button" id="btnPrevDate" class="cssbtn small">&lt;</button>
<button type="button" id="btnNextDate" class="cssbtn small">&gt;</button>
<span style="color:#888; font-size:12px; margin-left:10px;">(선택일 00시 ~ 23시 조회)</span> <span style="color:#888; font-size:12px; margin-left:10px;">(선택일 00시 ~ 23시 조회)</span>
</td> </td>
</tr> </tr>
<tr> <tr>
<th>API</th> <th>API ID</th>
<td> <td>
<input type="text" name="searchApiName" value="${param.searchApiName}"> <input type="text" name="searchApiName" value="${param.searchApiName}">
</td> </td>
@@ -621,7 +665,7 @@
<!-- 요약 그리드 --> <!-- 요약 그리드 -->
<div style="margin-top: 20px;"> <div style="margin-top: 20px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;"> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<div class="title" style="margin: 0;">API별 요약 통계</div> <div class="title" style="margin: 0;">API별 요약 통계</div>
<button type="button" class="cssbtn" id="btn_excel_export_summary" level="R"> <button type="button" class="cssbtn" id="btn_excel_export_summary" level="R">
<i class="material-icons">file_download</i> Excel 다운로드 (요약) <i class="material-icons">file_download</i> Excel 다운로드 (요약)
</button> </button>
@@ -26,6 +26,18 @@
border: 1px solid #ddd; border: 1px solid #ddd;
background: #fff; background: #fff;
} }
.cssbtn.small {
height: 24px;
border-radius: 2px;
min-width: 24px;
box-shadow: none;
border-color: #ebebec;
font-size: 11px;
}
/* 달력 팝업이 파이차트 가운데 숫자(z-index:10) 위로 오도록 */
#ui-datepicker-div {
z-index: 9999 !important;
}
</style> </style>
<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>
@@ -203,7 +215,6 @@
function validateAndAdjustDateRange() { function validateAndAdjustDateRange() {
var startDate = $("input[name=searchStartDate]").val().replace(/-/g, ""); var startDate = $("input[name=searchStartDate]").val().replace(/-/g, "");
var startTime = $("input[name=searchStartTime]").val().replace(/:/g, ""); var startTime = $("input[name=searchStartTime]").val().replace(/:/g, "");
var endDate = $("input[name=searchEndDate]").val().replace(/-/g, "");
var endTime = $("input[name=searchEndTime]").val().replace(/:/g, ""); var endTime = $("input[name=searchEndTime]").val().replace(/:/g, "");
if (!startDate || !startTime) return null; if (!startDate || !startTime) return null;
@@ -219,35 +230,43 @@
); );
} }
// UI 업데이트 헬퍼 // 종료일시(yyyyMMddHHmm) 포맷 헬퍼
function updateEndDateTime(dt) { function formatDateTime(dt) {
var d = dt.getFullYear() + return dt.getFullYear() +
String(dt.getMonth() + 1).padStart(2, '0') + String(dt.getMonth() + 1).padStart(2, '0') +
String(dt.getDate()).padStart(2, '0'); String(dt.getDate()).padStart(2, '0') +
var t = String(dt.getHours()).padStart(2, '0') + String(dt.getHours()).padStart(2, '0') +
String(dt.getMinutes()).padStart(2, '0'); String(dt.getMinutes()).padStart(2, '0');
$("input[name=searchEndDate]").val(d.substring(0, 4) + '-' + d.substring(4, 6) + '-' + d.substring(6, 8)); }
$("input[name=searchEndTime]").val(t.substring(0, 2) + ':' + t.substring(2, 4));
return d + t; // 종료시각 UI 업데이트 헬퍼
function updateEndTime(dt) {
$("input[name=searchEndTime]").val(
String(dt.getHours()).padStart(2, '0') + ':' + String(dt.getMinutes()).padStart(2, '0'));
return formatDateTime(dt);
} }
var start = parseDateTime(startDate, startTime); var start = parseDateTime(startDate, startTime);
var startDateTime = startDate + startTime; var startDateTime = startDate + startTime;
var endDateTime; var endDateTime;
if (endDate && endTime) { if (endTime) {
var end = parseDateTime(endDate, endTime); // 종료일은 시작일 기준. 종료시각이 시작시각보다 이르면 자정을 넘긴 것으로 보고 +1일
var end = parseDateTime(startDate, endTime);
if (end < start) {
end = new Date(end.getTime() + 24 * 60 * 60 * 1000);
}
var diffMinutes = (end - start) / (1000 * 60); var diffMinutes = (end - start) / (1000 * 60);
if (diffMinutes > 60) { if (diffMinutes > 60) {
// 1시간 초과 시 시작 + 1시간으로 조정 // 1시간 초과 시 시작 + 1시간으로 조정
endDateTime = updateEndDateTime(new Date(start.getTime() + 60 * 60 * 1000)); endDateTime = updateEndTime(new Date(start.getTime() + 60 * 60 * 1000));
} else { } else {
endDateTime = endDate + endTime; endDateTime = formatDateTime(end);
} }
} else { } else {
// 종료시간 미입력 시 시작 + 1시간으로 설정 // 종료시간 미입력 시 시작 + 1시간으로 설정
endDateTime = updateEndDateTime(new Date(start.getTime() + 60 * 60 * 1000)); endDateTime = updateEndTime(new Date(start.getTime() + 60 * 60 * 1000));
} }
return { start: startDateTime, end: endDateTime }; return { start: startDateTime, end: endDateTime };
@@ -302,6 +321,44 @@
fetchChartData(range); fetchChartData(range);
} }
// 조회기간을 정시 단위 1시간 구간으로 이동 (direction: -1 이전, 1 이후)
// ex) 시작 08:35, 이전 클릭 -> 08:00~09:00, 다시 -> 07:00~08:00
// ex) 시작 08:35, 이후 클릭 -> 09:00~10:00, 다시 -> 10:00~11:00
function shiftTimeRange(direction) {
var startDate = $("input[name=searchStartDate]").val().replace(/-/g, "");
var startTime = $("input[name=searchStartTime]").val().replace(/:/g, "");
if (!startDate || !startTime) return;
var start = new Date(
parseInt(startDate.substring(0, 4)),
parseInt(startDate.substring(4, 6)) - 1,
parseInt(startDate.substring(6, 8)),
parseInt(startTime.substring(0, 2)),
parseInt(startTime.substring(2, 4))
);
if (direction < 0) {
// 분 단위가 있으면 정시로 내림, 이미 정시면 1시간 전
if (start.getMinutes() === 0) start.setHours(start.getHours() - 1);
else start.setMinutes(0);
} else {
// 분 단위가 있으면 다음 정시로 올림, 이미 정시면 1시간 후
start.setMinutes(0);
start.setHours(start.getHours() + 1);
}
var end = new Date(start.getTime() + 60 * 60 * 1000);
function pad(n) { return String(n).padStart(2, '0'); }
$("input[name=searchStartDate]").val(
start.getFullYear() + '-' + pad(start.getMonth() + 1) + '-' + pad(start.getDate()));
$("input[name=searchStartTime]").val(pad(start.getHours()) + ':' + pad(start.getMinutes()));
$("input[name=searchEndTime]").val(pad(end.getHours()) + ':' + pad(end.getMinutes()));
search();
}
function exportToExcel(isSummary) { function exportToExcel(isSummary) {
var cmdType = isSummary ? 'EXCEL_EXPORT_SUMMARY' : 'EXCEL_EXPORT'; var cmdType = isSummary ? 'EXCEL_EXPORT_SUMMARY' : 'EXCEL_EXPORT';
console.log('[Excel Export] Starting... (type: ' + cmdType + ')'); console.log('[Excel Export] Starting... (type: ' + cmdType + ')');
@@ -317,13 +374,10 @@
serviceType: '${param.serviceType}' serviceType: '${param.serviceType}'
}; };
var startDate = $("input[name=searchStartDate]").val().replace(/-/g, ""); var range = validateAndAdjustDateRange();
var startTime = $("input[name=searchStartTime]").val().replace(/:/g, ""); if (range) {
var endDate = $("input[name=searchEndDate]").val().replace(/-/g, ""); postData.searchStartDateTime = range.start;
var endTime = $("input[name=searchEndTime]").val().replace(/:/g, ""); postData.searchEndDateTime = range.end;
if (startDate && startTime) {
postData.searchStartDateTime = startDate + startTime;
postData.searchEndDateTime = endDate + endTime;
} }
console.log('[Excel Export] Request data:', postData); console.log('[Excel Export] Request data:', postData);
@@ -431,12 +485,13 @@
mtype: 'POST', mtype: 'POST',
postData: gridPostData, postData: gridPostData,
colNames: [ colNames: [
'API명', 'API ID', 'API 명',
'총건수', '성공', 'Timeout', '시스템오류', '총건수', '성공', 'Timeout', '시스템오류',
'평균응답(ms)', '최소응답(ms)', '최대응답(ms)' '평균응답(ms)', '최소응답(ms)', '최대응답(ms)'
], ],
colModel: [ colModel: [
{ name: 'apiName', align: 'left', width: '250', sortable: false }, { name: 'apiName', align: 'left', width: '150', sortable: false },
{ name: 'apiDesc', align: 'left', width: '250', sortable: false },
{ name: 'totalCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false }, { name: 'totalCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false },
{ name: 'successCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false }, { name: 'successCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false },
{ name: 'timeoutCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false }, { name: 'timeoutCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false },
@@ -473,7 +528,7 @@
mtype: 'POST', mtype: 'POST',
postData: gridPostData, postData: gridPostData,
colNames: [ colNames: [
'통계시간', 'API', '인스턴스', '업무구분', '클라이언트ID', '통계시간', 'API ID', '인스턴스', '업무구분', '클라이언트ID',
'Inbound Adapter', 'Outbound Adapter', 'Inbound Adapter', 'Outbound Adapter',
'총건수', '성공', 'Timeout', '시스템오류', '총건수', '성공', 'Timeout', '시스템오류',
'평균응답(ms)', '최소응답(ms)', '최대응답(ms)' '평균응답(ms)', '최소응답(ms)', '최대응답(ms)'
@@ -517,9 +572,11 @@
$(document).ready(function() { $(document).ready(function() {
// 날짜/시간 입력 마스크 // 날짜/시간 입력 마스크
$("input[name=searchStartDate], input[name=searchEndDate]").inputmask("9999-99-99", { 'autoUnmask': true }); $("input[name=searchStartDate]").inputmask("9999-99-99", { 'autoUnmask': true });
$("input[name=searchStartTime], input[name=searchEndTime]").inputmask("99:99", { 'autoUnmask': true }); $("input[name=searchStartTime], input[name=searchEndTime]").inputmask("99:99", { 'autoUnmask': true });
$("input[name=searchStartDate]").datepicker();
// 기본값 설정 (현재 시간(분) 기준 1시간 전 ~ 현재 시간(분)) // 기본값 설정 (현재 시간(분) 기준 1시간 전 ~ 현재 시간(분))
var now = new Date(); var now = new Date();
var oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000); var oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000);
@@ -537,8 +594,7 @@
$("input[name=searchStartDate]").val(formatDate(oneHourAgo)); $("input[name=searchStartDate]").val(formatDate(oneHourAgo));
$("input[name=searchStartTime]").val(formatTime(oneHourAgo)); $("input[name=searchStartTime]").val(formatTime(oneHourAgo));
} }
if (!$("input[name=searchEndDate]").val()) { if (!$("input[name=searchEndTime]").val()) {
$("input[name=searchEndDate]").val(formatDate(now));
$("input[name=searchEndTime]").val(formatTime(now)); $("input[name=searchEndTime]").val(formatTime(now));
} }
@@ -554,6 +610,14 @@
search(); search();
}); });
$("#btnPrevTime").click(function() {
shiftTimeRange(-1);
});
$("#btnNextTime").click(function() {
shiftTimeRange(1);
});
$("#btn_excel_export_summary").click(function() { $("#btn_excel_export_summary").click(function() {
exportToExcel(true); exportToExcel(true);
}); });
@@ -568,8 +632,8 @@
} }
}); });
// 종료일/시간 변경 시 1시간 초과 검증 // 종료시각 변경 시 1시간 초과 검증
$("input[name=searchEndDate], input[name=searchEndTime]").on('change blur', function() { $("input[name=searchEndTime]").on('change blur', function() {
validateAndAdjustDateRange(); validateAndAdjustDateRange();
}); });
@@ -602,14 +666,15 @@
<tr> <tr>
<th style="width:100px;">조회기간</th> <th style="width:100px;">조회기간</th>
<td style="width:400px"> <td style="width:400px">
<input type="text" name="searchStartDate" value="${param.searchStartDate}" style="width:100px;"> <input type="text" name="searchStartDate" value="${param.searchStartDate}" style="width:80px;">
<input type="text" name="searchStartTime" value="${param.searchStartTime}" style="width:60px;"> <input type="text" name="searchStartTime" value="${param.searchStartTime}" style="width:40px;">
~ ~
<input type="text" name="searchEndDate" value="${param.searchEndDate}" style="width:100px;"> <input type="text" name="searchEndTime" value="${param.searchEndTime}" style="width:40px;">
<input type="text" name="searchEndTime" value="${param.searchEndTime}" style="width:60px;"> <button type="button" id="btnPrevTime" class="cssbtn small">&lt;</button>
<button type="button" id="btnNextTime" class="cssbtn small">&gt;</button>
<span style="color:#888; font-size:12px; margin-left:10px;">(최대 1시간, 초과시 자동 조정)</span> <span style="color:#888; font-size:12px; margin-left:10px;">(최대 1시간, 초과시 자동 조정)</span>
</td> </td>
<th style="width:100px;">API</th> <th style="width:100px;">API ID</th>
<td style="width:400px"> <td style="width:400px">
<input type="text" name="searchApiName" value="${param.searchApiName}"> <input type="text" name="searchApiName" value="${param.searchApiName}">
</td> </td>
@@ -421,12 +421,13 @@
mtype: 'POST', mtype: 'POST',
postData: gridPostData, postData: gridPostData,
colNames: [ colNames: [
'API명', 'API ID', 'API 명',
'총건수', '성공', 'Timeout', '시스템오류', '총건수', '성공', 'Timeout', '시스템오류',
'평균응답(ms)', '최소응답(ms)', '최대응답(ms)' '평균응답(ms)', '최소응답(ms)', '최대응답(ms)'
], ],
colModel: [ colModel: [
{ name: 'apiName', align: 'left', width: '250', sortable: false }, { name: 'apiName', align: 'left', width: '150', sortable: false },
{ name: 'apiDesc', align: 'left', width: '250', sortable: false },
{ name: 'totalCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false }, { name: 'totalCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false },
{ name: 'successCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false }, { name: 'successCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false },
{ name: 'timeoutCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false }, { name: 'timeoutCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false },
@@ -463,7 +464,7 @@
mtype: 'POST', mtype: 'POST',
postData: gridPostData, postData: gridPostData,
colNames: [ colNames: [
'통계시간', 'API', '인스턴스', '업무구분', '클라이언트ID', '통계시간', 'API ID', '인스턴스', '업무구분', '클라이언트ID',
'Inbound Adapter', 'Outbound Adapter', 'Inbound Adapter', 'Outbound Adapter',
'총건수', '성공', 'Timeout', '시스템오류', '총건수', '성공', 'Timeout', '시스템오류',
'평균응답(ms)', '최소응답(ms)', '최대응답(ms)' '평균응답(ms)', '최소응답(ms)', '최대응답(ms)'
@@ -510,11 +511,21 @@
$("input[name=searchStartDateTime]").inputmask("9999-99", { 'autoUnmask': true }); $("input[name=searchStartDateTime]").inputmask("9999-99", { 'autoUnmask': true });
$("input[name=searchEndDateTime]").inputmask("9999-99", { 'autoUnmask': true }); $("input[name=searchEndDateTime]").inputmask("9999-99", { 'autoUnmask': true });
// 기본값 설정 (최근 12월) // 기본값 설정: 올해 1월 ~ 지난달 (현재가 1월이면 작년 1월 ~ 작년 12월)
var today = new Date(); var today = new Date();
var endMonth = today.getFullYear() + '-' + (today.getMonth() + 1 < 10 ? '0' : '') + (today.getMonth() + 1); var startDate, endDate;
var startDate = new Date(today.getFullYear(), today.getMonth() - 11, 1); if (today.getMonth() === 0) { // 1월
var startMonth = startDate.getFullYear() + '-' + (startDate.getMonth() + 1 < 10 ? '0' : '') + (startDate.getMonth() + 1); startDate = new Date(today.getFullYear() - 1, 0, 1);
endDate = new Date(today.getFullYear() - 1, 11, 1);
} else {
startDate = new Date(today.getFullYear(), 0, 1);
endDate = new Date(today.getFullYear(), today.getMonth() - 1, 1);
}
function toMonthStr(d) {
return d.getFullYear() + '-' + (d.getMonth() + 1 < 10 ? '0' : '') + (d.getMonth() + 1);
}
var startMonth = toMonthStr(startDate);
var endMonth = toMonthStr(endDate);
if (!$("input[name=searchStartDateTime]").val()) { if (!$("input[name=searchStartDateTime]").val()) {
$("input[name=searchStartDateTime]").val(startMonth); $("input[name=searchStartDateTime]").val(startMonth);
@@ -593,7 +604,7 @@
</td> </td>
</tr> </tr>
<tr> <tr>
<th>API</th> <th>API ID</th>
<td> <td>
<input type="text" name="searchApiName" value="${param.searchApiName}"> <input type="text" name="searchApiName" value="${param.searchApiName}">
</td> </td>
@@ -632,10 +643,10 @@
</div> </div>
<!-- 요약 그리드 --> <!-- 요약 그리드 -->
<div style="margin-top: 20px;"> <div style="margin-top: 20px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;"> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<div class="title" style="margin: 0;">API별 요약 통계</div> <div class="title" style="margin: 0;">API별 요약 통계</div>
<button type="button" class="cssbtn" id="btn_excel_export_summary" level="R"> <button type="button" class="cssbtn" id="btn_excel_export_summary" level="R">
<i class="material-icons">file_download</i> Excel 다운로드 (요약) <i class="material-icons">file_download</i> Excel 다운로드 (요약)
</button> </button>
@@ -397,12 +397,13 @@
mtype: 'POST', mtype: 'POST',
postData: gridPostData, postData: gridPostData,
colNames: [ colNames: [
'API명', 'API ID', 'API 명',
'총건수', '성공', 'Timeout', '시스템오류', '총건수', '성공', 'Timeout', '시스템오류',
'평균응답(ms)', '최소응답(ms)', '최대응답(ms)' '평균응답(ms)', '최소응답(ms)', '최대응답(ms)'
], ],
colModel: [ colModel: [
{ name: 'apiName', align: 'left', width: '250', sortable: false }, { name: 'apiName', align: 'left', width: '150', sortable: false },
{ name: 'apiDesc', align: 'left', width: '250', sortable: false },
{ name: 'totalCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false }, { name: 'totalCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false },
{ name: 'successCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false }, { name: 'successCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false },
{ name: 'timeoutCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false }, { name: 'timeoutCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false },
@@ -439,7 +440,7 @@
mtype: 'POST', mtype: 'POST',
postData: gridPostData, postData: gridPostData,
colNames: [ colNames: [
'통계시간', 'API', '인스턴스', '업무구분', '클라이언트ID', '통계시간', 'API ID', '인스턴스', '업무구분', '클라이언트ID',
'Inbound Adapter', 'Outbound Adapter', 'Inbound Adapter', 'Outbound Adapter',
'총건수', '성공', 'Timeout', '시스템오류', '총건수', '성공', 'Timeout', '시스템오류',
'평균응답(ms)', '최소응답(ms)', '최대응답(ms)' '평균응답(ms)', '최소응답(ms)', '최대응답(ms)'
@@ -563,7 +564,7 @@
</td> </td>
</tr> </tr>
<tr> <tr>
<th>API</th> <th>API ID</th>
<td> <td>
<input type="text" name="searchApiName" value="${param.searchApiName}"> <input type="text" name="searchApiName" value="${param.searchApiName}">
</td> </td>
@@ -604,7 +605,7 @@
<!-- 요약 그리드 --> <!-- 요약 그리드 -->
<div style="margin-top: 20px;"> <div style="margin-top: 20px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;"> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<div class="title" style="margin: 0;">API별 요약 통계</div> <div class="title" style="margin: 0;">API별 요약 통계</div>
<button type="button" class="cssbtn" id="btn_excel_export_summary" level="R"> <button type="button" class="cssbtn" id="btn_excel_export_summary" level="R">
<i class="material-icons">file_download</i> Excel 다운로드 (요약) <i class="material-icons">file_download</i> Excel 다운로드 (요약)
</button> </button>
@@ -19,6 +19,14 @@
#gbox_grid .ui-jqgrid-ftable tr.footrow td { #gbox_grid .ui-jqgrid-ftable tr.footrow td {
height: 26px; height: 26px;
} }
.cssbtn.small {
height: 24px;
border-radius: 2px;
min-width: 24px;
box-shadow: none;
border-color: #ebebec;
font-size: 11px;
}
</style> </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"/>';
@@ -65,13 +73,53 @@
} }
// 조회구분(searchType)에 따라 '구분' / '구분명' 컬럼의 헤더명·폭·표시여부를 조정
function applyGridColumnsByType(type) {
var $g = $("#grid");
if (type === 'API') {
$g.jqGrid('setLabel', 'orgName', 'API ID');
$g.jqGrid('setLabel', 'apiDesc', 'API명');
$g.jqGrid('setColProp', 'orgName', { widthOrg: 150, width: 150 });
$g.jqGrid('setColProp', 'apiDesc', { widthOrg: 250, width: 250 });
$g.jqGrid('showCol', 'apiDesc');
} else {
$g.jqGrid('setLabel', 'orgName', (type === 'DATE') ? '사용일' : '제휴사');
$g.jqGrid('setColProp', 'orgName', { widthOrg: 250, width: 250 });
$g.jqGrid('hideCol', 'apiDesc');
}
$g.jqGrid('setGridWidth', $('#content_middle').width(), true);
}
function search() { function search() {
var postData = getPostData("cmd", "LIST"); var postData = getPostData("cmd", "LIST");
if (postData) { if (postData) {
applyGridColumnsByType($('input[name="searchType"]:checked').val());
$("#grid").setGridParam({ url: url, postData: postData }).trigger("reloadGrid"); $("#grid").setGridParam({ url: url, postData: postData }).trigger("reloadGrid");
} }
} }
// 조회기간을 시작일 기준 이전/다음 달의 1일~말일로 변경 후 조회 (direction: -1 이전달, 1 다음달)
function shiftMonthRange(direction) {
var v = $("input[name=searchStartDate]").val().replace(/-/g, "");
if (!v || v.length < 6) return;
var year = parseInt(v.substring(0, 4));
var month = parseInt(v.substring(4, 6)) - 1; // 0-based
var first = new Date(year, month + direction, 1);
var last = new Date(year, month + direction + 1, 0); // 해당 달의 말일
function pad(n) { return String(n).padStart(2, '0'); }
function fmt(d) {
return d.getFullYear() + '' + pad(d.getMonth() + 1) + pad(d.getDate());
}
$("input[name=searchStartDate]").val(fmt(first));
$("input[name=searchEndDate]").val(fmt(last));
search();
}
function exportToExcel() { function exportToExcel() {
var postData = getPostData("cmd", "EXCEL_EXPORT"); var postData = getPostData("cmd", "EXCEL_EXPORT");
@@ -186,12 +234,13 @@
mtype: 'POST', mtype: 'POST',
postData: gridPostData, postData: gridPostData,
colNames: [ colNames: [
'구분', '제휴사', 'API명',
'총건수', '성공', '성공율(%)', '실패율(%)', 'Timeout', '시스템오류', '총건수', '성공', '성공율(%)', '실패율(%)', 'Timeout', '시스템오류',
'평균응답(ms)', '최소응답(ms)', '최대응답(ms)' '평균응답(ms)', '최소응답(ms)', '최대응답(ms)'
], ],
colModel: [ colModel: [
{ name: 'orgName', align: 'left', width: '250', sortable: false }, { name: 'orgName', align: 'left', width: '250', sortable: false },
{ name: 'apiDesc', align: 'left', width: '250', sortable: false, hidden: true },
{ name: 'totalCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false }, { name: 'totalCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false },
{ name: 'successCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false }, { name: 'successCnt', align: 'right', width: '80', formatter: numberFormatter, sortable: false },
{ name: 'successRate', align: 'right', width: '80', formatter: decimalFormatter, sortable: false }, { name: 'successRate', align: 'right', width: '80', formatter: decimalFormatter, sortable: false },
@@ -225,10 +274,23 @@
$("input[name=searchStartDate], input[name=searchEndDate]").inputmask("9999-99-99", { 'autoUnmask': true }); $("input[name=searchStartDate], input[name=searchEndDate]").inputmask("9999-99-99", { 'autoUnmask': true });
$("input[name=searchStartDate], input[name=searchEndDate]").datepicker(); $("input[name=searchStartDate], input[name=searchEndDate]").datepicker();
// 기본값 설정: 이번달 1일 ~ 어제 (오늘이 1일이면 지난달 1일 ~ 지난달 말일)
var today = getToday(); var today = getToday();
var startDate = today.substring(0,6)+"01"; var now = new Date();
var endDate = today; function toYmd(d) {
var m = d.getMonth() + 1;
var day = d.getDate();
return d.getFullYear() + '' + (m < 10 ? '0' : '') + m + (day < 10 ? '0' : '') + day;
}
var startDate, endDate;
if (now.getDate() === 1) {
startDate = toYmd(new Date(now.getFullYear(), now.getMonth() - 1, 1));
endDate = toYmd(new Date(now.getFullYear(), now.getMonth(), 0));
} else {
startDate = today.substring(0,6) + "01";
endDate = toYmd(new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1));
}
if (!$("input[name=searchStartDate]").val()) { if (!$("input[name=searchStartDate]").val()) {
$("input[name=searchStartDate]").val(startDate); $("input[name=searchStartDate]").val(startDate);
@@ -246,6 +308,14 @@
search(); search();
}); });
$("#btnPrevMonth").click(function() {
shiftMonthRange(-1);
});
$("#btnNextMonth").click(function() {
shiftMonthRange(1);
});
$("input[name=searchType]").click(function() { $("input[name=searchType]").click(function() {
search(); search();
}); });
@@ -288,6 +358,8 @@
<input type="text" name="searchStartDate" id="searchStartDate" value="${param.searchStartDate}" style="width:100px;"> <input type="text" name="searchStartDate" id="searchStartDate" value="${param.searchStartDate}" style="width:100px;">
~ ~
<input type="text" name="searchEndDate" id="searchEndDate" value="${param.searchEndDate}" style="width:100px;"> <input type="text" name="searchEndDate" id="searchEndDate" value="${param.searchEndDate}" style="width:100px;">
<button type="button" id="btnPrevMonth" class="cssbtn small">&lt;</button>
<button type="button" id="btnNextMonth" class="cssbtn small">&gt;</button>
<span style="color:#888; font-size:12px; margin-left:10px;">(최대 31일)</span> <span style="color:#888; font-size:12px; margin-left:10px;">(최대 31일)</span>
</td> </td>
</tr> </tr>
@@ -302,11 +374,11 @@
<td> <td>
<input type="text" name="searchOrgName" id="searchOrgName" value="${param.searchOrgName}"> <input type="text" name="searchOrgName" id="searchOrgName" value="${param.searchOrgName}">
</td> </td>
<th style="width:120px;">API</th> <th style="width:120px;">API ID</th>
<td> <td>
<input type="text" name="searchApiName" id="searchApiName" value="${param.searchApiName}"> <input type="text" name="searchApiName" id="searchApiName" value="${param.searchApiName}">
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -55,10 +55,10 @@ $(document).ready(function() {
'200', '200',
'300', '300',
'400', '400',
'900', /* '900', */
'300', '300',
'400', '400',
'900', /* '900', */
'IF', 'IF',
'<%= localeMessage.getString("tranStat.recv") %>', '<%= localeMessage.getString("tranStat.recv") %>',
'<%= localeMessage.getString("common.total") %>', '<%= localeMessage.getString("common.total") %>',
@@ -75,11 +75,11 @@ $(document).ready(function() {
{ name : 'E200' , align : 'center' , width:'50' ,formatter:'integer',formatoptions:{thousandsSeparator:","},summaryType:'sum'}, { name : 'E200' , align : 'center' , width:'50' ,formatter:'integer',formatoptions:{thousandsSeparator:","},summaryType:'sum'},
{ name : 'E300' , align : 'center' , width:'50' ,formatter:'integer',formatoptions:{thousandsSeparator:","},summaryType:'sum'}, { name : 'E300' , align : 'center' , width:'50' ,formatter:'integer',formatoptions:{thousandsSeparator:","},summaryType:'sum'},
{ name : 'E400' , align : 'center' , width:'50' ,formatter:'integer',formatoptions:{thousandsSeparator:","},summaryType:'sum'}, { name : 'E400' , align : 'center' , width:'50' ,formatter:'integer',formatoptions:{thousandsSeparator:","},summaryType:'sum'},
{ name : 'E900' , align : 'center' , width:'50' ,formatter:'integer',formatoptions:{thousandsSeparator:","},summaryType:'sum'}, /* { name : 'E900' , align : 'center' , width:'50' ,formatter:'integer',formatoptions:{thousandsSeparator:","},summaryType:'sum'}, */
{ name : 'E9300' , align : 'center' , width:'50' ,formatter:'integer',formatoptions:{thousandsSeparator:","},summaryType:'sum'}, { name : 'E9300' , align : 'center' , width:'50' ,formatter:'integer',formatoptions:{thousandsSeparator:","},summaryType:'sum'},
{ name : 'E9400' , align : 'center' , width:'50' ,formatter:'integer',formatoptions:{thousandsSeparator:","},summaryType:'sum'}, { name : 'E9400' , align : 'center' , width:'50' ,formatter:'integer',formatoptions:{thousandsSeparator:","},summaryType:'sum'},
{ name : 'E9900' , align : 'center' , width:'50' ,formatter:'integer',formatoptions:{thousandsSeparator:","},summaryType:'sum'}, /* { name : 'E9900' , align : 'center' , width:'50' ,formatter:'integer',formatoptions:{thousandsSeparator:","},summaryType:'sum'}, */
{ name : 'AVGEAI' , align : 'center' , width:'46' ,formatter:'integer',formatoptions:{thousandsSeparator:","},summaryType:'avg'}, { name : 'AVGEAI' , align : 'center' , width:'46' ,formatter:'integer',formatoptions:{thousandsSeparator:","},summaryType:'avg'},
{ name : 'AVGPSV' , align : 'center' , width:'46' ,formatter:'integer',formatoptions:{thousandsSeparator:","},summaryType:'avg'}, { name : 'AVGPSV' , align : 'center' , width:'46' ,formatter:'integer',formatoptions:{thousandsSeparator:","},summaryType:'avg'},
@@ -152,8 +152,8 @@ $(document).ready(function() {
useColSpanStyle: true, useColSpanStyle: true,
groupHeaders:[ groupHeaders:[
{startColumnName: 'P100', numberOfColumns: 4, titleText: '<%= localeMessage.getString("tranStat.tranNo") %>'}, {startColumnName: 'P100', numberOfColumns: 4, titleText: '<%= localeMessage.getString("tranStat.tranNo") %>'},
{startColumnName: 'E100', numberOfColumns: 5, titleText: '<%= localeMessage.getString("tranStat.errNo") %>'}, {startColumnName: 'E100', numberOfColumns: 4, titleText: '<%= localeMessage.getString("tranStat.errNo") %>'},
{startColumnName: 'E9300', numberOfColumns: 3, titleText: '<%= localeMessage.getString("tranStat.timeout") %>'}, {startColumnName: 'E9300', numberOfColumns: 2, titleText: '<%= localeMessage.getString("tranStat.timeout") %>'},
{startColumnName: 'AVGEAI', numberOfColumns: 3, titleText: '<%= localeMessage.getString("tranStat.avgTime") %>'} {startColumnName: 'AVGEAI', numberOfColumns: 3, titleText: '<%= localeMessage.getString("tranStat.avgTime") %>'}
] ]
}); });
@@ -210,9 +210,9 @@ $(document).ready(function() {
var merge = new Array(); var merge = new Array();
merge.push(makeMerge("업무구분","0","1","0","0")); merge.push(makeMerge("업무구분","0","1","0","0"));
merge.push(makeMerge("구간별 처리건수","0","0","1","4")); merge.push(makeMerge("구간별 처리건수","0","0","1","4"));
merge.push(makeMerge("구간별 에러건수","0","0","5","9")); merge.push(makeMerge("구간별 에러건수","0","0","5","8"));
merge.push(makeMerge("타임아웃","0","0","10","12")); merge.push(makeMerge("타임아웃","0","0","9","11"));
merge.push(makeMerge("평균처리시간(초)","0","0","13","15")); merge.push(makeMerge("평균처리시간(초)","0","0","12","14"));
gridToExcelSubmit(url,"LIST_GRID_TO_EXCEL",$("#grid"),$("#ajaxForm"),"업무별 거래현황",merge); gridToExcelSubmit(url,"LIST_GRID_TO_EXCEL",$("#grid"),$("#ajaxForm"),"업무별 거래현황",merge);
return false; return false;
@@ -224,9 +224,9 @@ $(document).ready(function() {
var merge = new Array(); var merge = new Array();
merge.push(makeMerge("업무구분","0","1","0","0")); merge.push(makeMerge("업무구분","0","1","0","0"));
merge.push(makeMerge("구간별 처리건수","0","0","1","4")); merge.push(makeMerge("구간별 처리건수","0","0","1","4"));
merge.push(makeMerge("구간별 에러건수","0","0","5","9")); merge.push(makeMerge("구간별 에러건수","0","0","5","8"));
merge.push(makeMerge("타임아웃","0","0","10","12")); merge.push(makeMerge("타임아웃","0","0","9","11"));
merge.push(makeMerge("평균처리시간(초)","0","0","13","15")); merge.push(makeMerge("평균처리시간(초)","0","0","12","14"));
gridToExcelSubmit(url_excel,"LIST_GRID_TO_EXCEL",$("#grid"),$("#ajaxForm"),'업무별 거래현황',merge); gridToExcelSubmit(url_excel,"LIST_GRID_TO_EXCEL",$("#grid"),$("#ajaxForm"),'업무별 거래현황',merge);
return false; return false;
}); });
@@ -281,6 +281,9 @@ $(document).ready(function() {
<%-- <img id="btn_excel" src="<c:url value="/img/btn_excel.png"/>" level="W" status="DETAIL"/> --%> <%-- <img id="btn_excel" src="<c:url value="/img/btn_excel.png"/>" level="W" status="DETAIL"/> --%>
</div> </div>
<div class="title"><%= localeMessage.getString("tranStat.title") %><span class="tooltip"><%= localeMessage.getString("tranStat.tooltip") %></span></div> <div class="title"><%= localeMessage.getString("tranStat.title") %><span class="tooltip"><%= localeMessage.getString("tranStat.tooltip") %></span></div>
<div style="padding-bottom: 10px; color: #555">
※ 100 : 송신>APIM &nbsp; 200 : APIM>수신 &nbsp; 300 : 수신>APIM &nbsp; 400 : APIM>송신
</div>
<ajax> <ajax>
<table id="grid" ></table> <table id="grid" ></table>
</ajax> </ajax>
@@ -59,10 +59,10 @@ function w2GridToExcelSubmit(url,cmd,gridObj,formObject,fileName,merge){
records[i]['E200'] = data[i]['E200'].toString(); records[i]['E200'] = data[i]['E200'].toString();
records[i]['E300'] = data[i]['E300'].toString(); records[i]['E300'] = data[i]['E300'].toString();
records[i]['E400'] = data[i]['E400'].toString(); records[i]['E400'] = data[i]['E400'].toString();
records[i]['E900'] = data[i]['E900'].toString(); /* records[i]['E900'] = data[i]['E900'].toString(); */
records[i]['E9300'] = data[i]['E9300'].toString(); records[i]['E9300'] = data[i]['E9300'].toString();
records[i]['E9400'] = data[i]['E9400'].toString(); records[i]['E9400'] = data[i]['E9400'].toString();
records[i]['E9900'] = data[i]['E9900'].toString(); /* records[i]['E9900'] = data[i]['E9900'].toString(); */
records[i]['BIZNAME'] = data[i]['BIZNAME']; records[i]['BIZNAME'] = data[i]['BIZNAME'];
records[i]['SVCCODE'] = data[i]['SVCCODE']; records[i]['SVCCODE'] = data[i]['SVCCODE'];
@@ -245,8 +245,8 @@ $( document ).ready(function() {
{span : 4, caption:'<%= localeMessage.getString("tranStat.interface") %>'}, {span : 4, caption:'<%= localeMessage.getString("tranStat.interface") %>'},
{span : 4, caption:'<%= localeMessage.getString("tranStat.tranNo") %>'}, {span : 4, caption:'<%= localeMessage.getString("tranStat.tranNo") %>'},
{span : 3, caption:'<%= localeMessage.getString("tranStat.avgTime") %>'}, {span : 3, caption:'<%= localeMessage.getString("tranStat.avgTime") %>'},
{span : 5, caption:'<%= localeMessage.getString("tranStat.errNo") %>'}, {span : 4, caption:'<%= localeMessage.getString("tranStat.errNo") %>'},
{span : 3, caption:'<%= localeMessage.getString("tranStat.timeout") %>'} {span : 2, caption:'<%= localeMessage.getString("tranStat.timeout") %>'}
], ],
columns: [ columns: [
@@ -269,11 +269,11 @@ $( document ).ready(function() {
{ field : 'E200' ,type:'text' ,caption : '200' , size: '65px' ,style:'text-align:right', render:thousandsSeparator }, { field : 'E200' ,type:'text' ,caption : '200' , size: '65px' ,style:'text-align:right', render:thousandsSeparator },
{ field : 'E300' ,type:'text' ,caption : '300' , size: '65px' ,style:'text-align:right', render:thousandsSeparator }, { field : 'E300' ,type:'text' ,caption : '300' , size: '65px' ,style:'text-align:right', render:thousandsSeparator },
{ field : 'E400' ,type:'text' ,caption : '400' , size: '65px' ,style:'text-align:right', render:thousandsSeparator }, { field : 'E400' ,type:'text' ,caption : '400' , size: '65px' ,style:'text-align:right', render:thousandsSeparator },
{ field : 'E900' ,type:'text' ,caption : '900' , size: '65px' ,style:'text-align:right', render:thousandsSeparator }, /* { field : 'E900' ,type:'text' ,caption : '900' , size: '65px' ,style:'text-align:right', render:thousandsSeparator }, */
{ field : 'E9300' ,type:'text' ,caption : '300' , size: '50px' ,style:'text-align:right', render:thousandsSeparator }, { field : 'E9300' ,type:'text' ,caption : '300' , size: '50px' ,style:'text-align:right', render:thousandsSeparator },
{ field : 'E9400' ,type:'text' ,caption : '400' , size: '50px' ,style:'text-align:right', render:thousandsSeparator }, { field : 'E9400' ,type:'text' ,caption : '400' , size: '50px' ,style:'text-align:right', render:thousandsSeparator },
{ field : 'E9900' ,type:'text' ,caption : '900' , size: '50px' ,style:'text-align:right', render:thousandsSeparator } /* { field : 'E9900' ,type:'text' ,caption : '900' , size: '50px' ,style:'text-align:right', render:thousandsSeparator } */
] ]
,summary:[ ,summary:[
@@ -346,8 +346,8 @@ $( document ).ready(function() {
merge.push(makeMerge("API","0","0","0","3")); merge.push(makeMerge("API","0","0","0","3"));
merge.push(makeMerge("전문구간별 처리건수","0","0","4","7")); merge.push(makeMerge("전문구간별 처리건수","0","0","4","7"));
merge.push(makeMerge("평균처리시간(초)","0","0","8","10")); merge.push(makeMerge("평균처리시간(초)","0","0","8","10"));
merge.push(makeMerge("전문구간별 에러건수","0","0","11","15")); merge.push(makeMerge("전문구간별 에러건수","0","0","11","14"));
merge.push(makeMerge("타임아웃","0","0","16","18")); merge.push(makeMerge("타임아웃","0","0","15","16"));
w2GridToExcelSubmit(url_excel,"LIST_GRID_TO_EXCEL",w2ui['myGrid'],$("#ajaxForm"),"전문별 거래현황",merge); w2GridToExcelSubmit(url_excel,"LIST_GRID_TO_EXCEL",w2ui['myGrid'],$("#ajaxForm"),"전문별 거래현황",merge);
return false; return false;
@@ -376,8 +376,8 @@ $( document ).ready(function() {
merge.push(makeMerge("API","0","0","0","3")); merge.push(makeMerge("API","0","0","0","3"));
merge.push(makeMerge("구간별 처리건수","0","0","4","7")); merge.push(makeMerge("구간별 처리건수","0","0","4","7"));
merge.push(makeMerge("평균처리시간(초)","0","0","8","10")); merge.push(makeMerge("평균처리시간(초)","0","0","8","10"));
merge.push(makeMerge("구간별 에러건수","0","0","11","15")); merge.push(makeMerge("구간별 에러건수","0","0","11","14"));
merge.push(makeMerge("타임아웃","0","0","16","18")); merge.push(makeMerge("타임아웃","0","0","15","16"));
w2GridToExcelSubmit(url_excel,"LIST_GRID_TO_EXCEL",w2ui['myGrid'],$("#ajaxForm"),"전문별 거래현황",merge); w2GridToExcelSubmit(url_excel,"LIST_GRID_TO_EXCEL",w2ui['myGrid'],$("#ajaxForm"),"전문별 거래현황",merge);
return false; return false;
@@ -437,6 +437,9 @@ $( document ).ready(function() {
<%-- <img src="<c:url value="/img/btn_search.png"/>" alt="" id="btn_search" level="R" /> --%> <%-- <img src="<c:url value="/img/btn_search.png"/>" alt="" id="btn_search" level="R" /> --%>
</div> </div>
<div class="title"><%= localeMessage.getString("tranStatDetail.title") %><span class="tooltip"><%= localeMessage.getString("tranStat.tooltip") %></span></div> <div class="title"><%= localeMessage.getString("tranStatDetail.title") %><span class="tooltip"><%= localeMessage.getString("tranStat.tooltip") %></span></div>
<div style="padding-bottom: 10px; color: #555">
※ 100 : 송신>APIM &nbsp; 200 : APIM>수신 &nbsp; 300 : 수신>APIM &nbsp; 400 : APIM>송신
</div>
<table class="search_condition" cellspacing=0;> <table class="search_condition" cellspacing=0;>
<tbody> <tbody>
<tr> <tr>
@@ -407,7 +407,7 @@
$("#grid").tuiTableRowSpan("7"); $("#grid").tuiTableRowSpan("7");
//1000건이상 alert //1000건이상 alert
if (d.records == 1000) { if (d.records >= 1000) {
setTimeout(() => { setTimeout(() => {
alert("<%=localeMessage.getString("tracking.alert5")%>"); // 조회 결과 1,000건 초과 alert("<%=localeMessage.getString("tracking.alert5")%>"); // 조회 결과 1,000건 초과
}, 0); // Alert는 비동기적으로 처리 }, 0); // Alert는 비동기적으로 처리
@@ -23,6 +23,9 @@
white-space: normal; white-space: normal;
} }
.table_row td { padding: 0 5px; }
.table_row input { box-sizing: border-box; }
</style> </style>
<script language="javascript" > <script language="javascript" >
// var eaiBzwkDstcd = window.dialogArguments["eaiBzwkDstcd"]; // var eaiBzwkDstcd = window.dialogArguments["eaiBzwkDstcd"];
@@ -253,7 +256,7 @@
var name = $(this).attr("name"); var name = $(this).attr("name");
if ("${rmsMenuAuth}" =="W"){ //2020.07.08 admin 만 버튼보이게 수정 if ("${rmsMenuAuth}" =="W"){ //2020.07.08 admin 만 버튼보이게 수정
$("#"+name).show(); $("#"+name).show();
$("input[name="+name+"]").css("width","185px"); $("input[name="+name+"]").css("width","calc(100% - 60px)");
} }
}); });
@@ -261,7 +264,7 @@
var name = $(this).attr("name"); var name = $(this).attr("name");
if($("input[name="+name+"]").val().trim() != ""){ if($("input[name="+name+"]").val().trim() != ""){
$("#"+name).show(); $("#"+name).show();
$("input[name="+name+"]").css("width","185px"); $("input[name="+name+"]").css("width","calc(100% - 60px)");
} }
}); });
@@ -977,15 +980,23 @@ html,hbody {
<input type=hidden name=realHeader> <input type=hidden name=realHeader>
<div class="table_row_title"><%=localeMessage.getString("trackingDetail.common")%></div> <div class="table_row_title"><%=localeMessage.getString("trackingDetail.common")%></div>
<table class="table_row" cellspacing="0"> <table class="table_row" cellspacing="0">
<colgroup>
<col style="width:180px"/>
<col style="width:300px" />
<col style="width:180px"/>
<col style="width:300px" />
</colgroup>
<tr> <tr>
<th><%=localeMessage.getString("trackingDetail.eaiSvcSerno")%></th> <th><%=localeMessage.getString("trackingDetail.eaiSvcSerno")%></th>
<td colspan="3"><input type="text" name="eaiSvcSerno" readonly="readonly"/> </td> <td><input type="text" name="eaiSvcSerno" readonly="readonly"/> </td>
<th><%=localeMessage.getString("trackingDetail.rspnsErrcdName")%></th>
<td><input type="text" name="rspnsErrcdName" readonly="readonly"/> </td>
</tr> </tr>
<tr> <tr>
<th style="width:20%;"><%=localeMessage.getString("trackingDetail.eaiBzwkDstcd")%></th> <th><%=localeMessage.getString("trackingDetail.eaiBzwkDstcd")%></th>
<td style="width:30%;"><input type="text" name="eaiBzwkDstcd" readonly="readonly"/> </td> <td><input type="text" name="eaiBzwkDstcd" readonly="readonly"/> </td>
<th style="width:20%;"><%=localeMessage.getString("trackingDetail.msgDpstYMS")%></th> <th><%=localeMessage.getString("trackingDetail.msgDpstYMS")%></th>
<td style="width:30%;"><input type="text" name="msgDpstYMS" readonly="readonly"/> </td> <td><input type="text" name="msgDpstYMS" readonly="readonly"/> </td>
</tr> </tr>
<tr> <tr>
<th><%= localeMessage.getString("eaiMessage.eaiSvcName")%></th><td><input type="text" name="eaiSvcName" readonly="readonly" /> <th><%= localeMessage.getString("eaiMessage.eaiSvcName")%></th><td><input type="text" name="eaiSvcName" readonly="readonly" />
@@ -1019,11 +1030,17 @@ html,hbody {
</table> </table>
<div class="table_row_title">API 정보</div> <div class="table_row_title">API 정보</div>
<table class="table_row" cellspacing="0"> <table class="table_row" cellspacing="0">
<colgroup>
<col style="width:180px"/>
<col style="width:300px" />
<col style="width:180px"/>
<col style="width:300px" />
</colgroup>
<tr> <tr>
<th style="width:20%;">APP(Client) ID</th> <th>APP(Client) ID</th>
<td style="width:30%;"><input type="text" name="clientId" readonly="readonly"/> </td> <td><input type="text" name="clientId" readonly="readonly"/> </td>
<th style="width:20%;">APP 명</th> <th>APP 명</th>
<td style="width:30%;"><input type="text" name="clientName" readonly="readonly"/> </td> <td><input type="text" name="clientName" readonly="readonly"/> </td>
</tr> </tr>
<tr> <tr>
<th>법인 ID</th><td ><input type="text" name="orgId" readonly="readonly"/> </td> <th>법인 ID</th><td ><input type="text" name="orgId" readonly="readonly"/> </td>
@@ -1145,20 +1162,27 @@ html,hbody {
</div><!-- end.container-1 --> </div><!-- end.container-1 -->
<div class="table_row_title"><%=localeMessage.getString("trackingDetail.inbound")%></div> <div class="table_row_title"><%=localeMessage.getString("trackingDetail.inbound")%></div>
<table class="table_row" cellspacing="0" > <table class="table_row" cellspacing="0" >
<colgroup>
<col style="width:180px"/>
<col style="width:300px" />
<col style="width:180px"/>
<col style="width:300px" />
</colgroup>
<tr> <tr>
<th style="width:20%;"><%=localeMessage.getString("trackingDetail.svcMotivUseDstcd")%></th> <%-- <th style="width:20%;"><%=localeMessage.getString("trackingDetail.svcMotivUseDstcd")%></th>
<td style="width:30%;"><input type="text" name="svcMotivUseDstcd" readonly="readonly"/> </td> <td style="width:30%;"><input type="text" name="svcMotivUseDstcd" readonly="readonly"/> </td> --%>
<th style="width:20%;"><%=localeMessage.getString("trackingDetail.gstatSysAdptrBzwkGroupName")%></th> <th style="width:20%;"><%=localeMessage.getString("trackingDetail.gstatSysAdptrBzwkGroupName")%></th>
<td style="width:30%;"><input type="text" name="gstatSysAdptrBzwkGroupName" readonly="readonly"/> <td style="width:30%;"><input type="text" name="gstatSysAdptrBzwkGroupName" readonly="readonly" style="width: calc(100% - 60px);"/>
<%-- <img src="<c:url value="/images/bt/pop_detail.gif"/>" alt="" id="gstatSysAdptrBzwkGroupName" class="adapter_btn" level="W"/> --%> <%-- <img src="<c:url value="/images/bt/pop_detail.gif"/>" alt="" id="gstatSysAdptrBzwkGroupName" class="adapter_btn" level="W"/> --%>
<button type="button" class="cssbtn smallBtn2" id="gstatSysAdptrBzwkGroupName" style="vertical-align:middle; font-weight:bold;" level="W"><i class="material-icons">details</i><%= localeMessage.getString("button.detail") %></button> <button type="button" class="cssbtn smallBtn2" id="gstatSysAdptrBzwkGroupName" style="vertical-align:middle; font-weight:bold;" level="W"><i class="material-icons">details</i><%= localeMessage.getString("button.detail") %></button>
</td> </td>
</tr>
<tr>
<th><%=localeMessage.getString("trackingDetail.flowCtrlRoutName")%></th><td><input type="text" name="flowCtrlRoutName" readonly="readonly"/> </td>
<th><%=localeMessage.getString("trackingDetail.stndMsgUseYn")%></th><td><input type="text" name="stndMsgUseYn" readonly="readonly"/> </td> <th><%=localeMessage.getString("trackingDetail.stndMsgUseYn")%></th><td><input type="text" name="stndMsgUseYn" readonly="readonly"/> </td>
</tr> </tr>
<tr> <%-- <tr>
<th><%=localeMessage.getString("trackingDetail.flowCtrlRoutName")%></th><td><input type="text" name="flowCtrlRoutName" readonly="readonly"/> </td>
</tr> --%>
<%-- <tr>
<th><%=localeMessage.getString("trackingDetail.svcPrcssDsticName")%></th><td><input type="text" name="svcPrcssDsticName" readonly="readonly"/> </td> <th><%=localeMessage.getString("trackingDetail.svcPrcssDsticName")%></th><td><input type="text" name="svcPrcssDsticName" readonly="readonly"/> </td>
<th><%=localeMessage.getString("trackingDetail.svcBfClmnLogYn")%></th><td><input type="text" name="svcBfClmnLogYn" readonly="readonly"/> </td> <th><%=localeMessage.getString("trackingDetail.svcBfClmnLogYn")%></th><td><input type="text" name="svcBfClmnLogYn" readonly="readonly"/> </td>
</tr> </tr>
@@ -1169,39 +1193,52 @@ html,hbody {
<tr> <tr>
<th><%=localeMessage.getString("trackingDetail.gstatSvcDsticName")%></th><td><input type="text" name="gstatSvcDsticName" readonly="readonly"/> </td> <th><%=localeMessage.getString("trackingDetail.gstatSvcDsticName")%></th><td><input type="text" name="gstatSvcDsticName" readonly="readonly"/> </td>
<th><%=localeMessage.getString("trackingDetail.prsntMsgIdName")%></th><td><input type="text" name="prsntMsgIdName" readonly="readonly"/> </td> <th><%=localeMessage.getString("trackingDetail.prsntMsgIdName")%></th><td><input type="text" name="prsntMsgIdName" readonly="readonly"/> </td>
</tr> </tr> --%>
</table> </table>
<div class="table_row_title"><%=localeMessage.getString("trackingDetail.outbound")%></div> <div class="table_row_title"><%=localeMessage.getString("trackingDetail.outbound")%></div>
<table class="table_row" cellspacing="0" > <table class="table_row" cellspacing="0" >
<colgroup>
<col style="width:180px"/>
<col style="width:300px" />
<col style="width:180px"/>
<col style="width:300px" />
</colgroup>
<tr> <tr>
<th style="width:20%;"><%=localeMessage.getString("trackingDetail.psvIntfacDsticName")%></th> <%-- <th style="width:20%;"><%=localeMessage.getString("trackingDetail.psvIntfacDsticName")%></th>--%>
<td style="width:30%;"><input type="text" name="psvIntfacDsticName" readonly="readonly"/> </td> <%-- <td style="width:30%;"><input type="text" name="psvIntfacDsticName" readonly="readonly"/> </td> --%>
<th style="width:20%;"><%=localeMessage.getString("trackingDetail.psvSysAdptrBzwkGroupName")%></th> <th style="width:20%;"><%=localeMessage.getString("trackingDetail.psvSysAdptrBzwkGroupName")%></th>
<td style="width:30%;"><input type="text" name="psvSysAdptrBzwkGroupName" readonly="readonly"/> <td style="width:30%;"><input type="text" name="psvSysAdptrBzwkGroupName" readonly="readonly" style="width: calc(100% - 60px);"/>
<%-- <img src="<c:url value="/images/bt/pop_detail.gif"/>" alt="" id="psvSysAdptrBzwkGroupName" class="adapter_btn" level="W"/> --%> <%-- <img src="<c:url value="/images/bt/pop_detail.gif"/>" alt="" id="psvSysAdptrBzwkGroupName" class="adapter_btn" level="W"/> --%>
<button type="button" class="cssbtn smallBtn2" id="psvSysAdptrBzwkGroupName" style="vertical-align:middle; font-weight:bold;" level="W"><i class="material-icons">details</i><%= localeMessage.getString("button.detail") %></button> <button type="button" class="cssbtn smallBtn2" id="psvSysAdptrBzwkGroupName" style="vertical-align:middle; font-weight:bold;" level="W"><i class="material-icons">details</i><%= localeMessage.getString("button.detail") %></button>
</td> </td>
<th><%=localeMessage.getString("trackingDetail.psvBzwkSysName")%></th><td><input type="text" name="psvBzwkSysName" readonly="readonly"/> </td>
</tr> </tr>
<tr> <tr>
<th><%=localeMessage.getString("trackingDetail.outbndRoutName")%></th><td><input type="text" name="outbndRoutName" readonly="readonly"/> </td> <th><%=localeMessage.getString("trackingDetail.outbndRoutName")%></th><td><input type="text" name="outbndRoutName" readonly="readonly"/> </td>
<th><%=localeMessage.getString("trackingDetail.toutVal")%></th><td><input type="text" name="toutVal" readonly="readonly"/> </td> <th><%=localeMessage.getString("trackingDetail.toutVal")%></th><td><input type="text" name="toutVal" readonly="readonly"/> </td>
</tr> </tr>
<tr> <%-- <tr>
<th><%=localeMessage.getString("trackingDetail.psvSysSvcDsticName")%></th><td><input type="text" name="psvSysSvcDsticName" readonly="readonly"/> </td> <th><%=localeMessage.getString("trackingDetail.psvSysSvcDsticName")%></th><td><input type="text" name="psvSysSvcDsticName" readonly="readonly"/> </td>
<th><%=localeMessage.getString("trackingDetail.psvBzwkSysName")%></th><td><input type="text" name="psvBzwkSysName" readonly="readonly"/> </td>
</tr> </tr>--%>
<tr> <%-- <tr>
<th><%=localeMessage.getString("trackingDetail.rspnsErrFldName")%></th><td><input type="text" name="rspnsErrFldName" readonly="readonly"/> </td> <th><%=localeMessage.getString("trackingDetail.rspnsErrFldName")%></th><td><input type="text" name="rspnsErrFldName" readonly="readonly"/> </td>
<th><%=localeMessage.getString("trackingDetail.psvSysIdName")%></th><td><input type="text" name="psvSysIdName" readonly="readonly"/> </td> <th><%=localeMessage.getString("trackingDetail.psvSysIdName")%></th><td><input type="text" name="psvSysIdName" readonly="readonly"/> </td>
</tr> </tr> --%>
</table> </table>
<div class="table_row_title"><%=localeMessage.getString("trackingDetail.MAPPING")%></div> <div class="table_row_title"><%=localeMessage.getString("trackingDetail.MAPPING")%></div>
<table class="table_row" cellspacing="0" > <table class="table_row" cellspacing="0" >
<colgroup>
<col style="width:180px"/>
<col style="width:300px" />
<col style="width:180px"/>
<col style="width:300px" />
</colgroup>
<tr> <tr>
<th style="width:20%;"><%=localeMessage.getString("trackingDetail.chngYn")%></th> <th><%=localeMessage.getString("trackingDetail.chngYn")%></th>
<td style="width:30%;"><input type="text" name="chngYn" readonly="readonly"/> </td> <td><input type="text" name="chngYn" readonly="readonly"/> </td>
<th style="width:20%;"><%=localeMessage.getString("trackingDetail.chngMsgIdName")%></th> <th><%=localeMessage.getString("trackingDetail.chngMsgIdName")%></th>
<td style="width:30%;"><input type="text" name="chngMsgIdName" readonly="readonly"/> <td><input type="text" name="chngMsgIdName" readonly="readonly" style="width: calc(100% - 60px);"/>
<!-- img src="<c:url value="/images/bt/pop_detail.gif"/>" alt="" id="chngMsgIdName" class="layout_btn" /--> <!-- img src="<c:url value="/images/bt/pop_detail.gif"/>" alt="" id="chngMsgIdName" class="layout_btn" /-->
<button type="button" class="cssbtn smallBtn2" id="chngMsgIdName" style="vertical-align:middle; font-weight:bold;" level="W"><i class="material-icons">details</i><%= localeMessage.getString("button.detail") %></button> <button type="button" class="cssbtn smallBtn2" id="chngMsgIdName" style="vertical-align:middle; font-weight:bold;" level="W"><i class="material-icons">details</i><%= localeMessage.getString("button.detail") %></button>
</td> </td>
@@ -1210,7 +1247,7 @@ html,hbody {
<th><%=localeMessage.getString("trackingDetail.bascRspnsChngYn")%></th><td><input type="text" name="bascRspnsChngYn" readonly="readonly"/> </td> <th><%=localeMessage.getString("trackingDetail.bascRspnsChngYn")%></th><td><input type="text" name="bascRspnsChngYn" readonly="readonly"/> </td>
<th><%=localeMessage.getString("trackingDetail.bascRspnsChngMsgIdName")%></th> <th><%=localeMessage.getString("trackingDetail.bascRspnsChngMsgIdName")%></th>
<td> <td>
<input type="text" name="bascRspnsChngMsgIdName" readonly="readonly"/> <input type="text" name="bascRspnsChngMsgIdName" readonly="readonly" style="width: calc(100% - 60px);"/>
<!-- img src="<c:url value="/images/bt/pop_detail.gif"/>" alt="" id="bascRspnsChngMsgIdName" class="layout_btn" /--> <!-- img src="<c:url value="/images/bt/pop_detail.gif"/>" alt="" id="bascRspnsChngMsgIdName" class="layout_btn" /-->
<button type="button" class="cssbtn smallBtn2" id="bascRspnsChngMsgIdName" style="vertical-align:middle; font-weight:bold;" level="W"><i class="material-icons">details</i><%= localeMessage.getString("button.detail") %></button> <button type="button" class="cssbtn smallBtn2" id="bascRspnsChngMsgIdName" style="vertical-align:middle; font-weight:bold;" level="W"><i class="material-icons">details</i><%= localeMessage.getString("button.detail") %></button>
</td> </td>
@@ -1220,10 +1257,10 @@ html,hbody {
<th><%=localeMessage.getString("trackingDetail.errRspnsChngYn")%></th><td><input type="text" name="errRspnsChngYn" readonly="readonly"/> </td> <th><%=localeMessage.getString("trackingDetail.errRspnsChngYn")%></th><td><input type="text" name="errRspnsChngYn" readonly="readonly"/> </td>
<th><%=localeMessage.getString("trackingDetail.errRspnsChngMsgIdName")%></th> <th><%=localeMessage.getString("trackingDetail.errRspnsChngMsgIdName")%></th>
<td> <td>
<input type="text" name="errRspnsChngMsgIdName" readonly="readonly"/> <input type="text" name="errRspnsChngMsgIdName" readonly="readonly" style="width: calc(100% - 60px);"/>
<img src="<c:url value="/images/bt/pop_detail.gif"/>" alt="" id="errRspnsChngMsgIdName" class="layout_btn" /> </td> <img src="<c:url value="/images/bt/pop_detail.gif"/>" alt="" id="errRspnsChngMsgIdName" class="layout_btn" /> </td>
</tr> </tr>
<tr> <%-- <tr>
<th><%=localeMessage.getString("trackingDetail.inptMsgIDName")%></th> <th><%=localeMessage.getString("trackingDetail.inptMsgIDName")%></th>
<td> <td>
<input type="text" name="inptMsgIDName" readonly="readonly" /> <input type="text" name="inptMsgIDName" readonly="readonly" />
@@ -1233,9 +1270,9 @@ html,hbody {
<tr> <tr>
<th><%=localeMessage.getString("trackingDetail.bascRspnsMsgCmprCtnt")%></th><td><input type="text" name="bascRspnsMsgCmprCtnt" readonly="readonly"/> </td> <th><%=localeMessage.getString("trackingDetail.bascRspnsMsgCmprCtnt")%></th><td><input type="text" name="bascRspnsMsgCmprCtnt" readonly="readonly"/> </td>
<th><%=localeMessage.getString("trackingDetail.flovrYn")%></th><td><input type="text" name="flovrYn" readonly="readonly"/> </td> <th><%=localeMessage.getString("trackingDetail.flovrYn")%></th><td><input type="text" name="flovrYn" readonly="readonly"/> </td>
</tr> </tr> --%>
</table> </table>
<div class="table_row_title"><%=localeMessage.getString("trackingDetail.etc")%></div> <%-- <div class="table_row_title"><%=localeMessage.getString("trackingDetail.etc")%></div>
<table class="table_row" cellspacing="0" > <table class="table_row" cellspacing="0" >
<tr> <tr>
<th style="width:20%;"><%=localeMessage.getString("trackingDetail.rspnsErrcdName")%></th> <th style="width:20%;"><%=localeMessage.getString("trackingDetail.rspnsErrcdName")%></th>
@@ -1267,7 +1304,7 @@ html,hbody {
<th><%=localeMessage.getString("trackingDetail.trackAsisKey3Ctnt")%></th><td><input type="text" name="trackAsisKey3Ctnt" readonly="readonly"/> </td> <th><%=localeMessage.getString("trackingDetail.trackAsisKey3Ctnt")%></th><td><input type="text" name="trackAsisKey3Ctnt" readonly="readonly"/> </td>
<th><%=localeMessage.getString("trackingDetail.trackAsisKey4Ctnt")%></th><td><input type="text" name="trackAsisKey4Ctnt" readonly="readonly"/> </td> <th><%=localeMessage.getString("trackingDetail.trackAsisKey4Ctnt")%></th><td><input type="text" name="trackAsisKey4Ctnt" readonly="readonly"/> </td>
</tr> </tr>
</table> </table> --%>
</form> </form>
</div><!-- end.popup_box --> </div><!-- end.popup_box -->
<div id="bizLogObjectDialog" style="white-space: pre-wrap; overflow-y:auto" title="Biz Log Object Detail"> <div id="bizLogObjectDialog" style="white-space: pre-wrap; overflow-y:auto" title="Biz Log Object Detail">
@@ -273,6 +273,9 @@ public interface MonitoringContext {
// 비밀번호 변경 시 재사용을 금지할 최근 이력 개수 // 비밀번호 변경 시 재사용을 금지할 최근 이력 개수
public static final String RMS_PASSWORD_HISTORY_COUNT = "rms.password.history.count"; public static final String RMS_PASSWORD_HISTORY_COUNT = "rms.password.history.count";
// 비밀번호 변경 시 SMS 2차 인증 사용 여부 (djb.sms_auth.enabled 와 함께 true 여야 동작)
public static final String RMS_PASSWORD_SMS_AUTH_ENABLED = "rms.password.sms_auth.enabled";
// API 상태변화 스윙챗 발송여부 // API 상태변화 스윙챗 발송여부
public static final String DJB_UMS_MESSENGER_APIMONITOR_ENABLED = "djb.ums.messenger.api-monitor.enabled"; public static final String DJB_UMS_MESSENGER_APIMONITOR_ENABLED = "djb.ums.messenger.api-monitor.enabled";
@@ -464,6 +464,11 @@ public class MainController implements InterceptorSkipController {
MonitoringContext.RMS_PASSWORD_HISTORY_COUNT, DEFAULT_PASSWORD_HISTORY_COUNT); MonitoringContext.RMS_PASSWORD_HISTORY_COUNT, DEFAULT_PASSWORD_HISTORY_COUNT);
} }
// 비밀번호 변경 시 SMS 2차 인증 사용 여부 (djb.sms_auth.enabled 와 rms.password.sms_auth.enabled 가 모두 true 여야 동작)
private boolean isPasswordChangeSmsAuthEnabled() {
return monitoringContext.getBooleanProperty(MonitoringContext.RMS_PASSWORD_SMS_AUTH_ENABLED, false);
}
// 로그인 실패 횟수 증가, 임계치 도달 시 계정 잠금 // 로그인 실패 횟수 증가, 임계치 도달 시 계정 잠금
private int increaseLoginFailCount(UserInfo userInfo) { private int increaseLoginFailCount(UserInfo userInfo) {
int failCount = (userInfo.getLoginfailcount() == null ? 0 : userInfo.getLoginfailcount()) + 1; int failCount = (userInfo.getLoginfailcount() == null ? 0 : userInfo.getLoginfailcount()) + 1;
@@ -909,8 +914,9 @@ public class MainController implements InterceptorSkipController {
} }
// 5. SMS 2차 인증 필요 여부 체크 (비상모드는 로그인과 동일하게 우회) // 5. SMS 2차 인증 필요 여부 체크 (비상모드는 로그인과 동일하게 우회)
// djb.sms_auth.enabled 와 rms.password.sms_auth.enabled 가 모두 true 일 때만 SMS 인증 수행
Boolean emergencyMode = (Boolean) session.getAttribute("emergencyMode"); Boolean emergencyMode = (Boolean) session.getAttribute("emergencyMode");
if (!Boolean.TRUE.equals(emergencyMode) && smsAuthService.isEnabled()) { if (!Boolean.TRUE.equals(emergencyMode) && smsAuthService.isEnabled() && isPasswordChangeSmsAuthEnabled()) {
if (!smsAuthService.hasValidPhoneNumber(userInfo)) { if (!smsAuthService.hasValidPhoneNumber(userInfo)) {
UserAccessLogger.log(request, LOG_CATEGORY_LOGIN, "F", "휴대폰번호 미등록으로 비밀번호 변경 불가"); UserAccessLogger.log(request, LOG_CATEGORY_LOGIN, "F", "휴대폰번호 미등록으로 비밀번호 변경 불가");
return ChangePasswordResponseDto.builder() return ChangePasswordResponseDto.builder()
@@ -31,6 +31,12 @@ public class LayoutSyncHistoryService extends AbstractDataService<LayoutSyncHist
if (StringUtils.isNotBlank(search.getSearchLoutName())) { if (StringUtils.isNotBlank(search.getSearchLoutName())) {
query.where(q.loutname.containsIgnoreCase(search.getSearchLoutName())); query.where(q.loutname.containsIgnoreCase(search.getSearchLoutName()));
} }
if (StringUtils.isNotBlank(search.getSearchServiceName())) {
query.where(q.servicename.containsIgnoreCase(search.getSearchServiceName()));
}
if (StringUtils.isNotBlank(search.getSearchCommand())) {
query.where(q.command.containsIgnoreCase(search.getSearchCommand()));
}
if (StringUtils.isNotBlank(search.getSearchStartDate())) { if (StringUtils.isNotBlank(search.getSearchStartDate())) {
query.where(q.recvamndhms.goe(parseYYYYMMDD(search.getSearchStartDate()).atStartOfDay())); query.where(q.recvamndhms.goe(parseYYYYMMDD(search.getSearchStartDate()).atStartOfDay()));
} }
@@ -31,16 +31,16 @@ public class InflowTokenService {
private UmsManager ums; private UmsManager ums;
public void checkRecentFails(long rangeMinute) { public void checkRecentFails(long rangeMinute) {
List<InflowTokenInsufficient> rows = inflowTokenRepository.countTokenInsufficient(rangeMinute); if (monitoringContext.getBooleanProperty(MonitoringContext.DJB_UMS_MESSENGER_APIMONITOR_ENABLED, true)) {
List<InflowTokenInsufficient> rows = inflowTokenRepository.countTokenInsufficient(rangeMinute);
for (InflowTokenInsufficient info : rows) {
log.debug("유량제어 토큰 획득 실패: {}-{} 최근 {}분 동안 {}건", info.getEaisvcname(), info.getEaisvcdesc(), rangeMinute, info.getCnt()); for (InflowTokenInsufficient info : rows) {
String message = "유량제어 토큰 획득 실패가 발생하였습니다 \n" + info.getEaisvcname(); log.debug("유량제어 토큰 획득 실패: {}-{} 최근 {}분 동안 {}건", info.getEaisvcname(), info.getEaisvcdesc(), rangeMinute, info.getCnt());
String message = "유량제어 토큰 획득 실패가 발생하였습니다 \n" + info.getEaisvcname();
if (monitoringContext.getBooleanProperty(MonitoringContext.DJB_UMS_MESSENGER_APIMONITOR_ENABLED, true)) {
HashMap<String,Object> params = new HashMap<String,Object>(); HashMap<String,Object> params = new HashMap<String,Object>();
params.put("message", message); params.put("message", message);
ums.send("api-monitor", MessageCode.INFLOW_TOKEN_FAILED, params); ums.sendMessenger("api-monitor", MessageCode.INFLOW_TOKEN_FAILED, params);
} }
} }
} }
@@ -126,9 +126,9 @@ public class ApiStatsHourlyAggregationJob implements Job {
" , API_NAME, GW_INSTANCE_ID, BIZ_DIV_CODE, NVL(A.CLIENT_ID, 'NONE')" + " , API_NAME, GW_INSTANCE_ID, BIZ_DIV_CODE, NVL(A.CLIENT_ID, 'NONE')" +
" , INBOUND_ADAPTER, OUTBOUND_ADAPTER" + " , INBOUND_ADAPTER, OUTBOUND_ADAPTER" +
" , COUNT(EAISVCSERNO)" + " , COUNT(EAISVCSERNO)" +
" , SUM(CASE WHEN (A.ERROR_CODE IS NULL OR C.CODE IS NOT NULL) AND E400 IS NOT NULL THEN 1 ELSE 0 END)" + " , SUM(CASE WHEN A.ERROR_CODE IS NULL OR C.CODE IS NOT NULL THEN 1 ELSE 0 END)" +
" , SUM(CASE WHEN B.CODE IS NOT NULL THEN 1 ELSE 0 END)" + " , SUM(CASE WHEN B.CODE IS NOT NULL THEN 1 ELSE 0 END)" +
" , SUM(CASE WHEN (A.E400 IS NULL OR A.ERROR_CODE IS NOT NULL) AND B.CODE IS NULL AND C.CODE IS NULL THEN 1 ELSE 0 END)" + " , SUM(CASE WHEN A.ERROR_CODE IS NOT NULL AND B.CODE IS NULL AND C.CODE IS NULL THEN 1 ELSE 0 END)" +
" , 0, 0, 0, 0" + " , 0, 0, 0, 0" +
" , TRUNC(AVG(RESP_TIME)), MIN(RESP_TIME), MAX(RESP_TIME), 0, 0" + " , TRUNC(AVG(RESP_TIME)), MIN(RESP_TIME), MAX(RESP_TIME), 0, 0" +
" FROM (" + " FROM (" +
@@ -143,8 +143,7 @@ public class ApiStatsHourlyAggregationJob implements Job {
" + TO_NUMBER(SUBSTR(MAX(MSGPRCSSYMS),15,3))" + " + TO_NUMBER(SUBSTR(MAX(MSGPRCSSYMS),15,3))" +
" - TO_NUMBER(SUBSTR(MIN(MSGPRCSSYMS),15,3))" + " - TO_NUMBER(SUBSTR(MIN(MSGPRCSSYMS),15,3))" +
" ) AS RESP_TIME" + " ) AS RESP_TIME" +
" , MAX(CASE WHEN LOGPRCSSSERNO = '400' THEN LOGPRCSSSERNO ELSE '' END) AS E400" + " , MAX(CASE WHEN LOGPRCSSSERNO = '400' THEN EAIERRCD ELSE '' END) AS ERROR_CODE" +
" , MAX(EAIERRCD) AS ERROR_CODE" +
" FROM " + logTableName + " A " + " FROM " + logTableName + " A " +
" WHERE MSGDPSTYMS >= :rangeStart AND MSGDPSTYMS < :rangeEnd" + " WHERE MSGDPSTYMS >= :rangeStart AND MSGDPSTYMS < :rangeEnd" +
" GROUP BY EAISVCSERNO" + " GROUP BY EAISVCSERNO" +
@@ -1,9 +1,7 @@
package com.eactive.eai.rms.ext.djb.job; package com.eactive.eai.rms.ext.djb.job;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@@ -13,7 +11,6 @@ import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext; import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException; import org.quartz.JobExecutionException;
import org.quartz.SchedulerException; import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
@@ -37,7 +34,7 @@ import com.eactive.eai.rms.onl.common.util.DateUtil;
* <p>Job 파라미터 (JobDataMap):</p> * <p>Job 파라미터 (JobDataMap):</p>
* <ul> * <ul>
* <li> * <li>
* NONE * <b>api.inflow.fail.range_minute</b>: 유량제어 토큰 획득 실패 조회 구간 (단위: 분, 기본값: 5)
* </li> * </li>
* </ul> * </ul>
* *
@@ -48,6 +45,12 @@ public class InflowTokenMonitorJob implements Job {
private static final Logger log = LoggerFactory.getLogger(InflowTokenMonitorJob.class); private static final Logger log = LoggerFactory.getLogger(InflowTokenMonitorJob.class);
/** Job 파라미터 키: 유량제어 실패 조회 구간(분) */
public static final String KEY_API_INFLOW_FAIL_RANGE_MINUTE = "api.inflow.fail.range_minute";
/** 유량제어 실패 조회 구간 기본값(분) */
public static final String DEFAULT_RANGE_MINUTE = "5";
private transient MonitoringContext monitoringContext; private transient MonitoringContext monitoringContext;
@Override @Override
@@ -69,18 +72,19 @@ public class InflowTokenMonitorJob implements Job {
log.info("*** START InflowTokenFailMonitorJob run({})", CommonUtil.getToday("yyyy-MM-dd HH:mm")); log.info("*** START InflowTokenFailMonitorJob run({})", CommonUtil.getToday("yyyy-MM-dd HH:mm"));
long execMinute = 1; //default 배치 실행주기(분) // Job 프로퍼티에서 유량제어 실패 조회 구간(분)을 읽는다. 미지정 시 기본값 사용
long execMinute;
Trigger trigger = context.getTrigger(); String rangeMinute = jobDataMap.getString(KEY_API_INFLOW_FAIL_RANGE_MINUTE);
Date prevFireTime = trigger.getPreviousFireTime(); if (StringUtils.isEmpty(rangeMinute)) {
Date nextFireTime = trigger.getNextFireTime(); rangeMinute = DEFAULT_RANGE_MINUTE;
}
//1회 이상 배치가 실행된 경우, 배치 간격을 구하여 유량제어조회 구간 파라미터로 전달한다 try {
if (prevFireTime != null && nextFireTime != null) { execMinute = Long.parseLong(rangeMinute.trim());
long diffMillis = nextFireTime.getTime() - prevFireTime.getTime(); } catch (NumberFormatException e) {
execMinute = TimeUnit.MILLISECONDS.toMinutes(diffMillis); log.warn("잘못된 {} 값: {} - 기본값 {} 사용", KEY_API_INFLOW_FAIL_RANGE_MINUTE, rangeMinute, DEFAULT_RANGE_MINUTE);
log.info("실행 주기: {}분", execMinute); execMinute = Long.parseLong(DEFAULT_RANGE_MINUTE);
} }
log.info("유량제어 실패 조회 구간: {}분", execMinute);
ApplicationContext appContext; ApplicationContext appContext;
try { try {
@@ -57,19 +57,19 @@ public class ApiUseStatsService
String dataSql = ""; String dataSql = "";
if ("ORG".equals(search.getSearchType())) { if ("ORG".equals(search.getSearchType())) {
dataSql = "SELECT NVL(NVL(B.ORGNAME, D.ORG_NAME),'미분류') AS ORGNAME"; dataSql = "SELECT NVL(NVL(B.ORGNAME, D.ORG_NAME),'미분류') AS ORGNAME, '' AS APIDESC";
dataSql += getQueryBody(search); dataSql += getQueryBody(search);
dataSql += " GROUP BY NVL(NVL(B.ORGNAME, D.ORG_NAME),'미분류')"; dataSql += " GROUP BY NVL(NVL(B.ORGNAME, D.ORG_NAME),'미분류')";
dataSql += " ORDER BY ORGNAME"; dataSql += " ORDER BY ORGNAME";
} }
else if ("API".equals(search.getSearchType())) { else if ("API".equals(search.getSearchType())) {
dataSql = "SELECT A.API_NAME "; dataSql = "SELECT A.API_NAME, A.EAISVCDESC ";
dataSql += getQueryBody(search); dataSql += getQueryBody(search);
dataSql += " GROUP BY A.API_NAME"; dataSql += " GROUP BY A.API_NAME, A.EAISVCDESC ";
dataSql += " ORDER BY API_NAME"; dataSql += " ORDER BY API_NAME";
} }
else if ("DATE".equals(search.getSearchType())) { else if ("DATE".equals(search.getSearchType())) {
dataSql = "SELECT TO_CHAR(STAT_TIME,'YYYY-MM-DD') AS STAT_TIME "; dataSql = "SELECT TO_CHAR(STAT_TIME,'YYYY-MM-DD') AS STAT_TIME, '' AS APIDESC ";
dataSql += getQueryBody(search); dataSql += getQueryBody(search);
dataSql += " GROUP BY TO_CHAR(STAT_TIME,'YYYY-MM-DD')"; dataSql += " GROUP BY TO_CHAR(STAT_TIME,'YYYY-MM-DD')";
dataSql += " ORDER BY STAT_TIME"; dataSql += " ORDER BY STAT_TIME";
@@ -85,7 +85,7 @@ public class ApiUseStatsService
StringBuilder where = buildNativeWhere(search); StringBuilder where = buildNativeWhere(search);
String totalSql; String totalSql;
totalSql = "SELECT '합계' AS ORGNAME "; totalSql = "SELECT '합계' AS ORGNAME, '' AS APIDESC ";
totalSql += getQueryBody(search); totalSql += getQueryBody(search);
Query totalQuery = entityManager.createNativeQuery(totalSql); Query totalQuery = entityManager.createNativeQuery(totalSql);
@@ -109,6 +109,7 @@ public class ApiUseStatsService
+ " FROM (" + " FROM ("
+ " SELECT A.STAT_TIME " + " SELECT A.STAT_TIME "
+ " , A.API_NAME" + " , A.API_NAME"
+ " , C.EAISVCDESC"
+ " , A.CLIENT_ID " + " , A.CLIENT_ID "
+ " , A.TOTAL_CNT" + " , A.TOTAL_CNT"
+ " , A.SUCCESS_CNT" + " , A.SUCCESS_CNT"
@@ -118,13 +119,13 @@ public class ApiUseStatsService
+ " , A.MIN_RESP_TIME" + " , A.MIN_RESP_TIME"
+ " , A.MAX_RESP_TIME" + " , A.MAX_RESP_TIME"
+ " , CASE WHEN API_NAME LIKE '%1' THEN SUBSTR(OUTBOUND_ADAPTER,2,3) ELSE SUBSTR(INBOUND_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 LEFT OUTER JOIN TSEAIHE01 C ON A.API_NAME = C.EAISVCNAME "
+ " WHERE A.STAT_TIME >= :searchStartDate " + " WHERE A.STAT_TIME >= :searchStartDate "
+ " AND A.STAT_TIME <= :searchEndDate "; + " AND A.STAT_TIME <= :searchEndDate ";
if (StringUtils.isNotBlank(search.getSearchApiName())) { if (StringUtils.isNotBlank(search.getSearchApiName())) {
dataSql += " AND A.API_NAME LIKE :searchApiName "; dataSql += " AND UPPER(A.API_NAME) LIKE :searchApiName ";
} }
dataSql += " ) A" dataSql += " ) A"
@@ -160,15 +161,16 @@ public class ApiUseStatsService
private ApiStatsUI toVO(Object[] row) { private ApiStatsUI toVO(Object[] row) {
ApiStatsUI vo = new ApiStatsUI(); ApiStatsUI vo = new ApiStatsUI();
vo.setOrgName(StringUtils.toString(row[0])); vo.setOrgName(StringUtils.toString(row[0]));
vo.setTotalCnt(StringUtils.toLong(row[1])); vo.setApiDesc(StringUtils.toString(row[1]));
vo.setSuccessCnt(StringUtils.toLong(row[2])); vo.setTotalCnt(StringUtils.toLong(row[2]));
vo.setSuccessRate(StringUtils.toDecimal(row[3]).setScale(2, RoundingMode.HALF_UP)); vo.setSuccessCnt(StringUtils.toLong(row[3]));
vo.setFailRate(StringUtils.toDecimal(row[4]).setScale(2, RoundingMode.HALF_UP)); vo.setSuccessRate(StringUtils.toDecimal(row[4]).setScale(2, RoundingMode.HALF_UP));
vo.setTimeoutCnt(StringUtils.toLong(row[5])); vo.setFailRate(StringUtils.toDecimal(row[5]).setScale(2, RoundingMode.HALF_UP));
vo.setSystemErrCnt(StringUtils.toLong(row[6])); vo.setTimeoutCnt(StringUtils.toLong(row[6]));
vo.setAvgRespTime(StringUtils.toDecimal(row[7])); vo.setSystemErrCnt(StringUtils.toLong(row[7]));
vo.setMinRespTime(StringUtils.toDecimal(row[8])); vo.setAvgRespTime(StringUtils.toDecimal(row[8]));
vo.setMaxRespTime(StringUtils.toDecimal(row[9])); vo.setMinRespTime(StringUtils.toDecimal(row[9]));
vo.setMaxRespTime(StringUtils.toDecimal(row[10]));
return vo; return vo;
} }
@@ -182,7 +182,7 @@ public class UmsService {
// 스윙챗 알림 데이터 // 스윙챗 알림 데이터
SwingDataVO swiDataVO = new SwingDataVO(); SwingDataVO swiDataVO = new SwingDataVO();
swiDataVO.setNotiCode("CNO_FEP_APIM_0002"); //0001:긴급, 0002:중요 swiDataVO.setNotiCode("CNO_FEP_API_0002"); //0001:긴급, 0002:중요
swiDataVO.setNotiTitle("알림"); swiDataVO.setNotiTitle("알림");
swiDataVO.setNotiContent(messageRequest.getMessage()); swiDataVO.setNotiContent(messageRequest.getMessage());
@@ -17,7 +17,6 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException; import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
@@ -146,8 +145,12 @@ public class WebhookService {
/** /**
* 웹훅 발송 메인 메서드 * 웹훅 발송 메인 메서드
*
* <p>{@code @Transactional}을 두지 않는다 - 재시도(최대 retryCount회, Thread.sleep 포함)가
* 이 메서드 안에서 동기로 도는데, 메서드 전체를 하나의 트랜잭션으로 감싸면 재시도 도중의
* 실패 로그 저장(sendWithRetry 내부)이 커밋되지 않고 메서드가 끝날 때까지 대기하게 되어
* "실패 시 즉시 로그부터 남긴다"는 의도가 무의미해진다. save() 호출마다 개별 커밋되도록 둔다.</p>
*/ */
@Transactional
public void send(WebhookSendRequest req) { public void send(WebhookSendRequest req) {
String targetUrl = req.getTargetUrl(); String targetUrl = req.getTargetUrl();
@@ -235,29 +238,50 @@ public class WebhookService {
* 재시도 포함 HTTP 발송 * 재시도 포함 HTTP 발송
* - 4xx: 클라이언트 오류이므로 즉시 throw (재시도 불필요) * - 4xx: 클라이언트 오류이므로 즉시 throw (재시도 불필요)
* - 5xx / 네트워크 오류: 최대 RETRY_COUNT회까지 선형 증가 backoff 재시도 (1s → 2s → 3s → 4s → 5s) * - 5xx / 네트워크 오류: 최대 RETRY_COUNT회까지 선형 증가 backoff 재시도 (1s → 2s → 3s → 4s → 5s)
* - 실패할 때마다 즉시 로그를 저장한다 (최초 실패는 INSERT, 이후 재시도 실패는 같은 row를 UPDATE) -
* 재시도 도중 앱이 죽어도 마지막으로 저장된 실패 기록이 남도록 하기 위함
*/ */
private ResponseEntity<String> sendWithRetry(String proxyUrl, HttpEntity<String> request, WebhookSendLog sendLog, int retryCount, int retryTime) throws Exception { private ResponseEntity<String> sendWithRetry(String proxyUrl, HttpEntity<String> request, WebhookSendLog sendLog, int retryCount, int retryTime) throws Exception {
Exception lastException = null; Exception lastException = null;
for (int attempt = 0; attempt <= retryCount; attempt++) { for (int attempt = 0; attempt <= retryCount; attempt++) {
try { try {
if (attempt == 0) {
// 배포 반영 확인용: HttpComponentsClientHttpRequestFactory 여야 SSL 우회 RestTemplate 사용 중.
// SimpleClientHttpRequestFactory 로 찍히면 기본 new RestTemplate() 이 주입된 것 → PKIX 원인.
log.info("[Webhook] POST 시도 - url: {}, requestFactory: {}", proxyUrl,
restTemplate.getRequestFactory().getClass().getSimpleName());
}
if (attempt > 0) { if (attempt > 0) {
long delayMs = (long) retryTime * attempt; long delayMs = (long) retryTime * attempt;
log.info("[Webhook] 재시도 {}/{} - url: {}, delay: {}ms", attempt, retryCount, proxyUrl, delayMs); log.info("[Webhook] 재시도 {}/{} - url: {}, delay: {}ms", attempt, retryCount, proxyUrl, delayMs);
sendLog.setRetryCount(attempt);
Thread.sleep(delayMs); Thread.sleep(delayMs);
} }
return restTemplate.postForEntity(proxyUrl, request, String.class); return restTemplate.postForEntity(proxyUrl, request, String.class);
} catch (HttpClientErrorException e) { } catch (HttpClientErrorException e) {
//throw e; // 4xx는 재시도 불필요 //throw e; // 4xx는 재시도 불필요
lastException = e; //TODO: 재시도 테스트 lastException = e; //TODO: 재시도 테스트
recordAttemptFailure(sendLog, attempt, e.getStatusCode().value(), e.getResponseBodyAsString(), e.getMessage());
} catch (Exception e) { } catch (Exception e) {
lastException = e; lastException = e;
log.warn("[Webhook] 발송 실패 (attempt {}/{}) - url: {}, error: {}", attempt, retryCount, proxyUrl, e.getMessage()); log.warn("[Webhook] 발송 실패 (attempt {}/{}) - url: {}, error: {}", attempt, retryCount, proxyUrl, e.getMessage());
recordAttemptFailure(sendLog, attempt, null, null, e.getMessage());
} }
} }
throw lastException; throw lastException;
} }
// 실패한 시도 하나를 즉시 저장한다. sendLog에 id가 없으면(최초 실패) INSERT, 있으면(재시도 실패) 같은 row를 UPDATE한다.
private void recordAttemptFailure(WebhookSendLog sendLog, int attempt, Integer statusCode, String responseBody, String errorMessage) {
sendLog.setRetryCount(attempt);
sendLog.setSuccess(false);
sendLog.setErrorMessage(errorMessage);
if (statusCode != null) {
sendLog.setStatusCode(statusCode);
sendLog.setResponseBody(responseBody);
}
sendLogRepository.save(sendLog);
}
/** /**
* HMAC-SHA256 서명 생성 * HMAC-SHA256 서명 생성
* Java 17 미만은 HexFormat 미지원이므로 직접 변환 * Java 17 미만은 HexFormat 미지원이므로 직접 변환
@@ -1,38 +1,62 @@
package com.eactive.eai.rms.ext.user.sync; package com.eactive.eai.rms.ext.user.sync;
import org.apache.http.client.HttpClient; import org.apache.http.client.HttpClient;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustAllStrategy;
import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.ssl.SSLContextBuilder;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import javax.net.ssl.SSLContext;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Configuration @Configuration
public class RestTemplateConfiguration { public class RestTemplateConfiguration {
@Bean @Bean
public HttpClient httpClient() { public HttpClient httpClient() throws Exception {
HttpClient httpClient = // 사내망 내부 CA(자체 발급) 인증서 체인을 JVM cacerts가 신뢰하지 못해 PKIX 오류가 나는 문제의
// 임시 우회. 인증서 체인/호스트명 검증을 모두 끄므로 MITM에 취약해진다 - 내부망 대상으로만 쓰고,
// 가능하면 해당 CA를 JVM 트러스트스토어에 등록하는 방식으로 대체할 것.
SSLContext sslContext = SSLContextBuilder.create()
.loadTrustMaterial(TrustAllStrategy.INSTANCE)
.build();
SSLConnectionSocketFactory sslSocketFactory =
new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
HttpClient httpClient =
HttpClientBuilder.create() HttpClientBuilder.create()
.setSSLSocketFactory(sslSocketFactory)
.setMaxConnTotal(50) .setMaxConnTotal(50)
.setMaxConnPerRoute(20) .setMaxConnPerRoute(20)
.build(); .build();
// 배포 반영 확인용: 이 줄이 기동 로그에 없으면 수정 전 클래스가 떠 있는 것.
log.info("[RestTemplateConfiguration] httpClient 생성: SSL 인증서 체인/호스트명 검증 비활성화(TrustAllStrategy+NoopHostnameVerifier) 적용");
return httpClient; return httpClient;
} }
@Bean @Bean
public HttpComponentsClientHttpRequestFactory factory(HttpClient httpClient) { public HttpComponentsClientHttpRequestFactory factory(HttpClient httpClient) {
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(); HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setHttpClient(httpClient); factory.setHttpClient(httpClient);
return factory; return factory;
} }
@Bean @Bean
public RestTemplate restTemplate(HttpComponentsClientHttpRequestFactory factory) { public RestTemplate restTemplate(HttpComponentsClientHttpRequestFactory factory) {
RestTemplate restTemplate = new RestTemplate(); RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(factory); restTemplate.setRequestFactory(factory);
log.info("[RestTemplateConfiguration] RestTemplate 빈 생성: requestFactory={}", factory.getClass().getSimpleName());
return restTemplate; return restTemplate;
} }
@@ -1,10 +1,10 @@
package com.eactive.eai.rms.onl.loader.controller; package com.eactive.eai.rms.onl.loader.controller;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -1451,77 +1451,92 @@ public class LoaderController implements InterceptorSkipController {
*/ */
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@RequestMapping( params ={"cmd=interface","control=downloadfull"}) @RequestMapping( params ={"cmd=interface","control=downloadfull"})
public ModelAndView interfaceDownLoadFully(String serviceType, String interfaceId) throws Exception { public ModelAndView interfaceDownLoadFully(String serviceType, String interfaceId) {
DataSourceType dt = DataSourceTypeManager.getDataSourceType(serviceType); DataSourceType dt = DataSourceTypeManager.getDataSourceType(serviceType);
DataSourceContextHolder.setDataSourceType(dt); DataSourceContextHolder.setDataSourceType(dt);
// 1. interface 데이터 다운로드 String json = "";
InterfaceDeployPack interfaceDeployPack = new InterfaceDeployPack(); try {
// 1. interface 데이터 다운로드
InterfaceDeployPack interfaceDeployPack = new InterfaceDeployPack();
EAIMessageDeploy eaiMessageDeploy = interfaceDeployService.selectDeploy(interfaceId); EAIMessageDeploy eaiMessageDeploy = interfaceDeployService.selectDeploy(interfaceId);
eaiMessageDeploy.setEailastamndyms(null); eaiMessageDeploy.setEailastamndyms(null);
interfaceDeployPack.setEaiMessageDeploy(eaiMessageDeploy); interfaceDeployPack.setEaiMessageDeploy(eaiMessageDeploy);
for(StandardMessageInfoDeploy standardMessageInfoDeploy : eaiMessageDeploy.getStandardMessageInfoDeploys()){ for(StandardMessageInfoDeploy standardMessageInfoDeploy : eaiMessageDeploy.getStandardMessageInfoDeploys()){
standardMessageInfoDeploy.setEailastamndyms(null); standardMessageInfoDeploy.setEailastamndyms(null);
} }
for (ServiceMessageDeploy serviceMessageDeploy : eaiMessageDeploy.getServiceMessages()){ for (ServiceMessageDeploy serviceMessageDeploy : eaiMessageDeploy.getServiceMessages()){
String[] transformNames = {serviceMessageDeploy.getChngmsgidname(), serviceMessageDeploy.getBascrspnschngmsgidname()}; String[] transformNames = {serviceMessageDeploy.getChngmsgidname(), serviceMessageDeploy.getBascrspnschngmsgidname()};
// 2. Layout 데이터 생성 // 2. Layout 데이터 생성
for(String transformName: transformNames){ for(String transformName: transformNames){
if(StringUtils.isBlank(transformName)){ if(StringUtils.isBlank(transformName)){
continue;
}
for(TransformSourceResultUI transformSourceResultUI : transform2Service.selectTransformSourceResult(transformName)){
String loutName = transformSourceResultUI.getLoutName();
if(StringUtils.isBlank(loutName)){
continue; continue;
} }
LayoutDeploy layoutDeploy = layoutDeployService.selectDeploy(loutName);
layoutDeploy.setLastamndhms(null); for(TransformSourceResultUI transformSourceResultUI : transform2Service.selectTransformSourceResult(transformName)){
interfaceDeployPack.getLayoutDeployList().add(layoutDeploy); String loutName = transformSourceResultUI.getLoutName();
if(StringUtils.isBlank(loutName)){
continue;
}
LayoutDeploy layoutDeploy = layoutDeployService.selectDeploy(loutName);
layoutDeploy.setLastamndhms(null);
interfaceDeployPack.getLayoutDeployList().add(layoutDeploy);
}
// 3. Transform 데이터 생성
TransformDeploy transformDeploy = transformDeployService.selectDeploy(transformName);
transformDeploy.setLastamndhms(null);
interfaceDeployPack.getTransformDeployList().add(transformDeploy);
} }
// 3. Transform 데이터 생성
TransformDeploy transformDeploy = transformDeployService.selectDeploy(transformName);
transformDeploy.setLastamndhms(null);
interfaceDeployPack.getTransformDeployList().add(transformDeploy);
} }
// 4. API 스펙 정보(ptl_api_spec_info) 추가 (감사(audit) 컬럼은 제외)
Optional<ApiSpecInfo> optSpecInfo = apiSpecInfoService.findById(interfaceId);
if (optSpecInfo.isPresent()) {
ApiSpecInfoUI specInfo = apiSpecUIMapper.mapToUI(optSpecInfo.get());
specInfo.setCreatedBy(null);
specInfo.setCreatedDate(null);
specInfo.setLastModifiedBy(null);
specInfo.setLastModifiedDate(null);
interfaceDeployPack.setSpecInfo(specInfo);
}
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
ObjectWriter writer = objectMapper.writerWithDefaultPrettyPrinter();
String bizCode = eaiMessageDeploy.getEaibzwkdstcd();
String ioMapPath = monitoringContext.getStringProperty(MonitoringContext.IOMAP_DOWNLOAD_PATH);
String fileDir = FilenameUtils.concat(ioMapPath, bizCode);
String fileFullPath = FilenameUtils.concat(fileDir, interfaceId+".json");
FileWriteUtil.makeDirWithGroupPermission(fileDir);
File file = new File(fileFullPath);
json = writer.writeValueAsString(interfaceDeployPack);
FileUtils.writeStringToFile(file, json, StandardCharsets.UTF_8);
// TSEAITR10 배포 이력 저장 (SERVICENAME=eCams, COMMAND=downloadfull, LOUTNAME=interfaceId, RECVDATA=json)
loaderService.insertDeploySyncLog(dt, "downloadfull", interfaceId, json);
Map<String,String> result = new HashMap<>();
result.put("status", "success");
result.put("message", fileFullPath+" write done.");
return new ModelAndView(resultView, "result", result);
} catch (Exception e) {
logger.error("interface downloadfull error - interfaceId=" + interfaceId, e);
loaderService.insertDeployFailLog(dt, "downloadfull", interfaceId, json, e.toString());
Map<String,String> result = new HashMap<>();
result.put("status", "fail");
result.put("message", e.toString());
return new ModelAndView(resultView, "result", result);
} }
// 4. API 스펙 정보(ptl_api_spec_info) 추가 (감사(audit) 컬럼은 제외)
Optional<ApiSpecInfo> optSpecInfo = apiSpecInfoService.findById(interfaceId);
if (optSpecInfo.isPresent()) {
ApiSpecInfoUI specInfo = apiSpecUIMapper.mapToUI(optSpecInfo.get());
specInfo.setCreatedBy(null);
specInfo.setCreatedDate(null);
specInfo.setLastModifiedBy(null);
specInfo.setLastModifiedDate(null);
interfaceDeployPack.setSpecInfo(specInfo);
}
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
ObjectWriter writer = objectMapper.writerWithDefaultPrettyPrinter();
String bizCode = eaiMessageDeploy.getEaibzwkdstcd();
String ioMapPath = monitoringContext.getStringProperty(MonitoringContext.IOMAP_DOWNLOAD_PATH);
String fileDir = FilenameUtils.concat(ioMapPath, bizCode);
String fileFullPath = FilenameUtils.concat(fileDir, interfaceId+".json");
FileWriteUtil.makeDirWithGroupPermission(fileDir);
File file = new File(fileFullPath);
writer.writeValue(file, interfaceDeployPack);
Map<String,String> result = new HashMap<>();
String status = "success";
result.put("status", status);
result.put("message", fileFullPath+" write done.");
return new ModelAndView(resultView, "result", result);
} }
/** /**
@@ -1533,57 +1548,73 @@ public class LoaderController implements InterceptorSkipController {
* @throws Exception * @throws Exception
*/ */
@RequestMapping( params ={"cmd=interface","control=uploadfull"}) @RequestMapping( params ={"cmd=interface","control=uploadfull"})
public ModelAndView interfaceLoadFully(String serviceType, String filePath) throws Exception { public ModelAndView interfaceLoadFully(String serviceType, String filePath) {
DataSourceType dt = DataSourceTypeManager.getDataSourceType(serviceType); DataSourceType dt = DataSourceTypeManager.getDataSourceType(serviceType);
DataSourceContextHolder.setDataSourceType(dt); DataSourceContextHolder.setDataSourceType(dt);
String ioMapPath = monitoringContext.getStringProperty(MonitoringContext.IOMAP_UPLOAD_PATH); String json = "";
String fileFullPath ; String loutName = filePath;
if(StringUtils.isBlank(ioMapPath)) { try {
fileFullPath = filePath; String ioMapPath = monitoringContext.getStringProperty(MonitoringContext.IOMAP_UPLOAD_PATH);
} else { String fileFullPath ;
fileFullPath = FilenameUtils.concat(ioMapPath, filePath); if(StringUtils.isBlank(ioMapPath)) {
} fileFullPath = filePath;
File file = new File(fileFullPath);
InputStream inputStream = new FileInputStream(file);
// ObjectMapper 생성 및 설정
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
InterfaceDeployPack interfaceDeployPack = objectMapper.readValue(inputStream, InterfaceDeployPack.class);
EAIMessageDeploy eaiMessageDeploy = interfaceDeployPack.getEaiMessageDeploy();
interfaceDeployService.saveDeploy(eaiMessageDeploy);
agentUtilService.broadcast(new CommonCommand("com.ext.eai.agent.stdmessage.ReloadSTDMessageCommand", "ALL"));
agentUtilService.broadcast(new CommonCommand("com.eactive.eai.agent.eaimessage.ReloadEAIMessageCommand", eaiMessageDeploy.getEaisvcname()));
for(LayoutDeploy layoutDeploy: interfaceDeployPack.getLayoutDeployList()){
layoutDeployService.saveDeploy(layoutDeploy);
agentUtilService.broadcast(new CommonCommand( "com.eactive.eai.agent.transformer.ReloadLayoutCommand", layoutDeploy.getLoutname()));
}
for(TransformDeploy transformDeploy : interfaceDeployPack.getTransformDeployList()){
transformDeployService.saveDeploy(transformDeploy);
agentUtilService.broadcast(new CommonCommand( "com.eactive.eai.agent.transformer.ReloadTransformCommand", transformDeploy.getCnvsnname()));
}
ApiSpecInfoUI specInfoUI = interfaceDeployPack.getSpecInfo();
if (specInfoUI != null && StringUtils.isNotBlank(specInfoUI.getApiId())) {
ApiSpecInfo specInfo = apiSpecUIMapper.mapToEntity(specInfoUI);
if (apiSpecInfoService.findById(specInfo.getApiId()).isPresent()) {
apiSpecInfoService.updateById(specInfo.getApiId(), specInfo);
} else { } else {
apiSpecInfoService.create(specInfo); fileFullPath = FilenameUtils.concat(ioMapPath, filePath);
} }
File file = new File(fileFullPath);
json = FileUtils.readFileToString(file, StandardCharsets.UTF_8);
// ObjectMapper 생성 및 설정
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
InterfaceDeployPack interfaceDeployPack = objectMapper.readValue(json, InterfaceDeployPack.class);
EAIMessageDeploy eaiMessageDeploy = interfaceDeployPack.getEaiMessageDeploy();
loutName = eaiMessageDeploy.getEaisvcname();
interfaceDeployService.saveDeploy(eaiMessageDeploy);
agentUtilService.broadcast(new CommonCommand("com.ext.eai.agent.stdmessage.ReloadSTDMessageCommand", "ALL"));
agentUtilService.broadcast(new CommonCommand("com.eactive.eai.agent.eaimessage.ReloadEAIMessageCommand", eaiMessageDeploy.getEaisvcname()));
for(LayoutDeploy layoutDeploy: interfaceDeployPack.getLayoutDeployList()){
layoutDeployService.saveDeploy(layoutDeploy);
agentUtilService.broadcast(new CommonCommand( "com.eactive.eai.agent.transformer.ReloadLayoutCommand", layoutDeploy.getLoutname()));
}
for(TransformDeploy transformDeploy : interfaceDeployPack.getTransformDeployList()){
transformDeployService.saveDeploy(transformDeploy);
agentUtilService.broadcast(new CommonCommand( "com.eactive.eai.agent.transformer.ReloadTransformCommand", transformDeploy.getCnvsnname()));
}
ApiSpecInfoUI specInfoUI = interfaceDeployPack.getSpecInfo();
if (specInfoUI != null && StringUtils.isNotBlank(specInfoUI.getApiId())) {
ApiSpecInfo specInfo = apiSpecUIMapper.mapToEntity(specInfoUI);
if (apiSpecInfoService.findById(specInfo.getApiId()).isPresent()) {
apiSpecInfoService.updateById(specInfo.getApiId(), specInfo);
} else {
apiSpecInfoService.create(specInfo);
}
}
// TSEAITR10 배포 이력 저장 (SERVICENAME=eCams, COMMAND=uploadfull, LOUTNAME=eaisvcname, RECVDATA=json)
loaderService.insertDeploySyncLog(dt, "uploadfull", loutName, json);
Map<String,String> result = new HashMap<String,String>();
result.put("status", "success");
result.put("message", fileFullPath+" deploy done.");
return new ModelAndView(resultView, "result", result);
} catch (Exception e) {
logger.error("interface uploadfull error - filePath=" + filePath, e);
loaderService.insertDeployFailLog(dt, "uploadfull", loutName, json, e.toString());
Map<String,String> result = new HashMap<String,String>();
result.put("status", "fail");
result.put("message", e.toString());
return new ModelAndView(resultView, "result", result);
} }
Map<String,String> result = new HashMap<String,String>();
String status = "success";
result.put("status", status);
result.put("message", fileFullPath+" deploy done.");
return new ModelAndView(resultView, "result", result);
} }
} }
@@ -41,4 +41,9 @@ public interface LoaderDao {
public List<LinkedHashMap> selectTableDataForListValue( DataSourceType dataSourceType, String tableName, String key1, List<String> param1 ) ; public List<LinkedHashMap> selectTableDataForListValue( DataSourceType dataSourceType, String tableName, String key1, List<String> param1 ) ;
public List<LinkedHashMap> selectSnaAdpApList( DataSourceType dataSourceType, String param1 ) ; public List<LinkedHashMap> selectSnaAdpApList( DataSourceType dataSourceType, String param1 ) ;
/**
* TSEAITR10(배포/동기화 이력)에 로그 1건을 저장한다.
*/
public int insertSyncLog( DataSourceType dt, HashMap<String, String> param ) ;
} }
@@ -189,10 +189,15 @@ public class LoaderDaoImpl extends SqlMapClientTemplateDao implements LoaderDao
// } // }
// }.execute(); // }.execute();
} }
public int insertSyncLog( DataSourceType dt, HashMap<String, String> param ) {
param.put("schemaId", dt.getSchema());
return this.template.update("Layout.insertLayoutSyncLog", param);
}
public List<LinkedHashMap> selectSnaAdpApList( DataSourceType dataSourceType, String param1 ) { public List<LinkedHashMap> selectSnaAdpApList( DataSourceType dataSourceType, String param1 ) {
final HashMap param = new HashMap(); final HashMap param = new HashMap();
param.put("param1", param1); param.put("param1", param1);
return template.queryForList("Loader.selectSnaAdpApList", param); return template.queryForList("Loader.selectSnaAdpApList", param);
// return new SpecifiedDataSourceExecutor<List<LinkedHashMap>>( // return new SpecifiedDataSourceExecutor<List<LinkedHashMap>>(
@@ -157,5 +157,15 @@ public interface TransactionLoaderService{
public HashMap syncResult(Command command ,String fileName ) throws Exception; public HashMap syncResult(Command command ,String fileName ) throws Exception;
public void appendToListfile(String bizCode, String interfaceId) throws Exception ; public void appendToListfile(String bizCode, String interfaceId) throws Exception ;
/**
* downloadfull / uploadfull 성공 시 TSEAITR10 에 배포 이력을 저장한다. (PRCSSRSLT=S)
*/
public void insertDeploySyncLog(DataSourceType dt, String command, String interfaceId, String recvData) ;
/**
* downloadfull / uploadfull 실패 시 TSEAITR10 에 실패 이력을 저장한다. (PRCSSRSLT=F, PRCSSRSLTCMNT=errMsg)
*/
public void insertDeployFailLog(DataSourceType dt, String command, String interfaceId, String recvData, String errMsg) ;
} }
@@ -2,6 +2,7 @@ package com.eactive.eai.rms.onl.loader.service;
import com.eactive.eai.agent.command.Command; import com.eactive.eai.agent.command.Command;
import com.eactive.eai.agent.command.CommonCommand; import com.eactive.eai.agent.command.CommonCommand;
import com.eactive.eai.common.util.UUIDGenerator;
import com.eactive.eai.rms.common.base.BaseService; import com.eactive.eai.rms.common.base.BaseService;
import com.eactive.eai.rms.common.context.MonitoringContext; import com.eactive.eai.rms.common.context.MonitoringContext;
import com.eactive.eai.rms.common.converter.ElinkObjectMapper; import com.eactive.eai.rms.common.converter.ElinkObjectMapper;
@@ -1483,4 +1484,33 @@ public class TransactionLoaderServiceImpl extends BaseService implements Transac
String targetLine = bizCode+"|"+interfaceId+System.lineSeparator(); String targetLine = bizCode+"|"+interfaceId+System.lineSeparator();
org.apache.commons.io.FileUtils.writeStringToFile(listFile, targetLine, Charset.defaultCharset(), true); org.apache.commons.io.FileUtils.writeStringToFile(listFile, targetLine, Charset.defaultCharset(), true);
} }
@Override
public void insertDeploySyncLog(DataSourceType dt, String command, String interfaceId, String recvData) {
insertDeployLog(dt, command, interfaceId, recvData, "S", "");
}
@Override
public void insertDeployFailLog(DataSourceType dt, String command, String interfaceId, String recvData, String errMsg) {
insertDeployLog(dt, command, interfaceId, recvData, "F", errMsg);
}
private void insertDeployLog(DataSourceType dt, String command, String interfaceId, String recvData,
String prcssRslt, String prcssRsltCmnt) {
try {
HashMap<String, String> param = new HashMap<String, String>();
param.put("logPrcssSeqno", UUIDGenerator.getUUID());
param.put("serviceName", "InterfaceDeploy");
param.put("command", command);
param.put("loutName", interfaceId);
param.put("recvData", recvData == null ? "" : recvData);
param.put("recvAmndHMS", DateUtil.getDateTime("yyyyMMddHHmmssSS").substring(0, 16));
param.put("prcssRslt", prcssRslt);
param.put("prcssRsltCmnt", StringUtils.left(prcssRsltCmnt, 4000)); // PRCSSRSLTCMNT 길이 보호
dao.insertSyncLog(dt, param);
} catch (Exception e) {
// 이력 저장 실패가 download/upload 본기능을 중단시키지 않도록 한다.
logger.error("insertDeployLog error (command=" + command + ", interfaceId=" + interfaceId + ")", e);
}
}
} }
@@ -7,6 +7,12 @@ public class LayoutSyncHistoryUISearch {
private String searchLoutName; private String searchLoutName;
/* 서비스명 (부분일치) */
private String searchServiceName;
/* 커맨드 (부분일치) */
private String searchCommand;
/* 연동시간 기간 검색 (yyyyMMdd, 8자리) */ /* 연동시간 기간 검색 (yyyyMMdd, 8자리) */
private String searchStartDate; private String searchStartDate;
private String searchEndDate; private String searchEndDate;
@@ -4,6 +4,8 @@ import java.io.IOException;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@@ -18,7 +20,10 @@ import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import com.eactive.eai.rms.common.datasource.DataSourceContextHolder;
import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
import com.eactive.eai.rms.common.vo.GridResponse; import com.eactive.eai.rms.common.vo.GridResponse;
import com.eactive.eai.rms.data.entity.onl.apim.obp.ObpGwMetricJpaDAO;
import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsDay; import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsDay;
import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsDayService; import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsDayService;
import com.eactive.ext.kjb.statistics.mapping.ApiStatsUIMapper; import com.eactive.ext.kjb.statistics.mapping.ApiStatsUIMapper;
@@ -38,6 +43,7 @@ public class ApiStatsDayController {
private final ApiStatsDayService service; private final ApiStatsDayService service;
private final ApiStatsUIMapper mapper; private final ApiStatsUIMapper mapper;
private final ApiStatsExcelExportService excelExportService; private final ApiStatsExcelExportService excelExportService;
private final ObpGwMetricJpaDAO apiNameDao;
@GetMapping(value = "/onl/kjb/statistics/apiStatsDayMan.view") @GetMapping(value = "/onl/kjb/statistics/apiStatsDayMan.view")
public String view() { public String view() {
@@ -66,9 +72,38 @@ public class ApiStatsDayController {
@PostMapping(value = "/onl/kjb/statistics/apiStatsDayMan.json", params = "cmd=LIST_SUMMARY") @PostMapping(value = "/onl/kjb/statistics/apiStatsDayMan.json", params = "cmd=LIST_SUMMARY")
public ResponseEntity<GridResponse<ApiStatsUI>> selectSummaryList(ApiStatsSearch search, Pageable pageable) { public ResponseEntity<GridResponse<ApiStatsUI>> selectSummaryList(ApiStatsSearch search, Pageable pageable) {
Page<ApiStatsUI> page = service.selectSummaryList(search, pageable); Page<ApiStatsUI> page = service.selectSummaryList(search, pageable);
fillApiDesc(page.getContent());
return ResponseEntity.ok(new GridResponse<>(page)); return ResponseEntity.ok(new GridResponse<>(page));
} }
/**
* 요약 통계 행의 apiName(=API ID)으로 TSEAIHE01 을 조회하여 API 명(EAISVCDESC)을 채운다.
* 매칭되는 항목이 없으면 공란으로 둔다.
*/
private void fillApiDesc(List<ApiStatsUI> rows) {
if (rows == null || rows.isEmpty()) {
return;
}
Set<String> apiIds = rows.stream()
.map(ApiStatsUI::getApiName)
.filter(id -> id != null && !id.isEmpty())
.collect(Collectors.toSet());
if (apiIds.isEmpty()) {
return;
}
// API_STATS_DAY 와 동일한 APIGW 스키마에서 조회
DataSourceContextHolder.setDataSourceType(
DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.APIGW));
Map<String, String> nameMap = apiNameDao.selectApiNames(apiIds);
for (ApiStatsUI row : rows) {
row.setApiDesc(nameMap.get(row.getApiName()));
}
}
@PostMapping(value = "/onl/kjb/statistics/apiStatsDayMan.json", params = "cmd=EXCEL_EXPORT") @PostMapping(value = "/onl/kjb/statistics/apiStatsDayMan.json", params = "cmd=EXCEL_EXPORT")
public void exportToExcel(ApiStatsSearch search, HttpServletResponse response) throws IOException { public void exportToExcel(ApiStatsSearch search, HttpServletResponse response) throws IOException {
log.info("Excel export started - search: {}", search); log.info("Excel export started - search: {}", search);
@@ -4,6 +4,8 @@ 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.List; import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@@ -18,7 +20,10 @@ import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import com.eactive.eai.rms.common.datasource.DataSourceContextHolder;
import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
import com.eactive.eai.rms.common.vo.GridResponse; import com.eactive.eai.rms.common.vo.GridResponse;
import com.eactive.eai.rms.data.entity.onl.apim.obp.ObpGwMetricJpaDAO;
import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsHour; import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsHour;
import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsHourService; import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsHourService;
import com.eactive.ext.kjb.statistics.mapping.ApiStatsUIMapper; import com.eactive.ext.kjb.statistics.mapping.ApiStatsUIMapper;
@@ -38,6 +43,7 @@ public class ApiStatsHourController {
private final ApiStatsHourService service; private final ApiStatsHourService service;
private final ApiStatsUIMapper mapper; private final ApiStatsUIMapper mapper;
private final ApiStatsExcelExportService excelExportService; private final ApiStatsExcelExportService excelExportService;
private final ObpGwMetricJpaDAO apiNameDao;
@GetMapping(value = "/onl/kjb/statistics/apiStatsHourMan.view") @GetMapping(value = "/onl/kjb/statistics/apiStatsHourMan.view")
public String view() { public String view() {
@@ -66,9 +72,38 @@ public class ApiStatsHourController {
@PostMapping(value = "/onl/kjb/statistics/apiStatsHourMan.json", params = "cmd=LIST_SUMMARY") @PostMapping(value = "/onl/kjb/statistics/apiStatsHourMan.json", params = "cmd=LIST_SUMMARY")
public ResponseEntity<GridResponse<ApiStatsUI>> selectSummaryList(ApiStatsSearch search, Pageable pageable) { public ResponseEntity<GridResponse<ApiStatsUI>> selectSummaryList(ApiStatsSearch search, Pageable pageable) {
Page<ApiStatsUI> page = service.selectSummaryList(search, pageable); Page<ApiStatsUI> page = service.selectSummaryList(search, pageable);
fillApiDesc(page.getContent());
return ResponseEntity.ok(new GridResponse<>(page)); return ResponseEntity.ok(new GridResponse<>(page));
} }
/**
* 요약 통계 행의 apiName(=API ID)으로 TSEAIHE01 을 조회하여 API 명(EAISVCDESC)을 채운다.
* 매칭되는 항목이 없으면 공란으로 둔다.
*/
private void fillApiDesc(List<ApiStatsUI> rows) {
if (rows == null || rows.isEmpty()) {
return;
}
Set<String> apiIds = rows.stream()
.map(ApiStatsUI::getApiName)
.filter(id -> id != null && !id.isEmpty())
.collect(Collectors.toSet());
if (apiIds.isEmpty()) {
return;
}
// API_STATS_HOUR 와 동일한 APIGW 스키마에서 조회
DataSourceContextHolder.setDataSourceType(
DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.APIGW));
Map<String, String> nameMap = apiNameDao.selectApiNames(apiIds);
for (ApiStatsUI row : rows) {
row.setApiDesc(nameMap.get(row.getApiName()));
}
}
@PostMapping(value = "/onl/kjb/statistics/apiStatsHourMan.json", params = "cmd=EXCEL_EXPORT") @PostMapping(value = "/onl/kjb/statistics/apiStatsHourMan.json", params = "cmd=EXCEL_EXPORT")
public void exportToExcel(ApiStatsSearch search, HttpServletResponse response) throws IOException { public void exportToExcel(ApiStatsSearch search, HttpServletResponse response) throws IOException {
log.info("Excel export started - search: {}", search); log.info("Excel export started - search: {}", search);
@@ -4,6 +4,8 @@ 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.List; import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@@ -18,7 +20,10 @@ import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import com.eactive.eai.rms.common.datasource.DataSourceContextHolder;
import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
import com.eactive.eai.rms.common.vo.GridResponse; import com.eactive.eai.rms.common.vo.GridResponse;
import com.eactive.eai.rms.data.entity.onl.apim.obp.ObpGwMetricJpaDAO;
import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsMinute; import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsMinute;
import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsMinuteService; import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsMinuteService;
import com.eactive.ext.kjb.statistics.mapping.ApiStatsUIMapper; import com.eactive.ext.kjb.statistics.mapping.ApiStatsUIMapper;
@@ -38,6 +43,7 @@ public class ApiStatsMinuteController {
private final ApiStatsMinuteService service; private final ApiStatsMinuteService service;
private final ApiStatsUIMapper mapper; private final ApiStatsUIMapper mapper;
private final ApiStatsExcelExportService excelExportService; private final ApiStatsExcelExportService excelExportService;
private final ObpGwMetricJpaDAO apiNameDao;
@GetMapping(value = "/onl/kjb/statistics/apiStatsMinuteMan.view") @GetMapping(value = "/onl/kjb/statistics/apiStatsMinuteMan.view")
public String view() { public String view() {
@@ -66,9 +72,38 @@ public class ApiStatsMinuteController {
@PostMapping(value = "/onl/kjb/statistics/apiStatsMinuteMan.json", params = "cmd=LIST_SUMMARY") @PostMapping(value = "/onl/kjb/statistics/apiStatsMinuteMan.json", params = "cmd=LIST_SUMMARY")
public ResponseEntity<GridResponse<ApiStatsUI>> selectSummaryList(ApiStatsSearch search, Pageable pageable) { public ResponseEntity<GridResponse<ApiStatsUI>> selectSummaryList(ApiStatsSearch search, Pageable pageable) {
Page<ApiStatsUI> page = service.selectSummaryList(search, pageable); Page<ApiStatsUI> page = service.selectSummaryList(search, pageable);
fillApiDesc(page.getContent());
return ResponseEntity.ok(new GridResponse<>(page)); return ResponseEntity.ok(new GridResponse<>(page));
} }
/**
* 요약 통계 행의 apiName(=API ID)으로 TSEAIHE01 을 조회하여 API 명(EAISVCDESC)을 채운다.
* 매칭되는 항목이 없으면 공란으로 둔다.
*/
private void fillApiDesc(List<ApiStatsUI> rows) {
if (rows == null || rows.isEmpty()) {
return;
}
Set<String> apiIds = rows.stream()
.map(ApiStatsUI::getApiName)
.filter(id -> id != null && !id.isEmpty())
.collect(Collectors.toSet());
if (apiIds.isEmpty()) {
return;
}
// API_STATS_MINUTE 와 동일한 APIGW 스키마에서 조회
DataSourceContextHolder.setDataSourceType(
DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.APIGW));
Map<String, String> nameMap = apiNameDao.selectApiNames(apiIds);
for (ApiStatsUI row : rows) {
row.setApiDesc(nameMap.get(row.getApiName()));
}
}
@PostMapping(value = "/onl/kjb/statistics/apiStatsMinuteMan.json", params = "cmd=EXCEL_EXPORT") @PostMapping(value = "/onl/kjb/statistics/apiStatsMinuteMan.json", params = "cmd=EXCEL_EXPORT")
public void exportToExcel(ApiStatsSearch search, HttpServletResponse response) throws IOException { public void exportToExcel(ApiStatsSearch search, HttpServletResponse response) throws IOException {
log.info("Excel export started - search: {}", search); log.info("Excel export started - search: {}", search);
@@ -4,6 +4,8 @@ import java.io.IOException;
import java.time.YearMonth; import java.time.YearMonth;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@@ -18,7 +20,10 @@ import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import com.eactive.eai.rms.common.datasource.DataSourceContextHolder;
import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
import com.eactive.eai.rms.common.vo.GridResponse; import com.eactive.eai.rms.common.vo.GridResponse;
import com.eactive.eai.rms.data.entity.onl.apim.obp.ObpGwMetricJpaDAO;
import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsMonth; import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsMonth;
import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsMonthService; import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsMonthService;
import com.eactive.ext.kjb.statistics.mapping.ApiStatsUIMapper; import com.eactive.ext.kjb.statistics.mapping.ApiStatsUIMapper;
@@ -38,6 +43,7 @@ public class ApiStatsMonthController {
private final ApiStatsMonthService service; private final ApiStatsMonthService service;
private final ApiStatsUIMapper mapper; private final ApiStatsUIMapper mapper;
private final ApiStatsExcelExportService excelExportService; private final ApiStatsExcelExportService excelExportService;
private final ObpGwMetricJpaDAO apiNameDao;
@GetMapping(value = "/onl/kjb/statistics/apiStatsMonthMan.view") @GetMapping(value = "/onl/kjb/statistics/apiStatsMonthMan.view")
public String view() { public String view() {
@@ -66,9 +72,38 @@ public class ApiStatsMonthController {
@PostMapping(value = "/onl/kjb/statistics/apiStatsMonthMan.json", params = "cmd=LIST_SUMMARY") @PostMapping(value = "/onl/kjb/statistics/apiStatsMonthMan.json", params = "cmd=LIST_SUMMARY")
public ResponseEntity<GridResponse<ApiStatsUI>> selectSummaryList(ApiStatsSearch search, Pageable pageable) { public ResponseEntity<GridResponse<ApiStatsUI>> selectSummaryList(ApiStatsSearch search, Pageable pageable) {
Page<ApiStatsUI> page = service.selectSummaryList(search, pageable); Page<ApiStatsUI> page = service.selectSummaryList(search, pageable);
fillApiDesc(page.getContent());
return ResponseEntity.ok(new GridResponse<>(page)); return ResponseEntity.ok(new GridResponse<>(page));
} }
/**
* 요약 통계 행의 apiName(=API ID)으로 TSEAIHE01 을 조회하여 API 명(EAISVCDESC)을 채운다.
* 매칭되는 항목이 없으면 공란으로 둔다.
*/
private void fillApiDesc(List<ApiStatsUI> rows) {
if (rows == null || rows.isEmpty()) {
return;
}
Set<String> apiIds = rows.stream()
.map(ApiStatsUI::getApiName)
.filter(id -> id != null && !id.isEmpty())
.collect(Collectors.toSet());
if (apiIds.isEmpty()) {
return;
}
// API_STATS_MONTH 와 동일한 APIGW 스키마에서 조회
DataSourceContextHolder.setDataSourceType(
DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.APIGW));
Map<String, String> nameMap = apiNameDao.selectApiNames(apiIds);
for (ApiStatsUI row : rows) {
row.setApiDesc(nameMap.get(row.getApiName()));
}
}
@PostMapping(value = "/onl/kjb/statistics/apiStatsMonthMan.json", params = "cmd=EXCEL_EXPORT") @PostMapping(value = "/onl/kjb/statistics/apiStatsMonthMan.json", params = "cmd=EXCEL_EXPORT")
public void exportToExcel(ApiStatsSearch search, HttpServletResponse response) throws IOException { public void exportToExcel(ApiStatsSearch search, HttpServletResponse response) throws IOException {
log.info("Excel export started - search: {}", search); log.info("Excel export started - search: {}", search);
@@ -3,6 +3,8 @@ package com.eactive.ext.kjb.statistics;
import java.io.IOException; import java.io.IOException;
import java.time.Year; import java.time.Year;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@@ -17,7 +19,10 @@ import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import com.eactive.eai.rms.common.datasource.DataSourceContextHolder;
import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
import com.eactive.eai.rms.common.vo.GridResponse; import com.eactive.eai.rms.common.vo.GridResponse;
import com.eactive.eai.rms.data.entity.onl.apim.obp.ObpGwMetricJpaDAO;
import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsYear; import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsYear;
import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsYearService; import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsYearService;
import com.eactive.ext.kjb.statistics.mapping.ApiStatsUIMapper; import com.eactive.ext.kjb.statistics.mapping.ApiStatsUIMapper;
@@ -37,6 +42,7 @@ public class ApiStatsYearController {
private final ApiStatsYearService service; private final ApiStatsYearService service;
private final ApiStatsUIMapper mapper; private final ApiStatsUIMapper mapper;
private final ApiStatsExcelExportService excelExportService; private final ApiStatsExcelExportService excelExportService;
private final ObpGwMetricJpaDAO apiNameDao;
@GetMapping(value = "/onl/kjb/statistics/apiStatsYearMan.view") @GetMapping(value = "/onl/kjb/statistics/apiStatsYearMan.view")
public String view() { public String view() {
@@ -65,9 +71,38 @@ public class ApiStatsYearController {
@PostMapping(value = "/onl/kjb/statistics/apiStatsYearMan.json", params = "cmd=LIST_SUMMARY") @PostMapping(value = "/onl/kjb/statistics/apiStatsYearMan.json", params = "cmd=LIST_SUMMARY")
public ResponseEntity<GridResponse<ApiStatsUI>> selectSummaryList(ApiStatsSearch search, Pageable pageable) { public ResponseEntity<GridResponse<ApiStatsUI>> selectSummaryList(ApiStatsSearch search, Pageable pageable) {
Page<ApiStatsUI> page = service.selectSummaryList(search, pageable); Page<ApiStatsUI> page = service.selectSummaryList(search, pageable);
fillApiDesc(page.getContent());
return ResponseEntity.ok(new GridResponse<>(page)); return ResponseEntity.ok(new GridResponse<>(page));
} }
/**
* 요약 통계 행의 apiName(=API ID)으로 TSEAIHE01 을 조회하여 API 명(EAISVCDESC)을 채운다.
* 매칭되는 항목이 없으면 공란으로 둔다.
*/
private void fillApiDesc(List<ApiStatsUI> rows) {
if (rows == null || rows.isEmpty()) {
return;
}
Set<String> apiIds = rows.stream()
.map(ApiStatsUI::getApiName)
.filter(id -> id != null && !id.isEmpty())
.collect(Collectors.toSet());
if (apiIds.isEmpty()) {
return;
}
// API_STATS_YEAR 와 동일한 APIGW 스키마에서 조회
DataSourceContextHolder.setDataSourceType(
DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.APIGW));
Map<String, String> nameMap = apiNameDao.selectApiNames(apiIds);
for (ApiStatsUI row : rows) {
row.setApiDesc(nameMap.get(row.getApiName()));
}
}
@PostMapping(value = "/onl/kjb/statistics/apiStatsYearMan.json", params = "cmd=EXCEL_EXPORT") @PostMapping(value = "/onl/kjb/statistics/apiStatsYearMan.json", params = "cmd=EXCEL_EXPORT")
public void exportToExcel(ApiStatsSearch search, HttpServletResponse response) throws IOException { public void exportToExcel(ApiStatsSearch search, HttpServletResponse response) throws IOException {
log.info("Excel export started - search: {}", search); log.info("Excel export started - search: {}", search);
@@ -11,6 +11,7 @@ import lombok.Data;
public class ApiStatsUI { public class ApiStatsUI {
private String statTime; private String statTime;
private String apiName; private String apiName;
private String apiDesc;
private String gwInstanceId; private String gwInstanceId;
private String bizDivCode; private String bizDivCode;
private String clientId; private String clientId;