23 Commits

Author SHA1 Message Date
Rinjae 8a884e53e3 ci - readiness probe port 39120 으로 정정
eapim-admin CI / build (push) Has been cancelled
- DEPLOY_HTTP_PORT 30120 → 39120 (실서버 tomcat 포트)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-16 16:30:13 +09:00
Rinjae 7de00ecad2 ci - readiness probe 안정화
eapim-admin CI / build (push) Has been cancelled
- probe URL 변경: login.do(POST) → loginForm.do(GET)
- timeout 180s → 300s (200MB war 첫 explode 시간 확보)
- 실패 시 catalina 로그 300줄 + systemd status + 포트 출력

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-16 16:22:07 +09:00
Rinjae df5bbb162a ci 수정
eapim-admin CI / build (push) Has been cancelled
2026-06-16 15:56:46 +09:00
Rinjae 3eec267769 Merge commit '58fe176715b2027f783a2dae98e1583beaf34c8c'
eapim-admin CI / build (push) Has been cancelled
2026-06-16 15:48:41 +09:00
Rinjae 6ab7e1fe16 ci 수정 2026-06-16 15:48:30 +09:00
eastargh 58fe176715 조회시 정렬순서 추가
eapim-admin CI / build (push) Has been cancelled
2026-06-16 15:05:48 +09:00
Rinjae 6b6e2863cd 공지사항 관리 - 장애 점검 등록 UI 추가
eapim-admin CI / build (push) Has been cancelled
2026-06-16 14:55:39 +09:00
eastargh cfafccc34d CA 인증타입 제거
eapim-admin CI / build (push) Has been cancelled
2026-06-16 14:32:35 +09:00
eastargh fe98ac45c4 Merge branch 'master' of https://git.eactive.synology.me:8090/eapim/djbank/eapim-admin 2026-06-16 14:26:21 +09:00
eastargh 5f9de93d35 인증타입 CA 제거 2026-06-16 14:19:27 +09:00
Rinjae 48dfcf7714 Merge branch 'feats/ci-test'
eapim-admin CI / build (push) Has been cancelled
2026-06-16 13:22:38 +09:00
Rinjae 6e3906878a Merge commit 'fafe7e45cb35b7475cdc54f6b390b6c4845777d5' 2026-06-16 13:21:55 +09:00
eastargh fafe7e45cb 서버명 표시 2026-06-16 13:19:10 +09:00
eastargh eee7f46aa7 ums 관련 수정 2026-06-16 11:10:36 +09:00
eastargh b5eb63448a 레이아웃 기본 데이타타입 Boolean 추가 2026-06-15 17:52:34 +09:00
eastargh 54291e5cbb ums 관련 정리 2026-06-15 13:16:23 +09:00
Rinjae a0743465ab - generic package 업로드 단계 제거
eapim-admin CI / build (push) Has been cancelled
- 인증 정리 전까지 artifact만 업로드 유지
2026-06-12 20:51:36 +09:00
Rinjae d3e05256b7 - package upload 인증 헤더 방식 변경
- basic oauth2 user → Authorization token 헤더
2026-06-12 20:28:17 +09:00
Rinjae 4d5572f880 로그 설정 변경
eapim-admin CI / build (push) Has been cancelled
2026-06-12 19:43:33 +09:00
Rinjae e21014f992 - gitea actions 워크플로우 추가
- 7개 sibling clone 후 monitoring.war 빌드
- systemd user unit으로 tomcat 라이프사이클 분리
- /monitoring/login.do 로 readiness probe (120s)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-12 19:39:01 +09:00
eastargh ac0d081a9a 인터페이스 아웃바운드 path에 {} 포함시 variableUrlRequest로 설정 2026-06-12 10:18:39 +09:00
eastargh 164ab92919 BAP 서버 목록조회 쿼리에 rownum=1 제거 2026-06-12 09:27:08 +09:00
eastargh a6e7d1adb3 아웃바운드 URL restoption type 판단시 {}안에 .도 포함되도록 변경 2026-06-11 11:38:38 +09:00
35 changed files with 1323 additions and 144 deletions
+140
View File
@@ -0,0 +1,140 @@
name: eapim-admin CI
on:
push:
branches:
- master
- develop
- stage
- feats/ci-test
jobs:
build:
runs-on: [self-hosted, linux, eapim-admin]
env:
JAVA_HOME: /apps/opts/jdk8
JAVA_HOME_TOMCAT: /apps/opts/jdk17
CATALINA_BASE: /prod/eapim/adminportal
CATALINA_HOME: /prod/eapim/apache-tomcat-9.0.116
CATALINA_PID: /prod/eapim/adminportal/temp/catalina.pid
DEPLOY_HTTP_PORT: "39120"
defaults:
run:
working-directory: eapim-admin
steps:
- name: Checkout eapim-admin (trigger SHA)
uses: http://172.30.1.50:3000/actions/checkout@v4
with:
path: eapim-admin
- name: Checkout sibling modules (master)
working-directory: ${{ github.workspace }}
run: |
set -e
mkdir -p eapim-online
# eapim-online/* — 5 modules referenced from settings.gradle
for REPO in elink-online-core elink-online-core-jpa elink-online-transformer elink-online-common elink-online-emsclient; do
rm -rf "eapim-online/$REPO"
git clone --depth=1 --branch master \
http://oauth2:${{ secrets.CI_TOKEN }}@172.30.1.50:3000/djb-eapim/$REPO.git \
"eapim-online/$REPO"
done
# standalone siblings
for REPO in elink-portal-common eapim-admin-djb; do
rm -rf "$REPO"
git clone --depth=1 --branch master \
http://oauth2:${{ secrets.CI_TOKEN }}@172.30.1.50:3000/djb-eapim/$REPO.git \
"$REPO"
done
- name: Verify toolchain
run: |
java -version
gradle --version
- name: Gradle build (no tests)
run: gradle clean build -x test --no-daemon
- name: Upload WAR artifact
uses: http://172.30.1.50:3000/actions/upload-artifact@v3
with:
name: eapim-admin-war-${{ github.sha }}
path: eapim-admin/build/libs/eapim-admin.war
if-no-files-found: error
- name: Stop tomcat (pre-clean)
working-directory: ${{ github.workspace }}
run: |
# 1) systemd 관리 인스턴스 정지 (이번 워크플로가 띄운 것)
systemctl --user stop eapim-admin 2>/dev/null || true
# 2) systemd 외부 detached 인스턴스 정리
if [ -f "$CATALINA_PID" ] && kill -0 "$(cat $CATALINA_PID)" 2>/dev/null; then
JAVA_HOME=$JAVA_HOME_TOMCAT "$CATALINA_HOME/bin/shutdown.sh" 30 -force || true
for i in $(seq 1 30); do
[ -f "$CATALINA_PID" ] && kill -0 "$(cat $CATALINA_PID)" 2>/dev/null || break
sleep 1
done
fi
rm -f "$CATALINA_PID"
- name: Deploy war as /monitoring
working-directory: ${{ github.workspace }}
run: |
DEPLOY_DIR="$CATALINA_BASE/webapps"
rm -rf "$DEPLOY_DIR/monitoring" "$DEPLOY_DIR/monitoring.war"
cp eapim-admin/build/libs/eapim-admin.war "$DEPLOY_DIR/monitoring.war"
ls -la "$DEPLOY_DIR"
- name: Start tomcat and readiness probe (timeout 300s)
working-directory: ${{ github.workspace }}
run: |
LOG="$CATALINA_BASE/logs/catalina.$(date +%Y-%m-%d).log"
BEFORE=0
[ -f "$LOG" ] && BEFORE=$(stat -c %s "$LOG")
# systemd 가 Tomcat 을 새 cgroup 으로 띄움 — runner step 종료와 무관하게 살아남음
systemctl --user start eapim-admin
# /loginForm.do 는 GET 매핑(MainController#loginForm) → tomcat·webapp 양쪽 살아있으면 200
# /login.do 는 POST 처리 — GET 으로 probe 시 405/잘못된 응답 가능성 있어 회피
PROBE_URL="http://localhost:$DEPLOY_HTTP_PORT/monitoring/loginForm.do"
DEADLINE=$(($(date +%s) + 300))
STATUS=000
while [ $(date +%s) -lt $DEADLINE ]; do
STATUS=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 3 \
"$PROBE_URL" 2>/dev/null || echo "000")
case "$STATUS" in
200|302|401)
echo "Readiness OK ($PROBE_URL HTTP $STATUS)"
break
;;
esac
if [ -f "$LOG" ] && tail -c +$((BEFORE+1)) "$LOG" | grep -qE "Application listener .* Exception|SEVERE.*startup.Catalina"; then
echo "::error::Startup error detected in log"
tail -c +$((BEFORE+1)) "$LOG" | tail -80
exit 1
fi
sleep 2
done
case "$STATUS" in
200|302|401) ;;
*)
echo "::error::Readiness probe failed within 300s (last HTTP status: $STATUS)"
echo "--- last 300 lines of catalina log ---"
[ -f "$LOG" ] && tail -c +$((BEFORE+1)) "$LOG" | tail -300
echo "--- systemd unit status ---"
systemctl --user status eapim-admin --no-pager || true
echo "--- listening ports ---"
ss -tlnp 2>/dev/null | grep -E ":$DEPLOY_HTTP_PORT\b" || echo "(port $DEPLOY_HTTP_PORT 미점유)"
exit 1
;;
esac
echo "--- key boot log lines ---"
tail -c +$((BEFORE+1)) "$LOG" | grep -E "Server startup|Deployment of web" | head -10 || true
@@ -115,7 +115,7 @@
</bean> </bean>
<bean id="ioAcceptor" class="com.eactive.eai.rms.onl.server.UdpServer" init-method="init" destroy-method="destory"> <bean id="ioAcceptor" class="com.eactive.eai.rms.onl.server.UdpServer" init-method="init" destroy-method="destory">
<property name="port" value="10800" /> <property name="port" value="39126" />
<property name="handler" ref="handler"/> <property name="handler" ref="handler"/>
</bean> </bean>
+2 -2
View File
@@ -353,10 +353,10 @@
</div> </div>
<!-- SSO 로그인 버튼 (비상모드 시 숨김) --> <!-- SSO 로그인 버튼 (비상모드 시 숨김) -->
<c:if test="${not emergencyMode}"> <c:if test="${not emergencyMode}">
<div class="form-group"> <!-- <div class="form-group">
<input type="button" id="btn_sso_login" class="form-control btn btn-success rounded submit px-3" <input type="button" id="btn_sso_login" class="form-control btn btn-success rounded submit px-3"
value="SSO Login"> value="SSO Login">
</div> </div> -->
</c:if> </c:if>
<p style="color:red;"><%=resultMsg %></p> <p style="color:red;"><%=resultMsg %></p>
</form> </form>
+2 -2
View File
@@ -551,9 +551,9 @@
</h1><!-- /monitoring/images/top_logo.png --> </h1><!-- /monitoring/images/top_logo.png -->
<div class="topmenu_box"> <div class="topmenu_box">
<ul> <ul>
<li style="width:240px;" id="newDashboardShow"> <li style="width:240px;" id="newDashboardShow">
<a style="width:240px;" <a style="width:240px;"
href="#"><span><%=SessionManager.getUserName(request) %>(<%=SessionManager.getUserId(request) %>)</span> href="#"><span><%=SessionManager.getUserName(request) %>(<%=SessionManager.getUserId(request) %>-<%=System.getProperty("inst.Name") %>)</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%> [ <%=LastLoginIp%> ]</span> <span style="display:block;font-size:10px;"><%=localeMessage.getString("screen.lastLogin") %> : <%=LastLoginYms%> [ <%=LastLoginIp%> ]</span>
@@ -34,7 +34,7 @@
, {id:'LLLVAR',name:'[ISO8583] LLLVAR'}, {id:'LLLLVAR',name:'[ISO8583] LLLLVAR'}, {id:'BINARY',name:'[ISO8583] BINARY'}, {id:'LLBIN',name:'[ISO8583] LLBIN'} , {id:'LLLVAR',name:'[ISO8583] LLLVAR'}, {id:'LLLLVAR',name:'[ISO8583] LLLLVAR'}, {id:'BINARY',name:'[ISO8583] BINARY'}, {id:'LLBIN',name:'[ISO8583] LLBIN'}
, {id:'LLLBIN',name:'[ISO8583] LLLBIN'}]; , {id:'LLLBIN',name:'[ISO8583] LLLBIN'}];
const basicDataTypes = [{id:'BigDecimal',name:'BigDecimal'},{id:'String',name:'String'}]; const basicDataTypes = [{id:'BigDecimal',name:'BigDecimal'},{id:'String',name:'String'},{id:'Boolean',name:'Boolean'}];
function gridRenderingInit() { function gridRenderingInit() {
$('#effectGrid').jqGrid({ $('#effectGrid').jqGrid({
@@ -44,14 +44,16 @@
'<%=localeMessage.getString("stdDefaultMan.columDesc")%>', '<%=localeMessage.getString("stdDefaultMan.columDesc")%>',
'<%=localeMessage.getString("stdDefaultMan.columLen")%>', '<%=localeMessage.getString("stdDefaultMan.columLen")%>',
'<%=localeMessage.getString("stdDefaultMan.keyOr")%>', '<%=localeMessage.getString("stdDefaultMan.keyOr")%>',
'<%=localeMessage.getString("stdDefaultMan.ifOr")%>' '<%=localeMessage.getString("stdDefaultMan.ifOr")%>',
'<%=localeMessage.getString("stdDefaultMan.orderSeq")%>'
], ],
colModel:[ colModel:[
{ name : 'COLUMNNAME' , align:'left' , sortable:false }, { name : 'COLUMNNAME' , align:'left' , width: '200px', sortable:false },
{ name : 'COLUMNDESC' , align:'left' }, { name : 'COLUMNDESC' , align:'left' , width: '200px' },
{ name : 'COLUMNLENGTH' , align:'right' }, { name : 'COLUMNLENGTH' , align:'center' , width: '100px' },
{ name : 'ISKEY' , align:'center' }, { name : 'ISKEY' , align:'center', width: '100px' },
{ name : 'ISINTERFACE' , align:'center' } { name : 'ISINTERFACE' , align:'center', width: '100px' },
{ name : 'ORDERSEQ' , align:'center', width: '100px' }
], ],
jsonReader: { jsonReader: {
repeatitems:false repeatitems:false
@@ -19,10 +19,23 @@
<script language="javascript" > <script language="javascript" >
var url = '<c:url value="/onl/apim/portalnotice/portalNoticeMan.json" />'; var url = '<c:url value="/onl/apim/portalnotice/portalNoticeMan.json" />';
var url_view = '<c:url value="/onl/apim/portalnotice/portalNoticeMan.view" />'; var url_view = '<c:url value="/onl/apim/portalnotice/portalNoticeMan.view" />';
var apiPopupUrl = '<c:url value="/onl/apim/apigroup/apiGroupMan.view" />';
var file_download = '<c:url value="/file/download.do" />'; var file_download = '<c:url value="/file/download.do" />';
var isDetail = false; var isDetail = false;
var fileInfo = null; var fileInfo = null;
// 코드 상수 — PortalNoticeUI 와 동기화
var NOTICE_TYPE_NORMAL = '1';
var NOTICE_TYPE_MAINTENANCE = '2';
var NOTICE_TYPE_INCIDENT = '3';
// 영향 API 목록 (UI 상태)
var affectedApis = [];
function isIncidentType(t) {
return t === NOTICE_TYPE_INCIDENT || t === NOTICE_TYPE_MAINTENANCE;
}
function init() { function init() {
var key = "${param.id}"; var key = "${param.id}";
isDetail = key != "" && key != "null"; isDetail = key != "" && key != "null";
@@ -35,8 +48,7 @@
$("#btn_modify").html('<i class="material-icons">save</i> <%= localeMessage.getString("button.register") %>'); $("#btn_modify").html('<i class="material-icons">save</i> <%= localeMessage.getString("button.register") %>');
buttonControl(false); buttonControl(false);
} }
// 초기화 로직
$.ajax({ $.ajax({
type : "POST", type : "POST",
url:url, url:url,
@@ -45,7 +57,8 @@
success:function(json){ success:function(json){
combo = json; combo = json;
new makeOptions("CODE","NAME").setObj($("select[name=noticeType]")).setNoValueInclude(false).setData(json.noticeTypeList).rendering(); new makeOptions("CODE","NAME").setObj($("select[name=noticeType]")).setNoValueInclude(false).setData(json.noticeTypeList).rendering();
toggleIncidentFields();
if (key) { if (key) {
detail(key); detail(key);
} }
@@ -56,6 +69,22 @@
}); });
} }
function toggleIncidentFields() {
var t = $('#noticeType').val();
if (isIncidentType(t)) {
$('.incident-row').show();
// INCIDENT 만 상태 영역 표시
if (t === NOTICE_TYPE_INCIDENT) {
$('.incident-state-row').show();
} else {
$('.incident-state-row').hide();
}
} else {
$('.incident-row').hide();
$('.incident-state-row').hide();
}
}
function decodeHTMLEntities(text) { function decodeHTMLEntities(text) {
var textArea = document.createElement('textarea'); var textArea = document.createElement('textarea');
textArea.innerHTML = text; textArea.innerHTML = text;
@@ -73,57 +102,100 @@
function displayAttachFile(file) { function displayAttachFile(file) {
var $attachFiles = $('#attachFiles'); var $attachFiles = $('#attachFiles');
$attachFiles.empty(); $attachFiles.empty();
if (file) { if (file) {
var iconPath = '${pageContext.request.contextPath}/images/icon_file.png'; var iconPath = '${pageContext.request.contextPath}/images/icon_file.png';
var fileItem = $('<div>').addClass('file-item'); var fileItem = $('<div>').addClass('file-item');
$('<img>').attr('src', iconPath).css({'marginLeft': '15px','marginRight': '5px' ,'height': '1.2em'}).appendTo(fileItem); $('<img>').attr('src', iconPath).css({'marginLeft': '15px','marginRight': '5px' ,'height': '1.2em'}).appendTo(fileItem);
var fileNameSpan = $('<span>').addClass('fileName')
var fileNameSpan = $('<span>') .text(file.originalFileName + '.' + file.fileExtension + ' (' + formatFileSize(file.size) + ')')
.addClass('fileName') .css('cursor', 'pointer')
.text(file.originalFileName + '.' + file.fileExtension + ' (' + formatFileSize(file.size) + ')') .on('click', function() { if (file.fileId) downloadFile(file.fileId); });
.css('cursor', 'pointer')
.on('click', function() {
if (file.fileId) {
downloadFile(file.fileId);
}
});
fileNameSpan.appendTo(fileItem); fileNameSpan.appendTo(fileItem);
$('<button>').text('X').addClass('delete-file').css('marginLeft', '15px') $('<button>').text('X').addClass('delete-file').css('marginLeft', '15px')
.on('click', function() { .on('click', function() {
fileInfo = null; fileInfo = null;
displayAttachFile(null); displayAttachFile(null);
// 폼에 삭제 플래그 추가 $('<input>').attr({type: 'hidden', name: 'fileDeleted', value: 'true'}).appendTo('#ajaxForm');
$('<input>').attr({ }).appendTo(fileItem);
type: 'hidden',
name: 'fileDeleted',
value: 'true'
}).appendTo('#ajaxForm');
})
.appendTo(fileItem);
fileItem.appendTo($attachFiles); fileItem.appendTo($attachFiles);
} }
} }
function downloadFile(fileId) { function downloadFile(fileId) {
if (fileId) { if (fileId) {
var fileSn = 1; // 단일 파일이므로 시리얼 넘버를 1로 고정 window.location.href = file_download + '?fileId=' + fileId + '&fileSn=1';
window.location.href = file_download + '?fileId=' + fileId + '&fileSn=' + fileSn;
} else {
console.log("File not yet uploaded or doesn't have an ID");
} }
} }
function renderAffectedApis() {
var $tb = $('#affectedApiTbody');
$tb.empty();
if (!affectedApis.length) {
$tb.append('<tr><td colspan="3" style="text-align:center;color:#999;">선택된 영향 API가 없습니다.</td></tr>');
return;
}
$.each(affectedApis, function(idx, api) {
var $tr = $('<tr>');
$tr.append('<td style="text-align:center;width:40px;"><input type="checkbox" class="affected-api-check" data-idx="' + idx + '"></td>');
$tr.append('<td>' + (api.apiId || '') + '</td>');
$tr.append('<td>' + (api.apiName || '') + '</td>');
$tb.append($tr);
});
}
function openApiPopup() {
// showModal 헬퍼는 urlAddServiceType + &menuId=getMenuId() + &pop=true 를 자동 부여 →
// AuthorizeInterceptor 가 정상적으로 권한 인식. window.open 직접 호출 시 권한 부족 발생.
// 본 페이지는 summernote 본문 에디터 등 다른 iframe 이 함께 존재할 수 있어
// frames[0] 가정은 위험 → DOM 에서 .ui-dialog 내 iframe 중 src=apiGroupMan 인 것을 식별.
var selectedIds = affectedApis.map(function(a) { return a.apiId; }).join(',');
var url2 = apiPopupUrl + '?cmd=POPUP&selectedApiIds=' + encodeURIComponent(selectedIds);
var args = {};
showModal(url2, args, 1280, 860, function () {
var doc = args.self && args.self.document;
if (!doc) return;
var popupFrame = null;
var iframes = doc.querySelectorAll('iframe');
for (var i = 0; i < iframes.length; i++) {
var src = iframes[i].getAttribute('src') || '';
if (src.indexOf('apiGroupMan') >= 0) {
popupFrame = iframes[i].contentWindow;
break;
}
}
if (!popupFrame) return;
var rv = popupFrame.returnValue;
if (!rv) return;
var rows = Array.isArray(rv) ? rv : [rv];
$.each(rows, function(_, item) {
if (!item || !item.apiId) return;
var exists = affectedApis.some(function(a){ return a.apiId === item.apiId; });
if (!exists) {
affectedApis.push({apiId: item.apiId, apiName: item.apiDesc || item.apiName || ''});
}
});
renderAffectedApis();
});
}
function removeSelectedAffectedApis() {
var idxes = [];
$('.affected-api-check:checked').each(function() {
idxes.push(parseInt($(this).data('idx'), 10));
});
if (!idxes.length) {
alert('삭제할 항목을 선택해주세요.');
return;
}
// 역순 삭제
idxes.sort(function(a,b){return b-a;}).forEach(function(i){ affectedApis.splice(i, 1); });
renderAffectedApis();
}
function detail(key) { function detail(key) {
if (!isDetail) return; if (!isDetail) return;
$.ajax({ $.ajax({
type: "POST", type: "POST", url: url, dataType: "json",
url: url,
dataType: "json",
data: {cmd: 'DETAIL', id: key}, data: {cmd: 'DETAIL', id: key},
success: function (data) { success: function (data) {
$("#id").val(key); $("#id").val(key);
@@ -135,7 +207,18 @@
var decodedContent = decodeHTMLEntities(data.noticeDetail); var decodedContent = decodeHTMLEntities(data.noticeDetail);
$('#contents').summernote('code', decodedContent); $('#contents').summernote('code', decodedContent);
// 파일 정보 표시 // 장애/점검 필드
$('#summary').val(data.summary || '');
$('#startedAt').val(data.startedAt || '');
$('#endAt').val(data.endAt || '');
$('#state').val(data.state || 'INVESTIGATING');
$('#previousState').text(data.previousState || '없음');
affectedApis = (data.affectedApis || []).map(function(a){
return {apiId: a.apiId, apiName: a.apiName || ''};
});
renderAffectedApis();
toggleIncidentFields();
if (data.fileInfo) { if (data.fileInfo) {
fileInfo = { fileInfo = {
originalFileName: data.fileInfo.fileDetails[0].originalFileName, originalFileName: data.fileInfo.fileDetails[0].originalFileName,
@@ -146,24 +229,21 @@
displayAttachFile(fileInfo); displayAttachFile(fileInfo);
} }
}, },
error: function (e) { error: function (e) { alert(e.responseText); }
alert(e.responseText);
}
}); });
} }
$(document).ready(function () { $(document).ready(function () {
var returnUrl = getReturnUrlForReturn(); var returnUrl = getReturnUrlForReturn();
init(); init();
// Summernote 에디터 초기화 (이미지 붙여넣기 data-uri 방식, 리사이징 지원) initSummernote('#contents', { placeholder: '여기에 내용을 입력하세요.', height: 300 });
initSummernote('#contents', {
placeholder: '여기에 내용을 입력하세요.',
height: 300
});
$('#noticeType').on('change', toggleIncidentFields);
$('#btn_addAffectedApi').click(openApiPopup);
$('#btn_removeAffectedApi').click(removeSelectedAffectedApis);
$('#fileInput').change(function(e) { $('#fileInput').change(function(e) {
var file = e.target.files[0]; var file = e.target.files[0];
@@ -171,52 +251,66 @@
fileInfo = { fileInfo = {
originalFileName: file.name.split('.').slice(0, -1).join('.'), originalFileName: file.name.split('.').slice(0, -1).join('.'),
fileExtension: file.name.split('.').pop(), fileExtension: file.name.split('.').pop(),
file: file, file: file, size: file.size
size: file.size
}; };
displayAttachFile(fileInfo); displayAttachFile(fileInfo);
} }
this.value = ''; // 입력 필드 초기화 this.value = '';
});
$('#addFile').click(function() {
$('#fileInput').click();
}); });
$('#addFile').click(function() { $('#fileInput').click(); });
$("#btn_modify").click(function () { $("#btn_modify").click(function () {
if (!checkRequired("ajaxForm")) return; if (!checkRequired("ajaxForm")) return;
if (confirm("<%= localeMessage.getString("common.checkSave")%>") != true) var noticeType = $('#noticeType').val();
return; if (isIncidentType(noticeType)) {
if (!$('#startedAt').val()) {
alert('시작 시각을 입력해 주십시오.');
return;
}
if (noticeType === NOTICE_TYPE_MAINTENANCE && !$('#endAt').val()) {
alert('점검 종료 시각을 입력해 주십시오.');
return;
}
if (affectedApis.length === 0) {
alert('영향 API를 1건 이상 선택해 주십시오.');
return;
}
}
if (confirm("<%= localeMessage.getString("common.checkSave")%>") != true) return;
var formData = new FormData($("#ajaxForm")[0]); var formData = new FormData($("#ajaxForm")[0]);
formData.set("useYn", $("#useYn").is(":checked") ? "Y" : "N"); formData.set("useYn", $("#useYn").is(":checked") ? "Y" : "N");
formData.set("fixYn", $("#fixYn").is(":checked") ? "Y" : "N"); formData.set("fixYn", $("#fixYn").is(":checked") ? "Y" : "N");
formData.set("noticeDetail", $('#contents').summernote('code')); formData.set("noticeDetail", $('#contents').summernote('code'));
// 시작/종료 시각이 비어 있으면 전송하지 않음 (서버 파싱 오류 방지)
if (!$('#startedAt').val()) formData.delete('startedAt');
if (!$('#endAt').val()) formData.delete('endAt');
// 영향 API → affectedApis[i].apiId/apiName 으로 전송
$.each(affectedApis, function(i, api) {
formData.set('affectedApis[' + i + '].apiId', api.apiId);
formData.set('affectedApis[' + i + '].apiName', api.apiName || '');
});
formData.append("cmd", isDetail ? "UPDATE" : "INSERT"); formData.append("cmd", isDetail ? "UPDATE" : "INSERT");
// 파일 처리
if (fileInfo && fileInfo.file) { if (fileInfo && fileInfo.file) {
formData.set("files", fileInfo.file); formData.set("files", fileInfo.file);
formData.set("fileName", fileInfo.originalFileName + '.' + fileInfo.fileExtension); formData.set("fileName", fileInfo.originalFileName + '.' + fileInfo.fileExtension);
} }
$.ajax({ $.ajax({
type: "POST", type: "POST", url: url, data: formData,
url: url, processData: false, contentType: false,
data: formData, success: function () {
processData: false,
contentType: false,
success: function (json) {
alert("<%= localeMessage.getString("common.saveMsg") %>"); alert("<%= localeMessage.getString("common.saveMsg") %>");
goNav(returnUrl); goNav(returnUrl);
}, },
error: function (e) { error: function (e) { alert(e.responseText); }
alert(e.responseText);
}
}); });
}); });
@@ -224,25 +318,18 @@
if (confirm("<%= localeMessage.getString("common.confirmMsg")%>")) { if (confirm("<%= localeMessage.getString("common.confirmMsg")%>")) {
var postData = $('#ajaxForm').serializeArray(); var postData = $('#ajaxForm').serializeArray();
postData.push({name: "cmd", value: "DELETE"}); postData.push({name: "cmd", value: "DELETE"});
$.ajax({ $.ajax({
type: "POST", type: "POST", url: url, data: postData,
url: url, success: function () {
data: postData,
success: function (args) {
alert("<%= localeMessage.getString("common.deleteMsg") %>"); alert("<%= localeMessage.getString("common.deleteMsg") %>");
goNav(returnUrl); goNav(returnUrl);
}, },
error: function (e) { error: function (e) { alert(e.responseText); }
alert(e.responseText);
}
}); });
} }
}); });
$("#btn_previous").click(function () { $("#btn_previous").click(function () { goNav(returnUrl); });
goNav(returnUrl);
});
}); });
</script> </script>
</head> </head>
@@ -252,7 +339,7 @@
<ul class="path"> <ul class="path">
<li><a href="#">${rmsMenuPath}</a></li> <li><a href="#">${rmsMenuPath}</a></li>
</ul> </ul>
</div><!-- end content_top --> </div>
<div class="content_middle"> <div class="content_middle">
<div class="search_wrap"> <div class="search_wrap">
<button type="button" class="cssbtn" id="btn_modify" level="W" status="DETAIL,NEW"><i class="material-icons">save</i> <%= localeMessage.getString("button.modify") %></button> <button type="button" class="cssbtn" id="btn_modify" level="W" status="DETAIL,NEW"><i class="material-icons">save</i> <%= localeMessage.getString("button.modify") %></button>
@@ -270,9 +357,9 @@
<col style="width: 40%"/> <col style="width: 40%"/>
</colgroup> </colgroup>
<tr> <tr>
<th>게시유형</th> <th>게시유형 <font color="red">*</font></th>
<td colspan="3"> <td colspan="3">
<div class="select-style" > <div class="select-style">
<select name="noticeType" id="noticeType"></select> <select name="noticeType" id="noticeType"></select>
</div> </div>
</td> </td>
@@ -286,7 +373,7 @@
<td> <td>
<input type="checkbox" name="useYn" id="useYn" value="Y" ${portalNoticeUI.useYn eq 'Y' ? 'checked' : ''}> 사용 <input type="checkbox" name="useYn" id="useYn" value="Y" ${portalNoticeUI.useYn eq 'Y' ? 'checked' : ''}> 사용
</td> </td>
<th>고정여부</th> <th>상단고정</th>
<td> <td>
<input type="checkbox" name="fixYn" id="fixYn" value="Y" ${portalNoticeUI.fixYn eq 'Y' ? 'checked' : ''}> 고정 <input type="checkbox" name="fixYn" id="fixYn" value="Y" ${portalNoticeUI.fixYn eq 'Y' ? 'checked' : ''}> 고정
</td> </td>
@@ -297,19 +384,58 @@
<textarea id="contents" name="noticeDetail" style="width:100%;height:300px" data-required data-warning="본문을 입력하여 주십시오."></textarea> <textarea id="contents" name="noticeDetail" style="width:100%;height:300px" data-required data-warning="본문을 입력하여 주십시오."></textarea>
</td> </td>
</tr> </tr>
<!-- <tr>
<th>첨부파일</th> <!-- ───── 장애/점검 전용 영역 ───── -->
<td colspan="3"> <tr class="incident-row" style="display:none;">
<div style="margin: 5px 0px; display: inline-block;"> <th>시작 <font color="red">*</font></th>
<input type="file" id="fileInput" name="files" style="display:none;" /> <td>
<button type="button" id="addFile" class="cssbtn smallBtn">파일 선택</button> <input type="datetime-local" id="startedAt" name="startedAt" style="width:90%">
</div>
<div id="attachFiles" style="margin: 5px 0px; display: inline-block;"></div>
</td> </td>
</tr> --> <th>종료</th>
<td>
<input type="datetime-local" id="endAt" name="endAt" style="width:90%" placeholder="해소시 자동 채움">
</td>
</tr>
<tr class="incident-row incident-state-row" style="display:none;">
<th>상태</th>
<td>
<div class="select-style">
<select id="state" name="state">
<option value="INVESTIGATING">INVESTIGATING - 조사중</option>
<option value="IDENTIFIED">IDENTIFIED - 원인 파악</option>
<option value="MONITORING">MONITORING - 모니터링</option>
<option value="RESOLVED">RESOLVED - 해소</option>
<option value="CANCELED">CANCELED - 취소</option>
</select>
</div>
</td>
<th>이전 상태</th>
<td><span id="previousState">없음</span></td>
</tr>
<tr class="incident-row" style="display:none;">
<th>영향 API 선택</th>
<td colspan="3">
<div style="margin-bottom:5px;">
<button type="button" class="cssbtn smallBtn" id="btn_removeAffectedApi"><i class="material-icons">delete</i> 삭제</button>
<button type="button" class="cssbtn smallBtn" id="btn_addAffectedApi"><i class="material-icons">add</i> 추가</button>
</div>
<table class="table_row" cellspacing="0" style="width:100%;">
<thead>
<tr>
<th style="width:40px;text-align:center;"><input type="checkbox" id="affectedApiAll" onclick="$('.affected-api-check').prop('checked', this.checked);"></th>
<th>API ID</th>
<th>API 명</th>
</tr>
</thead>
<tbody id="affectedApiTbody">
<tr><td colspan="3" style="text-align:center;color:#999;">선택된 영향 API가 없습니다.</td></tr>
</tbody>
</table>
</td>
</tr>
</table> </table>
</form> </form>
</div> </div>
</div> </div>
</body> </body>
</html> </html>
@@ -1686,7 +1686,7 @@
<option value="none" selected>NONE</option> <option value="none" selected>NONE</option>
<option value="oauth">OAUTH</option> <option value="oauth">OAUTH</option>
<option value="api_key">API_KEY</option> <option value="api_key">API_KEY</option>
<option value="ca">CA</option> <!-- <option value="ca">CA</option> -->
</select> </select>
</div> </div>
</div> </div>
+51
View File
@@ -0,0 +1,51 @@
# DJBank EAPIM Admin — user-scope systemd unit
#
# Install (one-time, on the host as `djb`):
# sudo loginctl enable-linger djb # allow services to outlive ssh logout
# mkdir -p ~/.config/systemd/user
# # workflow copies this file in on each deploy; for first boot you can also do:
# # cp /prod/.../deploy/systemd/eapim-admin.service ~/.config/systemd/user/
# systemctl --user daemon-reload
# systemctl --user enable eapim-admin
# systemctl --user start eapim-admin
#
# Why this exists:
# - `catalina.sh start` + `nohup &` does NOT escape the Gitea Actions runner
# process group. When the workflow step finishes, the runner sends SIGTERM
# to the process tree and Tomcat dies seconds after boot.
# - Running Tomcat as a systemd user service detaches lifecycle from the
# runner entirely. The workflow just calls `systemctl --user restart` and
# systemd owns the process.
[Unit]
Description=DJBank EAPIM Admin (Tomcat 9)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
Environment=JAVA_HOME=/apps/opts/jdk17
Environment=CATALINA_BASE=/prod/eapim/adminportal
Environment=CATALINA_HOME=/prod/eapim/apache-tomcat-9.0.116
Environment=CATALINA_PID=/prod/eapim/adminportal/temp/catalina.pid
# `catalina.sh run` keeps Tomcat in the foreground so systemd can supervise it.
ExecStart=/prod/eapim/apache-tomcat-9.0.116/bin/catalina.sh run
# Graceful shutdown via Tomcat's shutdown port, 30s window.
ExecStop=/prod/eapim/apache-tomcat-9.0.116/bin/catalina.sh stop 30
# Tomcat returns 143 on SIGTERM during normal stop — treat as success.
SuccessExitStatus=143
TimeoutStartSec=180
TimeoutStopSec=60
Restart=on-failure
RestartSec=10
# Memory / fd limits — adjust as needed.
LimitNOFILE=65535
[Install]
WantedBy=default.target
@@ -35,6 +35,7 @@ public class MonitoringPropertyManService extends BaseService {
public MonitoringPropertyGroupUI selectDetail(String prptyGroupName) { public MonitoringPropertyGroupUI selectDetail(String prptyGroupName) {
MonitoringPropertyGroup propertyGroup = propertyGroupService.getById(prptyGroupName); MonitoringPropertyGroup propertyGroup = propertyGroupService.getById(prptyGroupName);
propertyGroup.getMonitoringProperties().sort(Comparator.comparing(p -> p.getId().getPrptyName()));
return propertyGroupMapper.toVo(propertyGroup); return propertyGroupMapper.toVo(propertyGroup);
} }
@@ -5,7 +5,9 @@ import java.util.List;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -42,7 +44,12 @@ public class ExtendedColumnDefinitionService
predicate = predicate.and(qExtendedColumnDefinition.columnDesc.containsIgnoreCase(searchColumnDesc)); predicate = predicate.and(qExtendedColumnDefinition.columnDesc.containsIgnoreCase(searchColumnDesc));
} }
return repository.findAll(predicate, pageable); Pageable orderedPageable = PageRequest.of(
pageable.getPageNumber(),
pageable.getPageSize(),
Sort.by(Sort.Order.asc("orderSeq"), Sort.Order.asc("columnName")));
return repository.findAll(predicate, orderedPageable);
} }
} }
@@ -15,7 +15,7 @@ import com.eactive.apim.portal.template.entity.MessageCode;
import com.eactive.eai.rms.data.entity.man.role.Role; import com.eactive.eai.rms.data.entity.man.role.Role;
import com.eactive.eai.rms.data.entity.onl.djb.apistatus.ApiStatus; import com.eactive.eai.rms.data.entity.onl.djb.apistatus.ApiStatus;
import com.eactive.eai.rms.data.entity.onl.djb.apistatus.ApiStatusEvent; import com.eactive.eai.rms.data.entity.onl.djb.apistatus.ApiStatusEvent;
import com.eactive.eai.rms.ext.djb.util.UmsManager; import com.eactive.eai.rms.ext.djb.ums.UmsManager;
@Service @Service
@@ -11,7 +11,7 @@ import org.springframework.transaction.annotation.Transactional;
import com.eactive.apim.portal.template.entity.MessageCode; import com.eactive.apim.portal.template.entity.MessageCode;
import com.eactive.eai.rms.data.entity.man.role.Role; import com.eactive.eai.rms.data.entity.man.role.Role;
import com.eactive.eai.rms.data.entity.onl.djb.inflow.InflowTokenInsufficient; import com.eactive.eai.rms.data.entity.onl.djb.inflow.InflowTokenInsufficient;
import com.eactive.eai.rms.ext.djb.util.UmsManager; import com.eactive.eai.rms.ext.djb.ums.UmsManager;
@Service @Service
@@ -1,4 +1,4 @@
package com.eactive.eai.rms.ext.djb.util; package com.eactive.eai.rms.ext.djb.ums;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@@ -0,0 +1,43 @@
package com.eactive.eai.rms.ext.djb.ums.event;
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Component;
import com.eactive.apim.portal.template.entity.MessageCode;
import com.eactive.apim.portal.template.service.MessageEventHandler;
import com.eactive.apim.portal.template.service.MessageRecipient;
import com.eactive.apim.portal.template.service.MessageSendEvent;
@Component
public class ApiStatusChangedEventHandler implements MessageEventHandler {
@Override
public MessageCode getKey() {
return MessageCode.API_STATUS_CHANGED;
}
@Override
public boolean allowAdditionalRecipients() {
return true;
}
@Override
public String getDisplayName() {
return "API 상태 변화";
}
@Override
public String getDescription() {
return "<div>사용 가능 변수: </div><br/>"
+ "<div> - message 알림 메시지 </div><br/>";
}
@Override
public MessageSendEvent createEvent(Object source, MessageRecipient recipient, Map<String, Object> params) {
Map<String, String> eventParams = new HashMap<>();
eventParams.put("message", (String) params.get("message"));
return new MessageSendEvent(source, MessageCode.API_STATUS_CHANGED, recipient, eventParams);
}
}
@@ -0,0 +1,43 @@
package com.eactive.eai.rms.ext.djb.ums.event;
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Component;
import com.eactive.apim.portal.template.entity.MessageCode;
import com.eactive.apim.portal.template.service.MessageEventHandler;
import com.eactive.apim.portal.template.service.MessageRecipient;
import com.eactive.apim.portal.template.service.MessageSendEvent;
@Component
public class InflowTokenFailedEventHandler implements MessageEventHandler {
@Override
public MessageCode getKey() {
return MessageCode.INFLOW_TOKEN_FAILED;
}
@Override
public boolean allowAdditionalRecipients() {
return true;
}
@Override
public String getDisplayName() {
return "유량제어 토큰 획득 실패";
}
@Override
public String getDescription() {
return "<div>사용 가능 변수: </div><br/>"
+ "<div> - message 알림 메시지 </div><br/>";
}
@Override
public MessageSendEvent createEvent(Object source, MessageRecipient recipient, Map<String, Object> params) {
Map<String, String> eventParams = new HashMap<>();
eventParams.put("message", (String) params.get("message"));
return new MessageSendEvent(source, MessageCode.INFLOW_TOKEN_FAILED, recipient, eventParams);
}
}
@@ -0,0 +1,344 @@
package com.eactive.eai.rms.ext.djb.ums.service;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.Charset;
import java.security.SecureRandom;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Service;
import com.eactive.apim.portal.portalproperty.service.PortalPropertyService;
import com.eactive.apim.portal.template.entity.MessageRequest;
import com.eactive.eai.common.util.SystemUtil;
import com.eactive.eai.rms.data.entity.man.monitoringProperty.service.MonitoringPropertyService;
import com.eactive.eai.rms.ext.djb.ums.vo.EmailDataVO;
import com.eactive.eai.rms.ext.djb.ums.vo.SmsDataVO;
import com.eactive.eai.rms.ext.djb.ums.vo.StdHeadVO;
import com.eactive.eai.rms.ext.djb.ums.vo.SwingCommonVO;
import com.eactive.eai.rms.ext.djb.ums.vo.SwingDataVO;
import com.eactive.eai.rms.ext.djb.ums.vo.SwingReceiverDataVO;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
public class UmsService {
private static final Gson gson = new Gson();
private static final String SYS_DVCD = "OPA";
private final MonitoringPropertyService propertyService;
public UmsService(MonitoringPropertyService propertyService) {
this.propertyService = propertyService;
}
public boolean send(MessageRequest messageRequest) throws Exception {
if (messageRequest == null) {
throw new IllegalArgumentException("messageRequest is null");
}
this.httpConnection(messageRequest);
return true;
}
private void httpConnection(MessageRequest messageRequest) throws Exception {
HttpURLConnection conn = null;
BufferedReader br = null;
log.debug("<<<HTTP Connection>>>:" + messageRequest.toString());
try {
String host = propertyService.getPropertyValue("Monitoring", "djb.ums."+messageRequest.getMessageType().toLowerCase()+".url", "");
URL url = new URL(host);
int timeoutValue = 5 * 1000; // 타임아웃 설정을 위한 값 (단위: ms)
// Charset charset = Charset.forName("UTF-8");
Charset charset = Charset.forName("MS949");
StringBuffer sb = null;
String bodyData = this.makeBodyData(messageRequest);
String responseData = "";
String method = bodyData.isEmpty() ? "GET" : "POST";
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod(method); // 전송방식
// conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
conn.setRequestProperty("Content-Type", "application/json; charset=MS949");
conn.setConnectTimeout(timeoutValue); // 연결 타임아웃 설정(5초)
conn.setReadTimeout(timeoutValue); // 읽기 타임아웃 설정(5초)
conn.setDoOutput(true);
// POst 방식인 경우에만
if (method.equals("POST")) {
OutputStream os = conn.getOutputStream();
byte requestData[] = bodyData.getBytes(charset);
os.write(requestData);
os.close();
}
if (log.isDebugEnabled()) {
log.debug("getContentType();" + conn.getContentType()); // 응답 콘텐츠 유형 구하기
log.debug("getResponsecode();" + conn.getResponseCode()); // 응답 코드 구하기
log.debug("getResponseMessage():" + conn.getResponseMessage()); // 응답 메세지 구하기
}
// http 요청 후 응답 받은 데이타를 버퍼에 쌓는다
if (conn.getResponseCode() >= 200 && conn.getResponseCode() <= 300) {
br = new BufferedReader(new InputStreamReader(conn.getInputStream(), charset));
} else {
br = new BufferedReader(new InputStreamReader(conn.getErrorStream(), charset));
}
String inputLine;
sb = new StringBuffer();
while ((inputLine = br.readLine()) != null) {
sb.append(inputLine);
}
responseData = sb.toString();
} finally {
// http 요청 및 응답 완료 후 BufferedReader를 닫는다
if (br != null) {
br.close();
}
if (conn != null) {
conn.disconnect();
}
}
}
private String makeBodyData(MessageRequest messageRequest) {
// 리턴값
String rtnVal = "";
// guID 및 날짜 시간 정보
String jsonData[] = this.makeStdGuidInfo();
// 표준 헤더
StdHeadVO stdHeadVO = new StdHeadVO();
stdHeadVO.setNxgn_stnd_idfr("JERA");
stdHeadVO.setGuid(jsonData[2]);
stdHeadVO.setGuid_prgs_no("1");
stdHeadVO.setStnd_mesg_ver("R10");
stdHeadVO.setOrtr_guid(jsonData[2]);
stdHeadVO.setSect_ecrpt_yn("N");
stdHeadVO.setFrst_trnm_ipad(propertyService.getPropertyValue("Monitoring", "djb.ums.was_ip_address", ""));
stdHeadVO.setFrst_trnm_ipv4_addr(propertyService.getPropertyValue("Monitoring", "djb.ums.was_ip_address", ""));
stdHeadVO.setFrst_trnm_mac(propertyService.getPropertyValue("Monitoring", "djb.ums.was_mac_address", ""));
stdHeadVO.setFrst_mesg_dman_dt(jsonData[0]);
stdHeadVO.setFrst_mesg_dman_time(jsonData[1]);
stdHeadVO.setFrst_trnm_sys_dvcd(SYS_DVCD);
stdHeadVO.setTrnm_sys_dvcd(SYS_DVCD);
stdHeadVO.setDman_rspn_dvcd("S"); // 요청응답구분코드 S:요청 R:응답
stdHeadVO.setSys_env_dvcd(SystemUtil.getSysOperEvirnDstcd()); // D:개발 T:테스트 P:운영
stdHeadVO.setTx_tycd("O"); // O:온라인 B:배치
stdHeadVO.setSynz_dvcd("S"); // S:동기 A:비동기
stdHeadVO.setChnl_tycd(""); // 채널유형코드
stdHeadVO.setHmab_dvcd("1"); // 내외구분코드 1:내부 2:외부(타발)
stdHeadVO.setIf_id(messageRequest.getEaiInterfaceId()); // 인터페이스ID
stdHeadVO.setTx_id(messageRequest.getServiceId()); // 거래ID
if ("MESSENGER".equals(messageRequest.getMessageType()) ) {
// 스윙챗 알림 공용
SwingCommonVO swiComVO = new SwingCommonVO();
swiComVO.setClientld(propertyService.getPropertyValue("Monitoring", "djb.ums.messenger.client_id", ""));
swiComVO.setClientSecret(propertyService.getPropertyValue("Monitoring", "djb.ums.messenger.client_secret", ""));
swiComVO.setCompanyCode("CB");
swiComVO.setRequestUniqueKey(this.genrateRandomStringNumber26());
// 스윙챗 알림 데이터
SwingDataVO swiDataVO = new SwingDataVO();
swiDataVO.setNotiCode("CNO_FEP_HMP_0002");
swiDataVO.setNotiTitle("알림");
swiDataVO.setNotiContent(messageRequest.getMessage());
// 스윙챗 알림 데이터
List<SwingReceiverDataVO> swiReDataVOList = new ArrayList<SwingReceiverDataVO>();
SwingReceiverDataVO receiver = new SwingReceiverDataVO();
receiver.setCode(messageRequest.getMessengerId());
swiReDataVOList.add(receiver);
swiDataVO.setReceiverInfo(swiReDataVOList);
String header = gson.toJson(stdHeadVO);
String common = gson.toJson(swiComVO);
String data = gson.toJson(swiDataVO);
// 헤더 + 데이터
rtnVal = "{\"HEAD\":" + header + " ,\r\n" + "\"DATA\":\r\n" + " [" + "{\r\n"
+ " \"data_dvcd\":\"IO\", \r\n" + " \"BIZ_DATA\":\r\n" + " {\r\n"
+ " \"common\":\r\n" + common + ",\r\n" + " \"data\":" + data
+ " }\r\n" + " }\r\n" + " ]\r\n" + "}";
}
else if ("SMS".equals(messageRequest.getMessageType())
|| "KAKAO".equals(messageRequest.getMessageType())) {
// Sms 데이터
SmsDataVO dataVO = new SmsDataVO();
dataVO.setSnd_dman_dt(jsonData[0]);
dataVO.setSnd_dman_id("");
dataVO.setSnd_dvcd("EAI");
dataVO.setMsg_titl("");
dataVO.setMsg_ctnt(messageRequest.getMessage());
dataVO.setRecvr_no(messageRequest.getPhone());
dataVO.setSndn_no("15880079"); //발신번호
dataVO.setSnd_empno(""); //요청자
dataVO.setSnd_emp_dprm_cd(""); //요청부서코드
if ("SMS".equals(messageRequest.getMessageType())) {
dataVO.setLtrs_snd_tycd("1"); // 1:SMS 2:LMS/MMS
dataVO.setMsg_snd_tycd("0000"); // 알림톡 메세지 종류 0000:SMS, 1000:LMS, 1008:알림톡, ....
} else if ("KAKAO".equals(messageRequest.getMessageType())) {
dataVO.setLtrs_snd_tycd("2"); // 1:SMS 2:LMS/MMS
dataVO.setMsg_snd_tycd("1008"); // 알림톡 메세지 종류 0000:SMS, 1000:LMS, 1008:알림톡, ....
dataVO.setAlmtk_snd_prfl_key(""); // 플러스친구아이디
dataVO.setAlmtk_tmplt_cd(""); // 템플릿코드
dataVO.setAlmtk_err_ltrs_snd_yn("Y"); // 오류자문자발송여부
dataVO.setAlmtk_btn_trnm_tycd("2"); // 카카오버튼전송방식 1-format string 2-JSON 3-XML
}
dataVO.setSms_trnm_bzwk_dvcd(""); //업무구분코드
dataVO.setSms_bzwk_dcls_dvcd(""); //업무세분코드
dataVO.setSms_sys_dvcd("83"); //시스템코드
dataVO.setTx_dt(jsonData[0]); //거래일자
dataVO.setTx_tktm(jsonData[1].substring(0,6)); //거래시간
List<SmsDataVO> dataList = new ArrayList<SmsDataVO>();
dataList.add(dataVO);
String header = gson.toJson(stdHeadVO);
String data = gson.toJson(dataList);
// 헤더 + 데이터
rtnVal = "{\"HEAD\":" + header + " ,\r\n" + "\"DATA\":\r\n" + " [" + "{\r\n"
+ " \"data_dvcd\":\"IO\", \r\n" + " \"BIZ_DATA\":\r\n" + " {\r\n"
+ " \"ums_if_data_ncnt\":1, \r\n"
+ " \"ums_if_data\":" + data
+ " }\r\n" + " }\r\n" + " ]\r\n" + "}";
}
else if ("EMAIL".equals(messageRequest.getMessageType())) {
// 이메일 데이터
EmailDataVO dataVO = new EmailDataVO();
dataVO.setUms_evnt_id(""); // UMS이벤트ID(필수)
dataVO.setSnd_dman_dt(jsonData[0]);
dataVO.setSnd_dman_id("");
dataVO.setSnd_dman_sno("1");
dataVO.setEmail_titl(messageRequest.getSubject()); // 메일제목
dataVO.setCstno(""); // 고객번호(필수)
dataVO.setSendr_emaddr(""); // 발신자 이메일
dataVO.setSendr_nm(""); // 발신자 이름
dataVO.setSndbk_emaddr(""); // 리턴 이메일
dataVO.setRecvr_emaddr(messageRequest.getEmail()); // 수신자 이메일(필수)
dataVO.setRecvr_nm(messageRequest.getUsername()); // 수신자명(필수)
dataVO.setTmplt_mpng_info(messageRequest.getMessage()); // 탬플릿매핑정보
dataVO.setTmplt_mpng_rptt_info(""); // 탬플릿매핑반복정보
dataVO.setSnd_empno(""); // 발신자ID(필수)
dataVO.setSnd_emp_dprm_cd(""); // 발신자부서코드(필수)
dataVO.setBzwk_cd(""); // 업무구분코드
dataVO.setSub_bzwk_cd(""); // 업무세부코드
dataVO.setSys_dvcd(SYS_DVCD);
dataVO.setTx_dt(jsonData[0]); //거래일자
dataVO.setTx_tktm(jsonData[1].substring(0,6)); //거래시간
List<EmailDataVO> dataList = new ArrayList<EmailDataVO>();
dataList.add(dataVO);
String header = gson.toJson(stdHeadVO);
String data = gson.toJson(dataList);
// 헤더 + 데이터
rtnVal = "{\"HEAD\":" + header + " ,\r\n" + "\"DATA\":\r\n" + " [" + "{\r\n"
+ " \"data_dvcd\":\"IO\", \r\n" + " \"BIZ_DATA\":\r\n" + " {\r\n"
+ " \"ums_if_data_ncnt\":1, \r\n"
+ " \"ums_if_data\":" + data
+ " }\r\n" + " }\r\n" + " ]\r\n" + "}";
}
log.debug(rtnVal);
return rtnVal;
}
private String[] makeStdGuidInfo() {
// 리턴값
String rtnVal[] = new String[3];
// 현재시간
Date currentDate = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
String formattedDate = sdf.format(currentDate);
rtnVal[0] = formattedDate.substring(0,8);
rtnVal[1] = formattedDate.substring(8);
// 랜던값
String random20 = this.genrateRandomStringNumber20();
rtnVal[2] = formattedDate + SYS_DVCD + random20;
return rtnVal;
}
private String genrateRandomStringNumber20() {
String rtnVal= "";
StringBuilder buffer= new StringBuilder(10);
// 랜덤값
String characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
// 랜덤객체 생성
SecureRandom random = new SecureRandom();
// 반복문
for (int i= 0;i< 10;i++) {
// 랜덤수
int randomIndex = random.nextInt(characters.length());
// 생성된 랜덤값
char randomChar = characters.charAt(randomIndex);
// 랜덤값 저장
buffer.append(randomChar);
}
// 랜덤객체 재생성
random = new SecureRandom();
// 난수 10개 생성
int random10 = (int) (Math.pow(10, 9) + random.nextDouble() * Math.pow(10, 9));
// 문자 숫자 조합(10) + 숫자(10)
rtnVal = buffer.toString() + Integer.toString(random10);
//리턴
return rtnVal;
}
private String genrateRandomStringNumber26() {
StringBuilder buffer = new StringBuilder(26);
String characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ0123456789";
SecureRandom random = new SecureRandom();
for (int i = 0; i < 26; i++) {
buffer.append(characters.charAt(random.nextInt(characters.length())));
}
return buffer.toString();
}
}
@@ -0,0 +1,67 @@
package com.eactive.eai.rms.ext.djb.ums.vo;
import lombok.Data;
@Data
public class EmailDataVO {
/** UMS이벤트ID */
private String ums_evnt_id;
/** 발송요청일자 */
private String snd_dman_dt;
/** UUID */
private String snd_dman_id;
/** 발송요청순번 */
private String snd_dman_sno;
/** 메일제목 */
private String email_titl;
/** 고객번호 */
private String cstno;
/** 발신자 이메일 */
private String sendr_emaddr;
/** 발신자 이름 */
private String sendr_nm;
/** 리턴 이메일 */
private String sndbk_emaddr;
/** 수신자 이메일 */
private String recvr_emaddr;
/** 수신자명 */
private String recvr_nm;
/** 탬플릿매핑정보 */
private String tmplt_mpng_info;
/** 탬플릿매핑반복정보 */
private String tmplt_mpng_rptt_info;
/** 발신자ID */
private String snd_empno;
/** 발신자부서코드 */
private String snd_emp_dprm_cd;
/** 업무구분코드 */
private String bzwk_cd;
/** 업무세부코드 */
private String sub_bzwk_cd;
/** 시스템코드 */
private String sys_dvcd;
/** 거래일자 */
private String tx_dt;
/** 거래시간 */
private String tx_tktm;
}
@@ -0,0 +1,67 @@
package com.eactive.eai.rms.ext.djb.ums.vo;
import lombok.Data;
@Data
public class SmsDataVO {
/** 발송요청일자 */
private String snd_dman_dt;
/** UUID */
private String snd_dman_id;
/** 발송구분 */
private String snd_dvcd;
/** 발송타입 */
private String ltrs_snd_tycd;
/** LMS 제목 */
private String msg_titl;
/** 메세지 */
private String msg_ctnt;
/** 수신자번호 */
private String recvr_no;
/** 발신번호 */
private String sndn_no;
/** 요청자(직원번호) */
private String snd_empno;
/** 요청부서코드 */
private String snd_emp_dprm_cd;
/** 알림톡 메시지 종류 */
private String msg_snd_tycd;
/** 플러스친구아이디 */
private String almtk_snd_prfl_key;
/** 템플릿코드 */
private String almtk_tmplt_cd;
/** 오류자문자발송여부 */
private String almtk_err_ltrs_snd_yn;
/** 카카오톡전송방식 */
private String almtk_btn_trnm_tycd;
/** 업무구분 코드 */
private String sms_trnm_bzwk_dvcd;
/** 업무세부코드 */
private String sms_bzwk_dcls_dvcd;
/** 시스템코드 */
private String sms_sys_dvcd;
/** 거래일자 */
private String tx_dt;
/** 거래시간 */
private String tx_tktm;
}
@@ -0,0 +1,61 @@
package com.eactive.eai.rms.ext.djb.ums.vo;
import lombok.Data;
@Data
public class StdHeadVO {
private String nxgn_stnd_idfr;
private String guid;
private String guid_prgs_no;
private String stnd_mesg_ver;
private String ortr_guid;
private String sect_ecrpt_yn;
private String frst_trnm_ipad;
private String frst_trnm_ipv4_addr;
private String frst_trnm_mac;
private String frst_mesg_dman_dt;
private String frst_mesg_dman_time;
private String frst_trnm_sys_dvcd;
private String trnm_sys_dvcd;
private String dman_rspn_dvcd;
private String sys_env_dvcd;
private String tx_tycd;
private String synz_dvcd;
private String tx_id;
private String osdch_dvcd;
private String chnl_tycd;
private String if_id;
private String hmab_dvcd;
private String tx_brcd;
private String tlrno;
private String insl_brcd;
private String blng_brcd;
private String mgmt_brcd;
}
@@ -0,0 +1,19 @@
package com.eactive.eai.rms.ext.djb.ums.vo;
import lombok.Data;
@Data
public class SwingCommonVO {
/** 클라이업트 D */
private String clientld;
/** 클라이언트 시크릿 */
private String clientSecret;
/** 회사코드 */
private String companyCode;
/** 요청고유키 */
private String requestUniqueKey;
}
@@ -0,0 +1,21 @@
package com.eactive.eai.rms.ext.djb.ums.vo;
import java.util.List;
import lombok.Data;
@Data
public class SwingDataVO {
/** 알림코드 */
private String notiCode;
/** 알림제목 */
private String notiTitle;
/** 알림내용 */
private String notiContent;
/** 수신자정보 */
private List<SwingReceiverDataVO> receiverInfo;
}
@@ -0,0 +1,22 @@
package com.eactive.eai.rms.ext.djb.ums.vo;
import lombok.Data;
@Data
public class SwingReceiverDataVO {
/** 알림코드 */
private String name;
/** 알림제목 */
private String type;
/** 알림내용 */
private String companyCode;
/** 수신자정보 */
private String code;
/** 상품명 */
private String prdNm;
}
@@ -0,0 +1,11 @@
package com.eactive.eai.rms.onl.apim.portalnotice;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class IncidentAffectedApiUI {
private String apiId;
private String apiName;
}
@@ -1,5 +1,13 @@
package com.eactive.eai.rms.onl.apim.portalnotice; package com.eactive.eai.rms.onl.apim.portalnotice;
import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncident;
import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncidentApi;
import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncidentApiId;
import com.eactive.apim.portal.djb.apistatus.incident.entity.IncidentKind;
import com.eactive.apim.portal.djb.apistatus.incident.entity.IncidentState;
import com.eactive.apim.portal.djb.apistatus.incident.repository.DjbApistatusIncidentApiRepository;
import com.eactive.apim.portal.djb.apistatus.incident.repository.DjbApistatusIncidentRepository;
import com.eactive.apim.portal.djb.apistatus.incident.repository.DjbApistatusIncidentTimelineRepository;
import com.eactive.apim.portal.file.entity.FileDetail; import com.eactive.apim.portal.file.entity.FileDetail;
import com.eactive.apim.portal.file.entity.FileInfo; import com.eactive.apim.portal.file.entity.FileInfo;
import com.eactive.apim.portal.file.service.FileService; import com.eactive.apim.portal.file.service.FileService;
@@ -23,7 +31,12 @@ import java.io.IOException;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
@Service @Service
@Transactional(transactionManager = "transactionManagerForEMS") @Transactional(transactionManager = "transactionManagerForEMS")
@@ -32,6 +45,9 @@ public class PortalNoticeManService extends BaseService {
private final PortalNoticeService portalNoticeService; private final PortalNoticeService portalNoticeService;
private final PortalNoticeUIMapper portalNoticeUIMapper; private final PortalNoticeUIMapper portalNoticeUIMapper;
private final FileService fileService; private final FileService fileService;
private final DjbApistatusIncidentRepository incidentRepository;
private final DjbApistatusIncidentApiRepository incidentApiRepository;
private final DjbApistatusIncidentTimelineRepository incidentTimelineRepository;
private String decodeString(String value) { private String decodeString(String value) {
if (ContainerUtil.get() == ContainerUtil.TOMCAT) { if (ContainerUtil.get() == ContainerUtil.TOMCAT) {
@@ -48,10 +64,27 @@ public class PortalNoticeManService extends BaseService {
@Autowired @Autowired
public PortalNoticeManService(PortalNoticeService portalNoticeService, public PortalNoticeManService(PortalNoticeService portalNoticeService,
PortalNoticeUIMapper portalNoticeUIMapper, PortalNoticeUIMapper portalNoticeUIMapper,
FileService fileService){ FileService fileService,
DjbApistatusIncidentRepository incidentRepository,
DjbApistatusIncidentApiRepository incidentApiRepository,
DjbApistatusIncidentTimelineRepository incidentTimelineRepository){
this.portalNoticeService = portalNoticeService; this.portalNoticeService = portalNoticeService;
this.portalNoticeUIMapper = portalNoticeUIMapper; this.portalNoticeUIMapper = portalNoticeUIMapper;
this.fileService = fileService; this.fileService = fileService;
this.incidentRepository = incidentRepository;
this.incidentApiRepository = incidentApiRepository;
this.incidentTimelineRepository = incidentTimelineRepository;
}
private static boolean isIncidentKind(String noticeType) {
return PortalNoticeUI.NOTICE_TYPE_INCIDENT.equals(noticeType)
|| PortalNoticeUI.NOTICE_TYPE_MAINTENANCE.equals(noticeType);
}
private static IncidentKind toKind(String noticeType) {
if (PortalNoticeUI.NOTICE_TYPE_INCIDENT.equals(noticeType)) return IncidentKind.INCIDENT;
if (PortalNoticeUI.NOTICE_TYPE_MAINTENANCE.equals(noticeType)) return IncidentKind.MAINTENANCE;
return null;
} }
@@ -89,9 +122,39 @@ public class PortalNoticeManService extends BaseService {
PortalNoticeUI ui = portalNoticeUIMapper.toVo(portalNotice); PortalNoticeUI ui = portalNoticeUIMapper.toVo(portalNotice);
setFileInfo(portalNotice, ui); setFileInfo(portalNotice, ui);
populateIncident(portalNotice, ui);
return ui; return ui;
} }
private void populateIncident(PortalNotice portalNotice, PortalNoticeUI ui) {
if (!isIncidentKind(portalNotice.getNoticeType())) {
ui.setAffectedApis(Collections.emptyList());
return;
}
Optional<DjbApistatusIncident> incidentOpt = incidentRepository.findByNoticeId(portalNotice.getId());
if (!incidentOpt.isPresent()) {
ui.setAffectedApis(Collections.emptyList());
return;
}
DjbApistatusIncident incident = incidentOpt.get();
ui.setIncidentId(incident.getIncidentId());
ui.setSummary(incident.getSummary());
ui.setStartedAt(incident.getStartedAt());
ui.setEndAt(incident.getEndAt());
ui.setState(incident.getState() == null ? null : incident.getState().name());
ui.setPreviousState(incident.getPreviousState() == null ? null : incident.getPreviousState().name());
List<IncidentAffectedApiUI> apis = incidentApiRepository
.findByIncidentIdOrderByApiId(incident.getIncidentId()).stream()
.map(api -> {
IncidentAffectedApiUI vo = new IncidentAffectedApiUI();
vo.setApiId(api.getApiId());
vo.setApiName(api.getApiName());
return vo;
}).collect(Collectors.toList());
ui.setAffectedApis(apis);
}
public void insert(PortalNoticeUI portalNoticeUI) throws IOException { public void insert(PortalNoticeUI portalNoticeUI) throws IOException {
//portalNoticeUI.setNoticeSubject(decodeString(portalNoticeUI.getNoticeSubject())); //portalNoticeUI.setNoticeSubject(decodeString(portalNoticeUI.getNoticeSubject()));
//portalNoticeUI.setNoticeDetail(decodeString(StringEscapeUtils.unescapeHtml(portalNoticeUI.getNoticeDetail()))); //portalNoticeUI.setNoticeDetail(decodeString(StringEscapeUtils.unescapeHtml(portalNoticeUI.getNoticeDetail())));
@@ -106,6 +169,61 @@ public class PortalNoticeManService extends BaseService {
} }
portalNoticeService.save(portalNotice); portalNoticeService.save(portalNotice);
if (isIncidentKind(portalNoticeUI.getNoticeType())) {
persistIncident(portalNoticeUI, portalNotice, true);
}
}
private void persistIncident(PortalNoticeUI portalNoticeUI, PortalNotice portalNotice, boolean isCreate) {
IncidentKind kind = toKind(portalNoticeUI.getNoticeType());
DjbApistatusIncident incident = incidentRepository.findByNoticeId(portalNotice.getId())
.orElseGet(DjbApistatusIncident::new);
incident.setKind(kind);
incident.setTitle(portalNotice.getNoticeSubject());
incident.setSummary(portalNoticeUI.getSummary());
incident.setStartedAt(portalNoticeUI.getStartedAt() != null
? portalNoticeUI.getStartedAt() : LocalDateTime.now());
incident.setEndAt(portalNoticeUI.getEndAt());
incident.setDetectedBy("MANUAL");
incident.setNoticeId(portalNotice.getId());
incident.setDraftYn("N");
incident.setFixYn(StringUtils.defaultIfBlank(portalNotice.getFixYn(), "N"));
if (kind == IncidentKind.INCIDENT) {
IncidentState newState = StringUtils.isNotBlank(portalNoticeUI.getState())
? IncidentState.valueOf(portalNoticeUI.getState())
: IncidentState.INVESTIGATING;
if (!isCreate && incident.getState() != newState) {
incident.setPreviousState(incident.getState());
}
incident.setState(newState);
} else {
incident.setState(null);
incident.setPreviousState(null);
}
DjbApistatusIncident saved = incidentRepository.save(incident);
// 영향 API 동기화 — 단순 전체 삭제 후 재삽입
incidentApiRepository.deleteByIncidentId(saved.getIncidentId());
List<IncidentAffectedApiUI> affected = portalNoticeUI.getAffectedApis();
if (affected != null && !affected.isEmpty()) {
List<DjbApistatusIncidentApi> rows = new ArrayList<>();
for (IncidentAffectedApiUI src : affected) {
if (StringUtils.isBlank(src.getApiId())) continue;
DjbApistatusIncidentApi api = new DjbApistatusIncidentApi();
api.setIncidentId(saved.getIncidentId());
api.setApiId(src.getApiId());
api.setApiName(src.getApiName());
rows.add(api);
}
if (!rows.isEmpty()) {
incidentApiRepository.saveAll(rows);
}
}
} }
private void handleFileUpload(PortalNoticeUI portalNoticeUI, PortalNotice portalNotice) throws IOException { private void handleFileUpload(PortalNoticeUI portalNoticeUI, PortalNotice portalNotice) throws IOException {
@@ -129,6 +247,7 @@ public class PortalNoticeManService extends BaseService {
portalNoticeUI.setNoticeDetail(StringEscapeUtils.unescapeHtml(portalNoticeUI.getNoticeDetail())); portalNoticeUI.setNoticeDetail(StringEscapeUtils.unescapeHtml(portalNoticeUI.getNoticeDetail()));
PortalNotice portalNotice = portalNoticeService.getById(portalNoticeUI.getId()); PortalNotice portalNotice = portalNoticeService.getById(portalNoticeUI.getId());
String previousNoticeType = portalNotice.getNoticeType();
if (portalNoticeUI.getFiles() != null && !portalNoticeUI.getFiles().isEmpty()) { if (portalNoticeUI.getFiles() != null && !portalNoticeUI.getFiles().isEmpty()) {
handleFileUpload(portalNoticeUI, portalNotice); handleFileUpload(portalNoticeUI, portalNotice);
@@ -139,11 +258,27 @@ public class PortalNoticeManService extends BaseService {
portalNoticeUIMapper.updateToEntity(portalNoticeUI, portalNotice); portalNoticeUIMapper.updateToEntity(portalNoticeUI, portalNotice);
portalNoticeService.save(portalNotice); portalNoticeService.save(portalNotice);
if (isIncidentKind(portalNotice.getNoticeType())) {
persistIncident(portalNoticeUI, portalNotice, false);
} else if (isIncidentKind(previousNoticeType)) {
// 일반 공지로 유형 변경 → 기존 incident 정리
deleteIncidentIfExists(portalNotice.getId());
}
}
private void deleteIncidentIfExists(String noticeId) {
incidentRepository.findByNoticeId(noticeId).ifPresent(incident -> {
incidentApiRepository.deleteByIncidentId(incident.getIncidentId());
incidentTimelineRepository.deleteByIncidentId(incident.getIncidentId());
incidentRepository.delete(incident);
});
} }
public void delete(String id) { public void delete(String id) {
PortalNotice portalNotice = portalNoticeService.getById(id); PortalNotice portalNotice = portalNoticeService.getById(id);
Optional.ofNullable(portalNotice.getFileId()).ifPresent(fileService::deleteFile); Optional.ofNullable(portalNotice.getFileId()).ifPresent(fileService::deleteFile);
deleteIncidentIfExists(id);
portalNoticeService.deleteById(id); portalNoticeService.deleteById(id);
} }
@@ -8,11 +8,16 @@ import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List;
@Data @Data
public class PortalNoticeUI { public class PortalNoticeUI {
public static final String NOTICE_TYPE_NORMAL = "1";
public static final String NOTICE_TYPE_MAINTENANCE = "2";
public static final String NOTICE_TYPE_INCIDENT = "3";
private String id; private String id;
private String noticeSubject; private String noticeSubject;
@@ -53,4 +58,24 @@ public class PortalNoticeUI {
private boolean hasAttachment; // 첨부파일 여부 private boolean hasAttachment; // 첨부파일 여부
private boolean fileDeleted; // 파일 삭제 여부 private boolean fileDeleted; // 파일 삭제 여부
// ───── 장애/점검(djb_apistatus_incident) 연동 필드 ─────
private Long incidentId;
private String summary;
// HTML <input type="datetime-local"> 는 'T' 구분자 형식 (예: 2026-06-16T10:29).
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm")
@DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm")
private LocalDateTime startedAt;
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm")
@DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm")
private LocalDateTime endAt;
private String state; // INCIDENT 한정 (INVESTIGATING/IDENTIFIED/MONITORING/RESOLVED/CANCELED)
private String previousState; // 직전 상태
private List<IncidentAffectedApiUI> affectedApis;
} }
@@ -3,7 +3,9 @@ package com.eactive.eai.rms.onl.apim.portalnotice;
import com.eactive.apim.portal.portalNotice.entity.PortalNotice; import com.eactive.apim.portal.portalNotice.entity.PortalNotice;
import org.mapstruct.*; import org.mapstruct.*;
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE) @Mapper(componentModel = "spring",
unmappedTargetPolicy = ReportingPolicy.IGNORE,
unmappedSourcePolicy = ReportingPolicy.IGNORE)
public interface PortalNoticeUIMapper { public interface PortalNoticeUIMapper {
PortalNoticeUI toVo(PortalNotice entity); PortalNoticeUI toVo(PortalNotice entity);
@@ -19,9 +19,9 @@ import org.springframework.transaction.support.TransactionTemplate;
import com.eactive.apim.portal.template.entity.MessageRequest; import com.eactive.apim.portal.template.entity.MessageRequest;
import com.eactive.apim.portal.template.repository.MessageRequestRepository; import com.eactive.apim.portal.template.repository.MessageRequestRepository;
import com.eactive.ext.djb.ums.DjbMessengerService; import com.eactive.eai.rms.common.datasource.DataSourceContextHolder;
import com.eactive.ext.kjb.ums.KjbEmailSendModule; import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
import com.eactive.ext.kjb.ums.KjbUmsService; import com.eactive.eai.rms.ext.djb.ums.service.UmsService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -43,13 +43,17 @@ public class UmsDispatchService {
private EntityManager entityManager; private EntityManager entityManager;
private final MessageRequestRepository messageRequestRepository; private final MessageRequestRepository messageRequestRepository;
private final UmsService umsService;
@Autowired @Autowired
public UmsDispatchService( public UmsDispatchService(
@Qualifier("transactionManagerForEMS") PlatformTransactionManager transactionManager, @Qualifier("transactionManagerForEMS") PlatformTransactionManager transactionManager,
MessageRequestRepository messageRequestRepository MessageRequestRepository messageRequestRepository,
UmsService umsService
) { ) {
this.messageRequestRepository = messageRequestRepository; this.messageRequestRepository = messageRequestRepository;
this.umsService = umsService;
this.executorService = new ThreadPoolExecutor( this.executorService = new ThreadPoolExecutor(
CORE_POOL_SIZE, CORE_POOL_SIZE,
@@ -71,17 +75,6 @@ public class UmsDispatchService {
log.info("UmsDispatchService initialized - KjbUmsService and KjbEmailSendModule ready via singleton"); log.info("UmsDispatchService initialized - KjbUmsService and KjbEmailSendModule ready via singleton");
} }
private DjbMessengerService getMessengerService() {
return DjbMessengerService.getInstance();
}
private KjbUmsService getUmsService() {
return KjbUmsService.getInstance();
}
private KjbEmailSendModule getEmailModule() {
return KjbEmailSendModule.getInstance();
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Transactional(transactionManager = "transactionManagerForEMS") @Transactional(transactionManager = "transactionManagerForEMS")
@@ -138,15 +131,7 @@ public class UmsDispatchService {
try { try {
transactionTemplate.execute(status -> { transactionTemplate.execute(status -> {
try { try {
if (DjbMessengerService.isAllowChannel(message)) { umsService.send(message);
getMessengerService().send(message);
}
if (KjbUmsService.isAllowChannel(message)) {
getUmsService().send(message);
}
if (KjbEmailSendModule.isAllowChannel(message)) {
getEmailModule().sendEmail(message);
}
updateMessageStatus(message, "SENT"); updateMessageStatus(message, "SENT");
return null; return null;
} catch (Exception e) { } catch (Exception e) {
@@ -174,5 +159,6 @@ public class UmsDispatchService {
message.setRequestStatus(status); message.setRequestStatus(status);
message.setSentDate(LocalDateTime.now()); message.setSentDate(LocalDateTime.now());
entityManager.merge(message); entityManager.merge(message);
entityManager.flush();
} }
} }
@@ -565,7 +565,7 @@ public class ApiInterfaceService extends OnlBaseService {
restOption.setMethod(outboundHttpMethod); restOption.setMethod(outboundHttpMethod);
restOption.setExtraPath(outboundRestPath); restOption.setExtraPath(outboundRestPath);
String pathType = outboundRestPath.matches(".*\\{[_a-zA-Z]+\\}.*") ? "variableUrlRequest" : "simpleRequest"; String pathType = outboundRestPath.matches(".*\\{.+\\}.*") ? "variableUrlRequest" : "simpleRequest";
restOption.setType(pathType); restOption.setType(pathType);
if (StringUtils.isNotBlank(contentType)) { if (StringUtils.isNotBlank(contentType)) {
@@ -400,7 +400,7 @@ public class SimpleInterfaceService extends OnlBaseService {
jsonObject.put("method", outboundHttpMethod); jsonObject.put("method", outboundHttpMethod);
jsonObject.put("extraPath", outboundRestPath); jsonObject.put("extraPath", outboundRestPath);
String pathType = outboundRestPath.matches(".*\\{[a-zA-Z]+\\}.*") ? "variableUrlRequest" : "simpleRequest"; String pathType = outboundRestPath.matches(".*\\{.+\\}.*") ? "variableUrlRequest" : "simpleRequest";
jsonObject.put("type", pathType); jsonObject.put("type", pathType);
restOption = jsonObject.toString(); restOption = jsonObject.toString();
return restOption; return restOption;
@@ -23,7 +23,6 @@
FLOVRSEVRNAME AS "FAILOVERSVRNM", FLOVRSEVRNAME AS "FAILOVERSVRNM",
HOSTNAME AS "HOSTNAME" HOSTNAME AS "HOSTNAME"
FROM $schemaId$.TSEAIBP03 FROM $schemaId$.TSEAIBP03
WHERE ROWNUM = 1
</statement> </statement>
</sqlMap> </sqlMap>
+10 -3
View File
@@ -5,13 +5,20 @@
# \uC790\uB3D9 \uD56B\uC2A4\uC651 \uD65C\uC131\uD654 # \uC790\uB3D9 \uD56B\uC2A4\uC651 \uD65C\uC131\uD654
autoHotswap=true autoHotswap=true
# \uB85C\uAE45 \uB808\uBCA8 (TRACE, DEBUG, INFO, WARNING, ERROR) # \uB85C\uADF8 \uB808\uBCA8 (TRACE, DEBUG, INFO, WARNING, ERROR)
LOGGER.level=INFO LOGGER=INFO
# \uB85C\uADF8 \uD30C\uC77C \uACBD\uB85C (\uC9C0\uC815 \uC2DC \uCF58\uC194 \uB300\uC2E0 \uD30C\uC77C\uB85C \uCD9C\uB825)
LOGFILE=/Users/rinjae/eactive/djb-eapim/djb-eapim/eapim-admin/logs/hotswap-agent.log
LOGFILE.append=true
# \uC2DC\uAC01 \uD3EC\uB9F7
LOGGER_DATETIME_FORMAT=yyyy-MM-dd HH:mm:ss.SSS
# ============================================================ # ============================================================
# \uBA85\uC2DC\uC801 \uD50C\uB7EC\uADF8\uC778 \uBE44\uD65C\uC131\uD654 (\uC27C\uD45C \uAD6C\uBD84) # \uBA85\uC2DC\uC801 \uD50C\uB7EC\uADF8\uC778 \uBE44\uD65C\uC131\uD654 (\uC27C\uD45C \uAD6C\uBD84)
# ============================================================ # ============================================================
disabledPlugins=org.hotswap.agent.plugin.ibatis.IBatisPlugin,org.hotswap.agent.plugin.mybatis.MyBatisPlugin disabledPlugins=IBatis,MyBatis
# ============================================================ # ============================================================
# Core Plugin \uD65C\uC131\uD654 # Core Plugin \uD65C\uC131\uD654
+1 -1
View File
@@ -4,7 +4,7 @@
<property name="LOG_PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] \\(%F:%L\\) %-20M -%msg%n" /> <property name="LOG_PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] \\(%F:%L\\) %-20M -%msg%n" />
<property name="PREFIX" value="${inst.Type:-ems}" /> <property name="PREFIX" value="${inst.Type:-ems}" />
<property name="LOG_HOME" value="/Log/App/eapim/${inst.Name:-devSvr00}" /> <property name="LOG_HOME" value="/logs/eapim/${inst.Name:-emsSvr00}" />
<property name="BACKUP_HOME" value="${LOG_HOME}/backup/" /> <property name="BACKUP_HOME" value="${LOG_HOME}/backup/" />
<property name="LOG_LEVEL" value="${LOGBACK_LOG_LEVEL:-DEBUG}" /> <property name="LOG_LEVEL" value="${LOGBACK_LOG_LEVEL:-DEBUG}" />
+1 -1
View File
@@ -4,7 +4,7 @@
<property name="LOG_PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] \\(%F:%L\\) %-20M -%msg%n" /> <property name="LOG_PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] \\(%F:%L\\) %-20M -%msg%n" />
<property name="PREFIX" value="${inst.Type:-ems}" /> <property name="PREFIX" value="${inst.Type:-ems}" />
<property name="LOG_HOME" value="/Log/App/eapim/${inst.Name:-devSvr00}" /> <property name="LOG_HOME" value="/logs/eapim/${inst.Name:-devSvr00}" />
<property name="BACKUP_HOME" value="${LOG_HOME}/backup/" /> <property name="BACKUP_HOME" value="${LOG_HOME}/backup/" />
<property name="LOG_LEVEL" value="${LOGBACK_LOG_LEVEL:-DEBUG}" /> <property name="LOG_LEVEL" value="${LOGBACK_LOG_LEVEL:-DEBUG}" />
+1 -1
View File
@@ -4,7 +4,7 @@
<property name="LOG_PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] \\(%F:%L\\) %-20M -%msg%n" /> <property name="LOG_PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] \\(%F:%L\\) %-20M -%msg%n" />
<property name="PREFIX" value="${inst.Type:-ems}" /> <property name="PREFIX" value="${inst.Type:-ems}" />
<property name="LOG_HOME" value="/Log/App/eapim/${inst.Name:-emsSvr00}" /> <property name="LOG_HOME" value="/logs/eapim/${inst.Name:-emsSvr00}" />
<property name="BACKUP_HOME" value="${LOG_HOME}/backup/" /> <property name="BACKUP_HOME" value="${LOG_HOME}/backup/" />
<property name="LOG_LEVEL" value="${LOGBACK_LOG_LEVEL:-INFO}" /> <property name="LOG_LEVEL" value="${LOGBACK_LOG_LEVEL:-INFO}" />