API 외부데이타 통계 화면 추가

API 호출 건수 통계 날짜별 API 별 표시처리, CSV 내보내기 처리
main
유지인 2025-11-13 14:52:15 +09:00
parent 36a47a44c9
commit 23c591c1df
2 changed files with 192 additions and 164 deletions

View File

@ -37,58 +37,64 @@
<!-- API 관리 > 날짜별 통계 -->
<select id="selectWebApiLogDailyCount" parameterType="map" resultType="egovMap">
WITH date_range AS (
SELECT TO_DATE(#{COUNT_FROM_DT}, 'YYYY-MM-DD') + LEVEL - 1 AS dt
FROM dual
CONNECT BY LEVEL <![CDATA[<=]]> TO_DATE(#{COUNT_TO_DT}, 'YYYY-MM-DD') - TO_DATE(#{COUNT_FROM_DT}, 'YYYY-MM-DD') + 1
)
SELECT
TO_CHAR(dr.dt, 'YYYY-MM-DD') AS period,
NVL(COUNT(wal.access_dt), 0) || '건' AS access_count
FROM
date_range dr
LEFT JOIN GEOINFO.WEB_API_LOG wal
ON TRUNC(wal.access_dt) = dr.dt
GROUP BY dr.dt
ORDER BY dr.dt
SELECT TO_CHAR(TRUNC(wal.access_dt), 'YYYY-MM-DD') AS period
,wai.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
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,
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
GROUP BY mr.month_start
ORDER BY mr.month_start
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_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,
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
GROUP BY yr.year_start
ORDER BY yr.year_start
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_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">

View File

@ -7,10 +7,6 @@
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<!-- Bootstrap Datepicker -->
<!-- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.9.0/css/bootstrap-datepicker.min.css"> -->
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.9.0/js/bootstrap-datepicker.min.js"></script> -->
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.9.0/locales/bootstrap-datepicker.ko.min.js"></script> -->
<!-- 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>
@ -509,10 +505,11 @@
}
requestAnimationFrame(animation);
}
// --------- 일일 접속량 차트 관련 변수 --------------------
let trafficChart; // 전역 변수
// --------- 일일 접속량 차트 관련 변수 --------------------
// --------- API 호출 통계 기간 입력 관련 변수 --------------------
var today = new Date(); // (기준)오늘 날짜
var thisYear = today.getFullYear(); // (기준)올해연도
var thisMonth = today.getMonth+1; // (기준)이번달
@ -523,6 +520,16 @@
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. 날짜 선택기 초기화
@ -617,7 +624,7 @@
getMgmtApiList();
// API 호출 통계 > 일별 통계
getApiStatDaily();
getApiStat();
// API 호출로그 목록 조회
getMgmtApiLogList();
@ -632,12 +639,16 @@
// API 호출 현황 건수 - 날짜 변경 시 차트 다시 로드
$(document).on('change', '#count_from_dt, #count_to_dt', function() {
// console.log(validDate())
if(!validDate()){
return;
}
getApiStatDaily();
getApiStat();
});
// API 호출 통계 건수 - CSV 내보내기 클릭
$(document).on('click', '#export-csv-btn', function() {
exportToCSV();
})
});
// API 호출 통제 목록 조회
@ -728,8 +739,8 @@
return html;
}
// API 호출 통계 > 일별 통계
function getApiStatDaily(){
// 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();
@ -751,31 +762,34 @@
},
dataType :"json",
success : function(res){ // res.code, res.msg, res.data
if (res.code == "SUCCESS") {
let countList = res.data; //Array List
let $listContainer;
if (statType === "monthly") {
$listContainer = $("#monthly-tbody");
} else if (statType === "yearly") {
$listContainer = $("#yearly-tbody");
} else { // statType === "daily"
$listContainer = $("#daily-tbody");
}
$listContainer.empty(); // 기존 내용 제거
if (!countList.length > 1) {
$listContainer.append('<tr><td colspan="3">표시 할 통계데이타가 없습니다.</td></tr>');
return;
}
// 데이터 반복
$.each(res.data, function(i, item) {
// DOM에 추가
$listContainer.append(drawApiCountList(item));
});
} else {
alert("API 호출 로그 목록 조회중 오류가 발생하였습니다. 다시 시도해주세요.");
}
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 호출 로그 목록 조회중 내부 오류가 발생하였습니다. 다시 시도해주세요.");
@ -783,19 +797,43 @@
});
};
function drawApiCountList(item) {
// HTML 문자열 생성
const html = `
<tr>
<td>\${item.period}</td>
<td></td>
<td>\${item.accessCount}</td>
</tr>
`;
return html;
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({
@ -838,7 +876,6 @@
`;
return html;
}
// API 호출 로그 목록 조회
function getMgmtApiChart(){
@ -912,21 +949,65 @@
}
});
}
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 호출 통계 날짜 유효검사
*/
@ -940,65 +1021,6 @@
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>