thkim 2025-11-14 11:25:32 +09:00
commit 69b2ecd80b
9 changed files with 652 additions and 125 deletions

View File

@ -1,5 +1,6 @@
package geoinfo.admins.apiManagement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@ -70,6 +71,44 @@ public class ApiInDataManagementController {
return result;
}
/**
* API > API
* @param params
* @param model
* @param response
* @param request
* @return
* @throws Exception
*/
@ResponseBody
@RequestMapping(value = "/admins/mgmtApi/i_counts.do", method = RequestMethod.POST)
public HashMap<String, Object> getMgmtApiInDataCounts(@RequestParam HashMap<String, Object> params, ModelMap model, HttpServletResponse response, HttpServletRequest request) throws Exception {
strUtil sUtil = new strUtil();
HashMap<String, Object> result = new HashMap<String, Object>();
String STAT_TYPE = sUtil.checkNull((String)params.get("statType"));
String COUNT_FROM_DT = sUtil.checkNull((String)params.get("countFromDt"));
String COUNT_TO_DT = sUtil.checkNull((String)params.get("countToDt"));
params.put("COUNT_FROM_DT", COUNT_FROM_DT);
params.put("COUNT_TO_DT", COUNT_TO_DT);
List<?> countList = new ArrayList<>();
if("monthly".equals(STAT_TYPE)) {
countList = apiInDataManagementService.selectWebApiLogMonthlyCount(params);
} else if("yearly".equals(STAT_TYPE)) {
countList = apiInDataManagementService.selectWebApiLogYearlyCount(params);
} else { // daily(기본)
countList = apiInDataManagementService.selectWebApiLogDailyCount(params);
}
result.put("code", "SUCCESS");
result.put("msg", "일일접속량 데이터 조회를 성공했습니다.");
result.put("data", countList);
return result;
}
/**
* API > API

View File

@ -10,6 +10,12 @@ import egovframework.rte.psl.dataaccess.util.EgovMap;
public interface ApiInDataManagementMapper {
public EgovMap selectDailyAccessCountByHour(HashMap<String, Object> params) throws Exception;
public List<?> selectWebApiLogDailyCount(HashMap<String, Object> params) throws Exception;
public List<?> selectWebApiLogMonthlyCount(HashMap<String, Object> params) throws Exception;
public List<?> selectWebApiLogYearlyCount(HashMap<String, Object> params) throws Exception;
public List<?> selectInfo(HashMap<String, Object> params) throws Exception;

View File

@ -10,6 +10,12 @@ import egovframework.rte.psl.dataaccess.util.EgovMap;
public interface ApiInDataManagementService {
public EgovMap selectDailyAccessCountByHour(HashMap<String, Object> params) throws Exception;
public List<?> selectWebApiLogDailyCount(HashMap<String, Object> params) throws Exception;
public List<?> selectWebApiLogMonthlyCount(HashMap<String, Object> params) throws Exception;
public List<?> selectWebApiLogYearlyCount(HashMap<String, Object> params) throws Exception;
public List<?> selectInfo(HashMap<String, Object> params) throws Exception;

View File

@ -22,6 +22,21 @@ public class ApiInDataManagementServiceImpl implements ApiInDataManagementServic
return masterMapper.selectDailyAccessCountByHour(params);
}
@Override
public List<?> selectWebApiLogDailyCount(HashMap<String, Object> params) throws Exception {
return masterMapper.selectWebApiLogDailyCount(params);
}
@Override
public List<?> selectWebApiLogMonthlyCount(HashMap<String, Object> params) throws Exception {
return masterMapper.selectWebApiLogMonthlyCount(params);
}
@Override
public List<?> selectWebApiLogYearlyCount(HashMap<String, Object> params) throws Exception {
return masterMapper.selectWebApiLogYearlyCount(params);
}
@Override
public List<?> selectInfo(HashMap<String, Object> params) throws Exception {
return masterMapper.selectInfo(params);

View File

@ -30,12 +30,73 @@
NVL(SUM(CASE WHEN TO_CHAR(ACCESS_DT, 'HH24') = '21' THEN 1 END), 0) AS COUNT_21,
NVL(SUM(CASE WHEN TO_CHAR(ACCESS_DT, 'HH24') = '22' THEN 1 END), 0) AS COUNT_22,
NVL(SUM(CASE WHEN TO_CHAR(ACCESS_DT, 'HH24') = '23' THEN 1 END), 0) AS COUNT_23
FROM GEOINFO.WEB_API_LOG
FROM GEOINFO.WEB_API_INBOUND_LOG
WHERE ACCESS_DT >= TRUNC(TO_DATE(#{CHART_DATE}, 'YYYY-MM-DD'))
AND ACCESS_DT <![CDATA[<]]> TRUNC(TO_DATE(#{CHART_DATE}, 'YYYY-MM-DD')) + 1
</select>
<!-- API 관리 > 날짜별 통계 -->
<select id="selectWebApiLogDailyCount" parameterType="map" resultType="egovMap">
SELECT TO_CHAR(TRUNC(wal.access_dt), 'YYYY-MM-DD') AS period
,wai.name AS api_name
,COUNT(*) AS access_count
FROM WEB_API_INBOUND_LOG WAL
INNER JOIN WEB_API_INBOUND WAI ON WAL.API_SEQ = WAI.IDX
WHERE WAL.ACCESS_DT BETWEEN TO_DATE(#{COUNT_FROM_DT}, 'YYYY-MM-DD')
AND TO_DATE(#{COUNT_TO_DT}, 'YYYY-MM-DD') + 0.99999
GROUP BY TRUNC(WAL.ACCESS_DT)
,WAI.NAME
,WAI.IDX
HAVING COUNT(*) > 0
ORDER BY TRUNC(WAL.ACCESS_DT) DESC
,wai.idx ASC
,wai.NAME
</select>
<!-- API 관리 > 월별 통계 -->
<select id="selectWebApiLogMonthlyCount" parameterType="map" resultType="egovMap">
WITH MONTH_RANGE AS (
SELECT ADD_MONTHS(TRUNC(TO_DATE(#{COUNT_FROM_DT}, 'YYYY-MM'), 'MM'), LEVEL - 1) AS MONTH_START
FROM DUAL
CONNECT BY LEVEL <![CDATA[<=]]>
MONTHS_BETWEEN(
TRUNC(TO_DATE(#{COUNT_TO_DT}, 'YYYY-MM'), 'MM'),
TRUNC(TO_DATE(#{COUNT_FROM_DT}, 'YYYY-MM'), 'MM')
) + 1
)
SELECT TO_CHAR(MR.MONTH_START, 'YYYY-MM') AS period
,wai.name AS api_name
,NVL(COUNT(WAL.ACCESS_DT), 0) AS access_count
FROM MONTH_RANGE MR
LEFT JOIN GEOINFO.WEB_API_INBOUND_LOG WAL ON TRUNC(WAL.ACCESS_DT, 'MM') = MR.MONTH_START
INNER JOIN GEOINFO.WEB_API_INBOUND WAI ON WAL.API_SEQ = WAI.IDX
GROUP BY MR.MONTH_START, wai.name, wai.idx
ORDER BY MR.MONTH_START DESC, wai.idx ASC, wai.name
</select>
<!-- API 관리 > 연도별 통계 -->
<select id="selectWebApiLogYearlyCount" parameterType="map" resultType="egovMap">
WITH YEAR_RANGE AS (
SELECT ADD_MONTHS(TRUNC(TO_DATE(#{COUNT_FROM_DT}, 'YYYY'), 'YYYY'), (LEVEL - 1) * 12) AS YEAR_START
FROM DUAL
CONNECT BY LEVEL <![CDATA[<=]]>
FLOOR(
MONTHS_BETWEEN(
TRUNC(TO_DATE(#{COUNT_TO_DT}, 'YYYY'), 'YYYY'),
TRUNC(TO_DATE(#{COUNT_FROM_DT}, 'YYYY'), 'YYYY')
) / 12
) + 1
)
SELECT TO_CHAR(YR.YEAR_START, 'YYYY') AS period
,wai.name AS api_name
,NVL(COUNT(WAL.ACCESS_DT), 0) AS access_count
FROM YEAR_RANGE YR
LEFT JOIN GEOINFO.WEB_API_INBOUND_LOG WAL ON TRUNC(WAL.ACCESS_DT, 'YYYY') = YR.YEAR_START
INNER JOIN GEOINFO.WEB_API_INBOUND WAI ON WAL.API_SEQ = WAI.IDX
GROUP BY YR.YEAR_START, wai.name, wai.idx
ORDER BY YR.YEAR_START DESC, wai.idx ASC, wai.name
</select>
<select id="selectInfo" parameterType="map" resultType="egovMap">
SELECT TB.RN
,TB.IDX
@ -76,7 +137,7 @@
,IP_ADDRESS
,ROW_NUMBER() OVER(ORDER BY LOG_SEQ DESC) RN
,COUNT(*) OVER () AS TOTALROWS
FROM WEB_API_LOG
FROM WEB_API_INBOUND_LOG
WHERE 1=1
) TB
</select>

View File

@ -30,7 +30,7 @@
NVL(SUM(CASE WHEN TO_CHAR(ACCESS_DT, 'HH24') = '21' THEN 1 END), 0) AS COUNT_21,
NVL(SUM(CASE WHEN TO_CHAR(ACCESS_DT, 'HH24') = '22' THEN 1 END), 0) AS COUNT_22,
NVL(SUM(CASE WHEN TO_CHAR(ACCESS_DT, 'HH24') = '23' THEN 1 END), 0) AS COUNT_23
FROM GEOINFO.WEB_API_LOG
FROM GEOINFO.WEB_API_OUTBOUND_LOG
WHERE ACCESS_DT >= TRUNC(TO_DATE(#{CHART_DATE}, 'YYYY-MM-DD'))
AND ACCESS_DT <![CDATA[<]]> TRUNC(TO_DATE(#{CHART_DATE}, 'YYYY-MM-DD')) + 1
</select>
@ -38,19 +38,19 @@
<!-- API 관리 > 날짜별 통계 -->
<select id="selectWebApiLogDailyCount" parameterType="map" resultType="egovMap">
SELECT TO_CHAR(TRUNC(wal.access_dt), 'YYYY-MM-DD') AS period
,wai.name AS api_name
,WAO.name AS api_name
,COUNT(*) AS access_count
FROM WEB_API_LOG WAL
INNER JOIN WEB_API_INBOUND WAI ON WAL.API_SEQ = WAI.IDX
FROM WEB_API_OUTBOUND_LOG WAL
INNER JOIN WEB_API_OUTBOUND WAO ON WAL.API_SEQ = WAO.IDX
WHERE WAL.ACCESS_DT BETWEEN TO_DATE(#{COUNT_FROM_DT}, 'YYYY-MM-DD')
AND TO_DATE(#{COUNT_TO_DT}, 'YYYY-MM-DD') + 0.99999
GROUP BY TRUNC(WAL.ACCESS_DT)
,WAI.NAME
,WAI.IDX
,WAO.NAME
,WAO.IDX
HAVING COUNT(*) > 0
ORDER BY TRUNC(WAL.ACCESS_DT) DESC
,wai.idx ASC
,wai.NAME
,WAO.idx ASC
,WAO.NAME
</select>
<!-- API 관리 > 월별 통계 -->
@ -65,13 +65,13 @@
) + 1
)
SELECT TO_CHAR(MR.MONTH_START, 'YYYY-MM') AS period
,wai.name AS api_name
,WAO.name AS api_name
,NVL(COUNT(WAL.ACCESS_DT), 0) AS access_count
FROM MONTH_RANGE MR
LEFT JOIN GEOINFO.WEB_API_LOG WAL ON TRUNC(WAL.ACCESS_DT, 'MM') = MR.MONTH_START
INNER JOIN GEOINFO.WEB_API_INBOUND WAI ON WAL.API_SEQ = WAI.IDX
GROUP BY MR.MONTH_START, wai.name, wai.idx
ORDER BY MR.MONTH_START DESC, wai.idx ASC, wai.name
LEFT JOIN GEOINFO.WEB_API_OUTBOUND_LOG WAL ON TRUNC(WAL.ACCESS_DT, 'MM') = MR.MONTH_START
INNER JOIN GEOINFO.WEB_API_OUTBOUND WAO ON WAL.API_SEQ = WAO.IDX
GROUP BY MR.MONTH_START, WAO.name, WAO.idx
ORDER BY MR.MONTH_START DESC, WAO.idx ASC, WAO.name
</select>
<!-- API 관리 > 연도별 통계 -->
@ -88,13 +88,13 @@
) + 1
)
SELECT TO_CHAR(YR.YEAR_START, 'YYYY') AS period
,wai.name AS api_name
,WAO.name AS api_name
,NVL(COUNT(WAL.ACCESS_DT), 0) AS access_count
FROM YEAR_RANGE YR
LEFT JOIN GEOINFO.WEB_API_LOG WAL ON TRUNC(WAL.ACCESS_DT, 'YYYY') = YR.YEAR_START
INNER JOIN GEOINFO.WEB_API_INBOUND WAI ON WAL.API_SEQ = WAI.IDX
GROUP BY YR.YEAR_START, wai.name, wai.idx
ORDER BY YR.YEAR_START DESC, wai.idx ASC, wai.name
LEFT JOIN GEOINFO.WEB_API_OUTBOUND_LOG WAL ON TRUNC(WAL.ACCESS_DT, 'YYYY') = YR.YEAR_START
INNER JOIN GEOINFO.WEB_API_OUTBOUND WAO ON WAL.API_SEQ = WAO.IDX
GROUP BY YR.YEAR_START, WAO.name, WAO.idx
ORDER BY YR.YEAR_START DESC, WAO.idx ASC, WAO.name
</select>
<select id="selectInfo" parameterType="map" resultType="egovMap">
@ -137,14 +137,14 @@
,IP_ADDRESS
,ROW_NUMBER() OVER(ORDER BY LOG_SEQ DESC) RN
,COUNT(*) OVER () AS TOTALROWS
FROM WEB_API_LOG
FROM WEB_API_OUTBOUND_LOG
WHERE 1=1
) TB
</select>
<!-- API 호출 활성상태 변경 -->
<update id="updateInfoStatus" parameterType="map">
UPDATE WEB_API_INBOUND
UPDATE WEB_API_OUTBOUND
SET
<if test="activeYn != null and activeYn !=''">
ACTIVE_YN = #{activeYn}

View File

@ -80,6 +80,9 @@ img { border:0; }
<div class="menu-item">
<span style="cursor:hand" onClick="javascript:goUrl('construction-user-mgmt-index', '${menuId}')"><img src="${pageContext.request.contextPath}/images/renew/arrow-right.png" /> &nbsp;발주기관 계정</span>
</div>
<div class="menu-item">
<span style="cursor:hand" onClick="javascript:goUrl('construction-user-login-history', '${menuId}')"><img src="${pageContext.request.contextPath}/images/renew/arrow-right.png" /> &nbsp;발주기관 로그인 내역</span>
</div>
</td>
</tr>
</table>

View File

@ -599,7 +599,6 @@
format: "yyyy",
language: "ko",
autoclose: true,
// minViewMode: 1, // 월 단위
todayHighlight: true,
container: $("#count_from_dt").closest(".datepicker-wrapper")
});
@ -619,12 +618,12 @@
animateValue("delayRate", 0, 0, 1500);
animateValue("errorRate", 0, 0, 1500);
// API 호출 통계 > 일별, 월별, 연도별 통계
getApiStat();
// API 호출 통제 목록 조회
getMgmtApiList();
// API 호출 통계 > 일별 통계
getApiStat();
// API 호출로그 목록 조회
getMgmtApiLogList();
@ -753,7 +752,7 @@
}
$.ajax({
type : "POST",
url : "/admins/mgmtApi/o_counts.do" ,
url : "/admins/mgmtApi/i_counts.do" ,
data : {
statType : statType,
countFromDt: $('#count_from_dt').val(),

View File

@ -3,11 +3,10 @@
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="ui" uri="http://egovframework.gov/ctl/ui"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<!-- datepicker -->
<!-- Bootstrap Datepicker -->
<link rel="stylesheet" href="${pageContext.request.contextPath}/js/bootstrap-datepicker-1.9.0-dist/css/bootstrap-datepicker.min.css">
<script type="text/javascript" src="${pageContext.request.contextPath}/js/bootstrap-datepicker-1.9.0-dist/js/bootstrap-datepicker.min.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath}/js/bootstrap-datepicker-1.9.0-dist/locales/bootstrap-datepicker.ko.min.js"></script>
@ -15,14 +14,127 @@
<!-- MUI + Chart.js + jQuery -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body { font-family: 'Pretendard', 'Roboto', sans-serif; background-color: #f5f6fa; margin: 0; padding: 20px; }
body { font-family: 'Pretendard', 'Roboto', sans-serif; background-color: #f5f6fa; color: #333; margin: 0; padding: 20px; }
.chart-container {
background: #fff; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.05);
padding: 20px; margin-bottom: 20px;
}
.chart-container h3 { margin-bottom: 10px; }
/* 국토지반 API 호출 통계 */
.stats-container {
margin: 20px auto;
background-color: #ffffff;
border: 1px solid #dbe3e0;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
/* overflow: hidden; */
}
/* 헤더 영역 (버튼 배치를 위해 수정) */
.stats-header {
padding: 20px 30px;
border-bottom: 1px solid #eee;
overflow: hidden; /* float을 포함하기 위함 */
}
.stats-header h1 {
margin: 0;
color: #2c3e50;
font-size: 24px;
display: inline-block; /* 버튼과 정렬 */
float: left;
}
/* CSV 내보내기 버튼 스타일 */
.export-btn {
float: right;
background-color: #007bff;
color: white;
border: none;
padding: 8px 14px;
border-radius: 5px;
font-weight: 600;
cursor: pointer;
font-size: 14px;
transition: background-color 0.2s;
}
.export-btn:hover {
background-color: #0056b3;
}
/* (이하 탭, 테이블 스타일 동일) */
.tab-nav {
display: flex;
background-color: #fdfdfd;
border-bottom: 1px solid #eee;
padding: 0 30px;
}
.tab-link {
font-size: 16px;
font-weight: 600;
color: #555;
background-color: transparent;
border: none;
padding: 15px 20px;
cursor: pointer;
border-bottom: 3px solid transparent;
transition: all 0.3s ease;
margin-right: 10px;
}
.tab-link:hover {
color: #007bff;
}
.tab-link.active {
color: #007bff;
border-bottom-color: #007bff;
}
.tab-content-wrapper {
padding: 30px;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.tab-content h2 {
font-size: 20px;
color: #34495e;
margin-bottom: 20px;
}
.stats-table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
}
.stats-table th,
.stats-table td {
border: 1px solid #e0e0e0;
padding: 12px 15px;
text-align: left;
}
.stats-table thead {
background-color: #f9fafa;
}
.stats-table th {
font-weight: 600;
color: #444;
}
.stats-table tbody tr:nth-child(even) {
background-color: #fcfcfc;
}
.stats-table td:last-child {
text-align: right;
font-weight: 600;
color: #e74c3c;
}
.stats-table .period-cell {
vertical-align: top;
font-weight: 600;
color: #333;
background-color: #fdfdfd;
}
/* 국토지반 API 호출 통계 END ------------------------- */
.dashboard-grid {
display: grid; grid-template-columns: 1fr 1fr; gap: 20px;
}
@ -182,6 +294,17 @@
.pagination button:hover:not(.active) {
background-color: #cbd5e1;
}
.datepicker-wrapper {
}
.datepicker-dropdown {
z-index: 99999 !important;
position: absolute;
left: 0 !important;
background: #fff;
border: 1px solid #e6e6e6;
}
</style>
</head>
<body>
@ -189,9 +312,78 @@
<!-- 일일 접속량 -->
<div class="chart-container">
<h3>📈 일일 접속량</h3>
<input type="text" name="chartDate" id="chartDate" class="input" placeholder="클릭하여 날짜 선택" >
<!-- <div id="chartDateContainer" style="position: relative;"><input type="text" name="chartDate" id="chartDate " class="input" placeholder="클릭하여 날짜 선택" ></div> -->
<div class="datepicker-wrapper" style="position: relative; display: inline-block;">
<input type="text" name="chartDate" id="chartDate" class="input" placeholder="클릭하여 날짜 선택">
</div>
<canvas id="trafficChart" height="100"></canvas>
</div>
<!-- 국토지반 API 호출 건수 통계 -->
<div class="stats-container">
<header class="stats-header">
<h1>국토지반 API 호출 통계</h1>
<button id="export-csv-btn" class="export-btn">CSV 내보내기</button>
</header>
<nav class="tab-nav">
<button class="tab-link active" data-tab="daily">일별 통계</button>
<button class="tab-link" data-tab="monthly">월별 통계</button>
<button class="tab-link" data-tab="yearly">연도별 통계</button>
</nav>
<div class="tab-content-wrapper">
<div class="datepicker-wrapper" style="position: relative; display: inline-block;">
<input type="text" name="count_from_dt" id="count_from_dt" class="input" placeholder="클릭하여 날짜 선택" >~
<input type="text" name="count_to_dt" id="count_to_dt" class="input" placeholder="클릭하여 날짜 선택" >
</div>
<div id="daily" class="tab-content active">
<h2>일별 API 호출 현황</h2>
<table class="stats-table">
<thead>
<tr>
<th>날짜</th>
<th>API 명</th>
<th>호출 건수</th>
</tr>
</thead>
<tbody id="daily-tbody">
<tr><td colspan="3">표시 할 통계데이타가 없습니다.</td></tr>
</tbody>
</table>
</div>
<div id="monthly" class="tab-content">
<h2>월별 API 호출 현황</h2>
<table class="stats-table">
<thead>
<tr>
<th>월</th>
<th>API 명</th>
<th>총 호출 건수</th>
</tr>
</thead>
<tbody id="monthly-tbody">
<tr><td colspan="3">표시 할 통계데이타가 없습니다.</td></tr>
</tbody>
</table>
</div>
<div id="yearly" class="tab-content">
<h2>연도별 API 호출 현황</h2>
<table class="stats-table">
<thead>
<tr>
<th>연도</th>
<th>API 명</th>
<th>총 호출 건수</th>
</tr>
</thead>
<tbody id="yearly-tbody">
<tr><td colspan="3">표시 할 통계데이타가 없습니다.</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="dashboard-grid">
<!-- API 호출 현황 -->
@ -245,17 +437,17 @@
</div>
</div>
<!-- API 목록 -->
<!-- API 호출 통제 -->
<div class="card">
<div class="card-header">
<h3>API 목록</h3>
<!-- <label class="mui-switch"> -->
<!-- <input type="checkbox" id="globalSwitch" onchange="toggleStatus(this)" checked> -->
<!-- <span class="mui-slider"></span> -->
<!-- </label> -->
<h3>API 호출 통제</h3>
<label class="mui-switch">
<input type="checkbox" id="globalSwitch" onchange="toggleStatus(this)" checked>
<span class="mui-slider"></span>
</label>
</div>
<div class="switch-container" id="apiSwitchList">
<div class="switch-container" id="outApiSwitchList">
<div class="api-switch">
<div>
<div class="switch-title">표시 할 API 목록이 존재하지 않습니다.</div>
@ -313,22 +505,124 @@
requestAnimationFrame(animation);
}
// --------- 일일 접속량 차트 관련 변수 --------------------
let trafficChart; // 전역 변수
// --------- 일일 접속량 차트 관련 변수 --------------------
// --------- API 호출 통계 기간 입력 관련 변수 --------------------
var today = new Date(); // (기준)오늘 날짜
var thisYear = today.getFullYear(); // (기준)올해연도
var thisMonth = today.getMonth+1; // (기준)이번달
var toDate = new Date(today.getFullYear(), today.getMonth(), 1); // (기준)이번달1일
var weekAgo = new Date(); // 7일 전 날짜
weekAgo.setDate(today.getDate() - 7);
var fvMonthAgo = new Date();
fvMonthAgo.setMonth(toDate.getMonth() -5);
// --------- API 호출 통계 기간 입력 관련 변수 --------------------
// --------- API 호출 통계 CSV 내보내기 관련 변수 --------------------
let processedStats = {
daily: new Map(),
monthly: new Map(),
yearly: new Map()
};
// --------- API 호출 통계 CSV 내보내기 관련 변수 --------------------
$(document).ready(function(){
// ----------------------------------------------------
// 1. 날짜 선택기 초기화
// ----------------------------------------------------
// datepicker
$("#chartDate").datepicker({
format: "yyyy-mm-dd",
$("#chartDate, #count_from_dt, #count_to_dt").datepicker({
format: "yyyy-mm-dd",
language: "ko",
autoclose: true
autoclose: true,
todayHighlight: true,
container: $("#chartDate").closest(".datepicker-wrapper")
}).datepicker("setDate", new Date());
$("#count_from_dt").datepicker("setDate", weekAgo); // 국토지반 API 호출 통계 시작일 셋팅(저번주)
// API 호출 현황
// ----------------------------------------------------
// 2. 탭 전환 처리
// ----------------------------------------------------
$('.tab-link').on('click', function() {
const targetId = $(this).data('tab');
$('.tab-link').removeClass('active');
$(this).addClass('active');
$('.tab-content').removeClass('active');
$('#' + targetId).addClass('active');
$("#count_from_dt, #count_to_dt").datepicker('destroy'); // datepicker 재선언(format 변경)
// 일별 탭 클릭 시
if (targetId === 'daily') {
// 월 단위 datepicker 재초기화
$("#count_from_dt, #count_to_dt").datepicker({
format: "yyyy-mm-dd",
language: "ko",
autoclose: true,
minViewMode: 0, // 월 단위
todayHighlight: true,
container: $("#count_from_dt").closest(".datepicker-wrapper")
});
$("#count_to_dt").datepicker("setDate", today); // 국토지반 API 호출 통계 시작일 셋팅(저번주)
$("#count_from_dt").datepicker("setDate", weekAgo); // 국토지반 API 호출 통계 시작일 셋팅(저번주)
}
// 월별 탭 클릭 시
if (targetId === 'monthly') {
// 월 단위 datepicker 재초기화
$("#count_from_dt, #count_to_dt").datepicker({
format: "yyyy-mm",
language: "ko",
autoclose: true,
minViewMode: 1, // 월 단위
todayHighlight: true,
container: $("#count_from_dt").closest(".datepicker-wrapper")
});
$("#count_from_dt").datepicker("setDate", fvMonthAgo);
$("#count_to_dt").datepicker("setDate", thisMonth);
$("#count_from_dt").val($("#count_from_dt").val().substr(0,7));
$("#count_to_dt").val($("#count_to_dt").val().substr(0,7));
}
// 연도별 탭 클릭 시
if (targetId === 'yearly') {
// 월 단위 datepicker 재초기화
$("#count_from_dt, #count_to_dt").datepicker({
minViewMode: 'years',
format: "yyyy",
language: "ko",
autoclose: true,
todayHighlight: true,
container: $("#count_from_dt").closest(".datepicker-wrapper")
});
$("#count_to_dt").datepicker("setDate", new Date(thisYear, 0, 1));
$("#count_from_dt").datepicker("setDate", new Date((thisYear -5), 0, 1));
$("#count_to_dt").val($("#count_to_dt").val().substr(0,4))
$("#count_from_dt").val($("#count_from_dt").val().substr(0,4))
}
});
// ----------------------------------------------------
// 3. API 호출 현황 초기 로드
// ----------------------------------------------------
animateValue("successRate", 0, 100, 1500);
animateValue("failRate", 0, 0, 1500);
animateValue("delayRate", 0, 0, 1500);
animateValue("errorRate", 0, 0, 1500);
// API 목록 목록 조회
// API 호출 통계 > 일별, 월별, 연도별 통계
getApiStat();
// 외부요청 API 호출 통제 목록 조회
getMgmtApiList();
// API 호출로그 목록 조회
@ -341,9 +635,22 @@
$(document).on('change', '#chartDate', function() {
getMgmtApiChart();
});
// API 호출 현황 건수 - 날짜 변경 시 차트 다시 로드
$(document).on('change', '#count_from_dt, #count_to_dt', function() {
if(!validDate()){
return;
}
getApiStat();
});
// API 호출 통계 건수 - CSV 내보내기 클릭
$(document).on('click', '#export-csv-btn', function() {
exportToCSV();
})
});
// 외부요청 API 목록 조회
// 외부요청 API 호출 통제 목록 조회
function getMgmtApiList(){
$.ajax({
type : "POST",
@ -353,7 +660,7 @@
success : function(res){ // res.code, res.msg, res.data
if (res.code == "SUCCESS") {
let procList = res.data; //Array List
const $listContainer = $("#apiSwitchList");
const $listContainer = $("#outApiSwitchList");
$listContainer.empty(); // 기존 내용 제거
// 데이터 반복
$.each(res.data, function(i, item) {
@ -377,7 +684,7 @@
let idxArr = [];
let active = $(ele).is(":checked") ? "Y" : "N"
if (btnType == 'globalSwitch') {
$('#apiSwitchList .api-switch').each(function(i, el) {
$('#outApiSwitchList .api-switch').each(function(i, el) {
const idx = $(el).data('idx');
idxArr.push(idx);
});
@ -387,14 +694,14 @@
$.ajax({
type : "POST",
url : "/admins/mgmtApi/toggleSts.do" ,
url : "/admins/mgmtApi/o_toggleSts.do" ,
data : {idxArr:idxArr, activeYn: active},
traditional: true,
dataType :"json",
success : function(res){ // res.code, res.msg, res.data
if (res.code == "SUCCESS") {
let procList = res.data; //Array List
const $listContainer = $("#apiSwitchList");
const $listContainer = $("#outApiSwitchList");
$listContainer.empty(); // 기존 내용 제거
// 데이터 반복
$.each(res.data, function(i, item) {
@ -422,15 +729,110 @@
<div class="switch-title">\${item.idx} \${item.name}</div>
<div class="switch-desc">\${item.desc.replace(/\\n/g, "<br>")}</div>
</div>
<%-- <label class="mui-switch">
<label class="mui-switch">
<input type="checkbox" class="api-toggle" data-api="\${item.idx}" onchange="toggleStatus(this)" \${isChecked}>
<span class="mui-slider"></span>
</label>--%>
</label>
</div>
`;
return html;
}
// API 호출 통계 > 일별, 월별, 연도별 통계
function getApiStat(){
var statType = $('.stats-container > .tab-nav').find('.active').data("tab");
var fromDt = $('#count_from_dt').val();
var toDt = $('#count_to_dt').val();
// 날짜데이터가 올바르게 셋팅되지 않았을때는 요청하지 않는다.
if (statType === "monthly") {
if (fromDt.length < 7 || toDt.length < 7 ) return;
} else if (statType === "yearly") {
if (fromDt.length < 4 || toDt.length < 4 ) return;
} else { // statType === "daily"
if (fromDt.length < 10 || toDt.length < 10 ) return;
}
$.ajax({
type : "POST",
url : "/admins/mgmtApi/o_counts.do" ,
data : {
statType : statType,
countFromDt: $('#count_from_dt').val(),
countToDt: $('#count_to_dt').val()
},
dataType :"json",
success : function(res){ // res.code, res.msg, res.data
if (res.code == "SUCCESS") {
let countList = res.data;
let $listContainer;
// 활성화 된 탭의 tbody를 컨테이너로 지정
if (statType === "monthly") {
$listContainer = $("#monthly-tbody");
} else if (statType === "yearly") {
$listContainer = $("#yearly-tbody");
} else {
$listContainer = $("#daily-tbody");
}
// 활성화 된 탭의 csv 양식으로 지정
if (statType === "monthly") {
$listContainer = $("#monthly-tbody");
processedStats.monthly = countList;
} else if (statType === "yearly") {
$listContainer = $("#yearly-tbody");
processedStats.yearly = countList;
} else {
$listContainer = $("#daily-tbody");
processedStats.daily = countList;
}
// DOM에 추가
drawApiCountList(res.data, $listContainer);
} else {
alert("API 호출 로그 목록 조회중 오류가 발생하였습니다. 다시 시도해주세요.");
}
},
error : function(response){
alert("API 호출 로그 목록 조회중 내부 오류가 발생하였습니다. 다시 시도해주세요.");
}
});
};
function drawApiCountList(countList, $target) {
$target.empty();
if (!countList || countList.length === 0) {
$target.append('<tr><td colspan="3">표시 할 통계데이타가 없습니다.</td></tr>');
return;
}
const grouped = {};
let totalCount = 0; // 합계 초기화
countList.forEach(item => {
if (!grouped[item.period]) grouped[item.period] = [];
grouped[item.period].push(item);
totalCount += parseInt(item.accessCount);
});
let html = '';
Object.keys(grouped).sort((a, b) => b.localeCompare(a)).forEach(period => {
const items = grouped[period];
items.forEach((item, idx) => {
html += `<tr>
\${idx === 0 ? `<td rowspan="\${items.length}">\${period}</td>` : ''}
<td>\${item.apiName}</td>
<td>\${item.accessCount} 건</td>
</tr>`;
});
});
// 합계 row 추가
html += `<tr style="background:#f9fafa;">
<td colspan="2" style="font-weight:bold;">합계</td>
<td style="font-weight:bold;">\${totalCount} 건</td>
</tr>`;
$target.append(html);
}
// API 호출 로그 목록 조회
function getMgmtApiLogList(){
$.ajax({
@ -474,7 +876,6 @@
return html;
}
// API 호출 로그 목록 조회
function getMgmtApiChart(){
var chartDate = $('#chartDate').val();
@ -547,80 +948,77 @@
}
});
}
function exportToCSV() {
// 1. 현재 활성화된 탭 찾기
const activeTab = document.querySelector('.tab-link.active');
if (!activeTab) return;
const tabId = activeTab.getAttribute('data-tab'); // 'daily', 'monthly', 'yearly'
const dataList = processedStats[tabId]; // 전역 변수에서 배열 데이터 가져오기
/**
* 날짜형식 YYYYMMDDHHMI -> YYYY-MM-DD HH:MI
*/
function formatDateTime(raw) {
if (!raw || raw.length !== 12) return "";
return (
raw.slice(0, 4) + "-" +
raw.slice(4, 6) + "-" +
raw.slice(6, 8) + " " +
raw.slice(8, 10) + ":" +
raw.slice(10, 12)
);
if (!Array.isArray(dataList) || dataList.length === 0) {
alert('내보낼 데이터가 없습니다.');
return;
}
let headers = [];
let filename = '';
// 2. 탭에 맞는 헤더와 파일명 설정
if (tabId === 'daily') {
headers = ['날짜', 'API 명', '호출 건수'];
filename = 'api_stats_daily.csv';
} else if (tabId === 'monthly') {
headers = ['월', 'API 명', '총 호출 건수'];
filename = 'api_stats_monthly.csv';
} else { // 'yearly'
headers = ['연도', 'API 명', '총 호출 건수'];
filename = 'api_stats_yearly.csv';
}
// 3. 날짜 기준으로 내림차순 정렬
const sortedData = [...dataList].sort((a, b) => b.period.localeCompare(a.period));
// 4. CSV 헤더 + 데이터 행 구성
const rows = [headers];
sortedData.forEach(item => {
const { period, apiName, accessCount } = item;
rows.push([period, apiName, accessCount]);
});
// 5. CSV 문자열로 변환
const csvContent = rows.map(row => row.join(',')).join('\r\n');
// 6. UTF-8 BOM 추가 및 파일 다운로드
const bom = '\uFEFF';
const blob = new Blob([bom + csvContent], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
// 6. 리소스 정리
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
/**
* API 호출 통계 날짜 유효검사
*/
function validDate(){
var statType = $('.stats-container > .tab-nav').find('.active').data("tab");
var from_dt_val = $('#count_from_dt').val();
var to_dt_val = $('#count_to_dt').val();
if ("daily" === statType && (from_dt_val.length != 10 || to_dt_val.length != 10)) return false; // YYYY-MM-DD
if ("monthly" === statType && (from_dt_val.length != 7 || to_dt_val.length != 7)) return false; // YYYY-MM
if ("yearly" === statType && (from_dt_val.length != 4 || to_dt_val.length != 4)) return false; // YYYY
return true;
}
// 예시 데이터 (100개 생성)
// const apiLogs = Array.from({ length: 100 }, (_, i) => ({
// api: i % 2 === 0 ? "/API_CHA_085/request" : "/API_BH_020/dc332",
// method: ["GET", "POST", "PUT"][Math.floor(Math.random() * 3)],
// params: `subCode=test${i}&prvcCode=x${i}`,
// status: [ "200 OK", "404 NOT FOUND", "500 INTERNAL ERROR" ][Math.floor(Math.random() * 3)],
// time: Math.floor(Math.random() * 500),
// result: ["성공", "지연", "실패"][Math.floor(Math.random() * 3)]
// }));
// const rowsPerPage = 10;
// let currentPage = 1;
// function renderTable(page) {
// const tableBody = document.getElementById("apiLogBody");
// tableBody.innerHTML = "";
// const start = (page - 1) * rowsPerPage;
// const end = start + rowsPerPage;
// const pageData = apiLogs.slice(start, end);
// pageData.forEach(row => {
// const tr = document.createElement("tr");
// tr.innerHTML += '<td>' + row.api + '</td><td>' + row.method + '</td><td>' + row.params + '</td><td>' + row.status + '</td><td>' + row.time + '</td><td>' + getResultIcon(row.result) + ' ' +row.result + '</td>';
// tableBody.appendChild(tr);
// });
// }
// function renderPagination() {
// const totalPages = Math.ceil(apiLogs.length / rowsPerPage);
// const pagination = document.getElementById("pagination");
// pagination.innerHTML = "";
// for (let i = 1; i <= totalPages; i++) {
// const btn = document.createElement("button");
// btn.textContent = i;
// if (i === currentPage) btn.classList.add("active");
// btn.addEventListener("click", () => {
// currentPage = i;
// renderTable(currentPage);
// renderPagination();
// });
// pagination.appendChild(btn);
// }
// }
// function getResultIcon(result) {
// if (result === "성공")
// return `<span style="color:#22c55e;">✔</span>`;
// if (result === "실패")
// return `<span style="color:#ef4444;">⚠</span>`;
// if (result === "지연")
// return `<span style="color:#3b82f6;">⏱</span>`;
// return "";
// }
// 초기 렌더링
// renderTable(currentPage);
// renderPagination();
</script>
</body>