관리자 화면에서 각 API 목록이 보이고 API를 활성/비활성화하는 기능 구현

main
유지인 2025-11-04 15:56:55 +09:00
parent 18f26c7909
commit 55ebfd4645
6 changed files with 409 additions and 252 deletions

View File

@ -1,59 +1,28 @@
package geoinfo.admins.apiManagement;
import java.net.URLEncoder;
import java.security.SecureRandom;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.log4j.Logger;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Sheet;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import egovframework.com.cmm.service.EgovProperties;
import egovframework.rte.psl.dataaccess.util.EgovMap;
import egovframework.rte.ptl.mvc.tags.ui.pagination.PaginationInfo;
import geoinfo.admins.board.RefrncRoomController;
import geoinfo.admins.user.service.GeneralUserMngService;
import geoinfo.admins.user.service.HomeTrainingService;
import geoinfo.com.EgovExcel;
import geoinfo.com.GeoinfoCommon;
import geoinfo.comm.util.ScriptUtil;
import geoinfo.admins.apiManagement.service.ApiManagementService;
import geoinfo.session.UserInfo;
import geoinfo.util.MyUtil;
import whois.whoisSMS;
@Controller
public class ApiManagementController {
@Resource(name = "generalUserMngService")
private GeneralUserMngService masterService;
@Resource(name = "homeTrainingService")
private HomeTrainingService homeTrainingService;
@Resource(name = "apiManagementService")
private ApiManagementService apiManagementService;
/**
* API > API
@ -74,4 +43,61 @@ public class ApiManagementController {
model.addAttribute("params", params);
return "admins/mgmtApi/api-request-statistics-index";
}
/**
* API > API
* @param params
* @param model
* @param response
* @param request
* @return
* @throws Exception
*/
@ResponseBody
@RequestMapping(value = "/admins/mgmtApi/list.do", method = RequestMethod.POST)
public HashMap<String, Object> getMgmtApiList(@RequestParam HashMap<String, Object> params, ModelMap model, HttpServletResponse response, HttpServletRequest request) throws Exception {
HashMap<String, Object> result = new HashMap<String, Object>();
List<?> listData = apiManagementService.selectInfo(params);
result.put("code", "SUCCESS");
result.put("msg", "관리 API 목록 조회를 성공했습니다.");
result.put("data", listData);
return result;
}
/**
* API > API
* @param params
* @param model
* @param response
* @param request
* @return
* @throws Exception
*/
@ResponseBody
@RequestMapping(value = "/admins/mgmtApi/toggleSts.do", method = RequestMethod.POST)
public HashMap<String, Object> modMgmtApiActiveYn(
@RequestParam(value="idxArr") List<Integer> idxArr,
@RequestParam(value="activeYn", required=false) String activeYn, ModelMap model, HttpServletResponse response, HttpServletRequest request) throws Exception {
HashMap<String, Object> result = new HashMap<String, Object>();
// String sIdxArr[] = params.get("idx").toString().split(",");
System.out.println("idxArr = " + idxArr);
System.out.println("activeYn = " + activeYn);
HashMap<String,Object> params = new HashMap<>();
params.put("idxArr", idxArr);
params.put("activeYn", activeYn);
apiManagementService.updateInfoStatus(params);
List<?> listData = apiManagementService.selectInfo(params);
result.put("code", "SUCCESS");
result.put("msg", "관리 API 목록 조회를 성공했습니다.");
result.put("data", listData);
return result;
}
}

View File

@ -0,0 +1,24 @@
package geoinfo.admins.apiManagement.service;
import java.util.HashMap;
import java.util.List;
import egovframework.rte.psl.dataaccess.mapper.Mapper;
import egovframework.rte.psl.dataaccess.util.EgovMap;
@Mapper("ApiManagementMapper")
public interface ApiManagementMapper {
public List<?> selectInfo(HashMap<String, Object> params) throws Exception;
public void saveInfo(HashMap<String, Object> params) throws Exception;
// public EgovMap selectDetailInfo(HashMap<String, Object> params) throws Exception;
// public void deleteInfo(HashMap<String, Object> params) throws Exception;
// public EgovMap selectModifyInfo(HashMap<String, Object> params) throws Exception;
public void updateInfoStatus(HashMap<String, Object> params) throws Exception;
}

View File

@ -0,0 +1,24 @@
package geoinfo.admins.apiManagement.service;
import java.util.HashMap;
import java.util.List;
import egovframework.rte.psl.dataaccess.util.EgovMap;
public interface ApiManagementService {
public List<?> selectInfo(HashMap<String, Object> params) throws Exception;
public void saveInfo(HashMap<String, Object> params) throws Exception;
// public EgovMap selectDetailInfo(HashMap<String, Object> params) throws Exception;
// public void deleteInfo(HashMap<String, Object> params) throws Exception;
// public EgovMap selectModifyInfo(HashMap<String, Object> params) throws Exception;
public void updateInfoStatus(HashMap<String, Object> params) throws Exception;
}

View File

@ -0,0 +1,49 @@
package geoinfo.admins.apiManagement.service.impl;
import java.util.HashMap;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import geoinfo.admins.apiManagement.service.ApiManagementMapper;
import geoinfo.admins.apiManagement.service.ApiManagementService;
@Service("apiManagementService")
public class ApiManagementServiceImpl implements ApiManagementService {
@Resource(name = "ApiManagementMapper")
private ApiManagementMapper masterMapper;
@Override
public List<?> selectInfo(HashMap<String, Object> params) throws Exception {
return masterMapper.selectInfo(params);
}
@Override
public void saveInfo(HashMap<String, Object> params) throws Exception {
masterMapper.saveInfo(params);
}
// @Override
// public EgovMap selectDetailInfo(HashMap<String, Object> params) throws Exception {
// return masterMapper.selectDetailInfo(params);
// }
// @Override
// public void deleteInfo(HashMap<String, Object> params) throws Exception {
// masterMapper.deleteInfo(params);
// }
// @Override
// public EgovMap selectModifyInfo(HashMap<String, Object> params) throws Exception {
// return masterMapper.selectModifyInfo(params);
// }
@Override
public void updateInfoStatus(HashMap<String, Object> params) throws Exception {
masterMapper.updateInfoStatus(params);
}
}

View File

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="geoinfo.admins.apiManagement.service.ApiManagementMapper">
<select id="selectInfo" parameterType="map" resultType="egovMap">
SELECT TB.RN
,TB.IDX
,TB."NAME"
,TB."DESC"
,TB.ACTIVE_YN
,TO_CHAR(LAST_VALUE(ROWNUM) OVER (ORDER BY ROWNUM ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)) AS TOTALROWS
FROM (SELECT IDX
,"NAME"
,"DESC"
,ACTIVE_YN
,ROW_NUMBER() OVER(ORDER BY IDX ASC) RN
,TO_CHAR(LAST_VALUE(ROWNUM) OVER (ORDER BY ROWNUM ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)) AS TOTALROWS
FROM WEB_API_INBOUND
WHERE 1=1
) TB
<!-- WHERE RN BETWEEN #{firstIndex} + 1 AND #{firstIndex} + #{recordCountPerPage} -->
</select>
<insert id="saveInfo" parameterType="map">
<![CDATA[
INSERT INTO WEB_COMMUNITY
SELECT NVL(MAX(IDX),0) + 1, #{name}, #{password}, #{email}, #{homepage}, #{subject}, #{content}, NVL(MAX(IDX),0) + 1, 0, #{fileName1}, #{saveName1}, SYSDATE, 0, #{topnotice}, #{fileName2}, #{saveName2}, #{fileName3}, #{saveName3}
FROM WEB_COMMUNITY
]]>
</insert>
<!-- <select id="selectDetailInfo" parameterType="map" resultType="egovMap">
SELECT IDX, SUBJECT, EMAIL, HOMEPAGE, SAVENAME, FILENAME, SAVENAME2, FILENAME2, SAVENAME3, FILENAME3, CONTENT, NAME, READCOUNT, TO_CHAR(DATETIME,'YYYY-MM-DD') DATETIME, SEQ
FROM WEB_COMMUNITY
WHERE IDX = #{idx}
</select> -->
<!-- <delete id="deleteInfo" parameterType="map">
<![CDATA[
DELETE FROM WEB_COMMUNITY
WHERE IDX = #{idx}
]]>
</delete> -->
<!-- <select id="selectModifyInfo" parameterType="map" resultType="egovMap">
SELECT IDX, SUBJECT, CONTENT, NAME, READCOUNT, TO_CHAR(DATETIME,'YYYY-MM-DD') DATETIME, SEQ, FILENAME, SAVENAME, TOP, FILENAME2, SAVENAME2, FILENAME3, SAVENAME3
FROM WEB_COMMUNITY
WHERE IDX = #{idx}
</select> -->
<!-- API 호출 활성상태 변경 -->
<update id="updateInfoStatus" parameterType="map">
UPDATE WEB_API_INBOUND
SET
<if test="activeYn != null and activeYn !=''">
ACTIVE_YN = #{activeYn}
</if>
WHERE IDX IN (
<foreach item="item" index="index" collection="idxArr" open="" separator=", " close="">
#{item}
</foreach>
)
</update>
</mapper>

View File

@ -52,38 +52,97 @@
.error { background-color: #fff7e6; color: #f1c40f; }
/* 통제 카드 */
.switch-container {
max-height: 260px; overflow-y: auto;
}
.api-switch {
display: flex; justify-content: space-between; align-items: center;
background: #f8f8f8; border-radius: 10px; padding: 12px 15px; margin-bottom: 10px;
}
.switch-title { font-weight: 600; }
.switch-desc { font-size: 12px; color: #777; }
.mui-switch {
position: relative; display: inline-block; width: 48px; height: 26px;
}
.mui-switch input { opacity: 0; width: 0; height: 0; }
.mui-slider {
position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0;
background-color: #ccc; transition: 0.4s; border-radius: 34px;
}
.mui-slider:before {
position: absolute; content: "OFF"; color: #fff; font-size: 10px;
height: 20px; width: 20px; left: 3px; bottom: 3px;
background-color: #999; display: flex; justify-content: center; align-items: center;
border-radius: 50%; transition: 0.4s;
}
input:checked + .mui-slider {
background-color: #4A90E2;
}
input:checked + .mui-slider:before {
transform: translateX(22px);
content: "ON"; background-color: #1A73E8;
}
.switch-container {
max-height: 260px;
overflow-y: auto;
}
.api-switch {
display: flex;
justify-content: space-between;
align-items: center; /* 수직 가운데 정렬 */
background: #f8f8f8;
border-radius: 10px;
padding: 12px 15px;
margin-bottom: 10px;
gap: 12px; /* 텍스트와 스위치 사이 여백 */
}
.api-switch > div {
flex: 1; /* 왼쪽 내용이 남는 공간 차지 */
min-width: 0; /* 긴 텍스트 줄바꿈 정상화 */
}
.switch-title {
font-weight: 600;
font-size: 14px;
color: #222;
margin-bottom: 4px;
}
.switch-desc {
font-size: 12px;
color: #777;
line-height: 1.4;
white-space: pre-line; /* \n이나 <br>을 줄바꿈으로 인식 */
}
/* 스위치 디자인 */
.mui-switch {
position: relative;
flex-shrink: 0; /* 스위치 크기 줄어들지 않게 */
display: inline-block;
width: 48px;
height: 26px;
}
.mui-switch input {
opacity: 0;
width: 0;
height: 0;
}
.mui-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: 0.4s;
border-radius: 34px;
display: flex;
align-items: center;
justify-content: ${''}; /* 가운데 맞춤 */
}
.mui-slider:before {
position: absolute;
content: "OFF";
color: #fff;
font-size: 10px;
height: 20px;
width: 20px;
left: 3px;
bottom: 3px;
background-color: #999;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
transition: 0.4s;
}
input:checked + .mui-slider {
background-color: #4A90E2;
}
input:checked + .mui-slider:before {
transform: translateX(22px);
content: "ON";
background-color: #1A73E8;
}
/* 로그 테이블 */
.table-container {
@ -187,7 +246,7 @@
<div class="card-header">
<h3>API 호출 통제</h3>
<label class="mui-switch">
<input type="checkbox" id="globalSwitch" checked>
<input type="checkbox" id="globalSwitch" onchange="toggleStatus(this)" checked>
<span class="mui-slider"></span>
</label>
</div>
@ -195,172 +254,10 @@
<div class="switch-container" id="apiSwitchList">
<div class="api-switch">
<div>
<div class="switch-title">프로젝트 목록 조회</div>
<div class="switch-desc">TBL_PROJECT_INFO 테이블에서 검색 조건에 해당하는 레코드를 모두 검색</div>
</div>
<label class="mui-switch">
<input type="checkbox" class="api-toggle" data-api="api1" checked>
<span class="mui-slider"></span>
</label>
<div class="switch-title">표시 할 API 목록이 존재하지 않습니다.</div>
<div class="switch-desc">API를 등록해주세요.</div>
</div>
</div>
<div class="api-switch">
<div>
<div class="switch-title">프로젝트 개수 조회</div>
<div class="switch-desc">TBL_PROJECT_INFO 테이블에서 검색 조건에 해당하는 레코드의 개수를 조회</div>
</div>
<label class="mui-switch">
<input type="checkbox" class="api-toggle" data-api="api2" checked>
<span class="mui-slider"></span>
</label>
</div>
<div class="api-switch">
<div>
<div class="switch-title">프로젝트 관련 하위 정보 개수 조회</div>
<div class="switch-desc">특정 프로젝트에 속한 각 테이블(물리탐사, 시추조사, 각종 시험 등)의 데이터 개수를 조회</div>
</div>
<label class="mui-switch">
<input type="checkbox" class="api-toggle" data-api="api3" checked>
<span class="mui-slider"></span>
</label>
</div>
<!-- 그 이하 항목들도 동일한 방식으로 반복 -->
<div class="api-switch">
<div>
<div class="switch-title">프로젝트 관련 하위 시추공 정보 개수 조회</div>
<div class="switch-desc">특정 프로젝트에 속한 전체 시추공의 시험정보 개수를 조회</div>
</div>
<label class="mui-switch">
<input type="checkbox" class="api-toggle" data-api="api4" checked>
<span class="mui-slider"></span>
</label>
</div>
<div class="api-switch">
<div>
<div class="switch-title">지형 및 지질 정보 조회</div>
<div class="switch-desc">TBL_TOPO_GEOLOGY 테이블에서 특정 프로젝트의 지형 및 지질 정보를 조회</div>
</div>
<label class="mui-switch">
<input type="checkbox" class="api-toggle" data-api="api5" checked>
<span class="mui-slider"></span>
</label>
</div>
<div class="api-switch">
<div>
<div class="switch-title">전기비저항탐사시험</div>
<div class="switch-desc">TBL_RESISTIVITY_SURVEY 테이블에서 특정 프로젝트의 전기 비저항 탐사 정보를 조회</div>
</div>
<label class="mui-switch">
<input type="checkbox" class="api-toggle" data-api="api6" checked>
<span class="mui-slider"></span>
</label>
</div>
<div class="api-switch">
<div>
<div class="switch-title">굴절법탄성파</div>
<div class="switch-desc">TBL_REFRACTION_SURVEY 테이블에서 특정 프로젝트의 굴절법 탐사 정보를 조회</div>
</div>
<label class="mui-switch">
<input type="checkbox" class="api-toggle" data-api="api7" checked>
<span class="mui-slider"></span>
</label>
</div>
<div class="api-switch">
<div>
<div class="switch-title">시추정보</div>
<div class="switch-desc">사용자 작업용 API</div>
</div>
<label class="mui-switch">
<input type="checkbox" class="api-toggle" data-api="api8" checked>
<span class="mui-slider"></span>
</label>
</div>
<div class="api-switch">
<div>
<div class="switch-title">지층정보</div>
<div class="switch-desc">사용자 작업용 API</div>
</div>
<label class="mui-switch">
<input type="checkbox" class="api-toggle" data-api="api9" checked>
<span class="mui-slider"></span>
</label>
</div>
<div class="api-switch">
<div>
<div class="switch-title">표준관입시험</div>
<div class="switch-desc">사용자 작업용 API</div>
</div>
<label class="mui-switch">
<input type="checkbox" class="api-toggle" data-api="api10" checked>
<span class="mui-slider"></span>
</label>
</div>
<div class="api-switch">
<div>
<div class="switch-title">절리정보</div>
<div class="switch-desc">사용자 작업용 API</div>
</div>
<label class="mui-switch">
<input type="checkbox" class="api-toggle" data-api="api11" checked>
<span class="mui-slider"></span>
</label>
</div>
<div class="api-switch">
<div>
<div class="switch-title">TCR RQD 시험정보</div>
<div class="switch-desc">사용자 작업용 API</div>
</div>
<label class="mui-switch">
<input type="checkbox" class="api-toggle" data-api="api12" checked>
<span class="mui-slider"></span>
</label>
</div>
<div class="api-switch">
<div>
<div class="switch-title">DSF 시험정보</div>
<div class="switch-desc">사용자 작업용 API</div>
</div>
<label class="mui-switch">
<input type="checkbox" class="api-toggle" data-api="api13" checked>
<span class="mui-slider"></span>
</label>
</div>
<div class="api-switch">
<div>
<div class="switch-title">RMR정보</div>
<div class="switch-desc">사용자 작업용 API</div>
</div>
<label class="mui-switch">
<input type="checkbox" class="api-toggle" data-api="api14" checked>
<span class="mui-slider"></span>
</label>
</div>
<div class="api-switch">
<div>
<div class="switch-title">Q시험정보</div>
<div class="switch-desc">사용자 작업용 API</div>
</div>
<label class="mui-switch">
<input type="checkbox" class="api-toggle" data-api="api15" checked>
<span class="mui-slider"></span>
</label>
</div>
</div>
</div>
</div>
@ -471,8 +368,99 @@
animateValue("failRate", 0, 0, 1500);
animateValue("delayRate", 0, 0.5, 1500);
animateValue("errorRate", 0, 0, 1500);
// API 호출 통제 목록 조회
getMgmtApiList();
});
// API 호출 통제 목록 조회
function getMgmtApiList(){
$.ajax({
type : "POST",
url : "/admins/mgmtApi/list.do" ,
data : {},
dataType :"json",
success : function(res){ // res.code, res.msg, res.data
if (res.code == "SUCCESS") {
let procList = res.data; //Array List
const $listContainer = $("#apiSwitchList");
$listContainer.empty(); // 기존 내용 제거
// 데이터 반복
$.each(res.data, function(i, item) {
// DOM에 추가
$listContainer.append(drawApiControlList(item));
});
} else {
alert("관리 API 목록 조회중 오류가 발생하였습니다. 다시 시도해주세요.");
}
},
error : function(response){
alert("관리 API 목록 조회중 내부 오류가 발생하였습니다. 다시 시도해주세요.");
}
});
}
// 개별 API 활성상태 변경
function toggleStatus(ele) { // ele => 스위치(형제요소 input 값으로 변경할 상태 Y,N 그리고 btnType을 판별한다)
let btnType = $(ele).attr('id'); // 일괄변경 버튼, 각 변경 버튼 여부(globalSwitch면 일괄변경임)
let idxArr = [];
let active = $(ele).is(":checked") ? "Y" : "N"
if (btnType == 'globalSwitch') {
$('#apiSwitchList .api-switch').each(function(i, el) {
const idx = $(el).data('idx');
idxArr.push(idx);
});
} else {
idxArr.push($(ele).parents('.api-switch').data("idx"));
}
$.ajax({
type : "POST",
url : "/admins/mgmtApi/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");
$listContainer.empty(); // 기존 내용 제거
// 데이터 반복
$.each(res.data, function(i, item) {
// DOM에 추가
$listContainer.append(drawApiControlList(item));
});
} else {
alert("활성상태 변경 중 오류가 발생했습니다. 다시 시도해주세요");
}
},
error : function(response){
alert("활성상태 변경 중 내부 오류가 발생했습니다. 다시 시도해주세요");
}
});
}
function drawApiControlList(item) {
// activeYn 이 "Y"이면 체크 상태, 아니면 체크 해제
const isChecked = item.activeYn === "Y" ? "checked" : "";
// HTML 문자열 생성
const html = `
<div class="api-switch" data-idx="\${item.idx}">
<div>
<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">
<input type="checkbox" class="api-toggle" data-api="\${item.idx}" onchange="toggleStatus(this)" \${isChecked}>
<span class="mui-slider"></span>
</label>
</div>
`;
return html;
}
// Chart.js: 일일 접속량
const ctx = document.getElementById('trafficChart');
new Chart(ctx, {
@ -490,26 +478,6 @@
options: { plugins: { legend: { display: false } } }
});
// 일괄 On/Off
$("#globalSwitch").on("change", function() {
const enabled = $(this).is(":checked");
$(".api-toggle").prop("checked", enabled).trigger("change");
});
// 개별 토글
$(".api-toggle").on("change", function() {
const apiName = $(this).data("api");
const enabled = $(this).is(":checked");
$.ajax({
url: "/api/control",
method: "POST",
data: { api: apiName, status: enabled },
success: () => console.log(`API ${apiName} → ${enabled ? '활성' : '비활성'}`),
// error: () => return false; /*alert("API 상태 변경 실패")*/
});
});
function refreshDashboard() {
location.reload();
}