Compare commits
99 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 481e82fec7 | |||
| 1b382f5859 | |||
| 608cb4188e | |||
| 534947637d | |||
| 153c68c6ab | |||
| 80ef41f4fc | |||
| 8aebabe005 | |||
| 6d33b69bed | |||
| 49ebdce4c3 | |||
| 979e43e382 | |||
| 53b075967c | |||
| 724117860b | |||
| 0ee52392aa | |||
| f0af06da88 | |||
| b16d03517a | |||
| edf024f0a3 | |||
| a1a1f27498 | |||
| c62f395983 | |||
| c3955cefd8 | |||
| 5c378eac0d | |||
| e51cdbc42a | |||
| 142d402bad | |||
| f23cb02b1c | |||
| eca120812c | |||
| 71b0fc7009 | |||
| 7b12ef2fb5 | |||
| 292e27e66e | |||
| 61d710dd8c | |||
| 8494758fcd | |||
| f2b25d2775 | |||
| 18489e2440 | |||
| 681e4bd014 | |||
| 27abe51b7b | |||
| 55ef71c5e7 | |||
| 235f4b67b3 | |||
| 89c4b89a71 | |||
| 7193070ebd | |||
| 49616cdd84 | |||
| d1683cc319 | |||
| b5ba2c2bee | |||
| baa528a628 | |||
| f49814b2a1 | |||
| 0ea73aa66f | |||
| ed44183de3 | |||
| aee02cc2a6 | |||
| 99b4470ffe | |||
| d9201eb603 | |||
| 185a23d748 | |||
| 99a440094d | |||
| 37380f2975 | |||
| 908b2ebcc8 | |||
| 25a8e13752 | |||
| 7928a0c27b | |||
| 65ba6c1115 | |||
| 2dcf524cd6 | |||
| bb991cea81 | |||
| 702a9a899b | |||
| 7520b96ed5 | |||
| 7935e69871 | |||
| 4cf6dc4da0 | |||
| a5d55f2647 | |||
| af8b925e65 | |||
| 62ae2d93ff | |||
| 124ecabccf | |||
| c521835633 | |||
| fe201b88cd | |||
| 9f81ad697d | |||
| ca94f2e70a | |||
| 97937a8a35 | |||
| a8e124db5d | |||
| a514b9d7a9 | |||
| 2ac61636a0 | |||
| 399bd9c58d | |||
| b8beeb7c3e | |||
| 0bb00b4fb9 | |||
| 655c35b1e1 | |||
| efd1c748ed | |||
| 64a9c7408e | |||
| 8f9411f990 | |||
| ed1f55c534 | |||
| d1392821ef | |||
| 6542b0c0f8 | |||
| f7de1b6885 | |||
| 3e32220061 | |||
| 10db8129eb | |||
| e34829437d | |||
| ef3f0e802a | |||
| b741a7e563 | |||
| e8069ef371 | |||
| c0e218451e | |||
| af63b1a256 | |||
| 02072df973 | |||
| bc478ad4ed | |||
| 161a68a0a2 | |||
| 3b59f7acd7 | |||
| 1e29d1d721 | |||
| 9342b6484c | |||
| dc3842aa9b | |||
| a440ac842d |
@@ -17,7 +17,7 @@ jobs:
|
||||
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: "30120"
|
||||
DEPLOY_HTTP_PORT: "39120"
|
||||
defaults:
|
||||
run:
|
||||
working-directory: eapim-admin
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
cp eapim-admin/build/libs/eapim-admin.war "$DEPLOY_DIR/monitoring.war"
|
||||
ls -la "$DEPLOY_DIR"
|
||||
|
||||
- name: Start tomcat and readiness probe (timeout 180s)
|
||||
- name: Start tomcat and readiness probe (timeout 300s)
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
LOG="$CATALINA_BASE/logs/catalina.$(date +%Y-%m-%d).log"
|
||||
@@ -99,14 +99,17 @@ jobs:
|
||||
# systemd 가 Tomcat 을 새 cgroup 으로 띄움 — runner step 종료와 무관하게 살아남음
|
||||
systemctl --user start eapim-admin
|
||||
|
||||
DEADLINE=$(($(date +%s) + 180))
|
||||
# /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 \
|
||||
"http://localhost:$DEPLOY_HTTP_PORT/monitoring/login.do" 2>/dev/null || echo "000")
|
||||
"$PROBE_URL" 2>/dev/null || echo "000")
|
||||
case "$STATUS" in
|
||||
200|302|401)
|
||||
echo "Readiness OK (/monitoring/login.do HTTP $STATUS)"
|
||||
echo "Readiness OK ($PROBE_URL HTTP $STATUS)"
|
||||
break
|
||||
;;
|
||||
esac
|
||||
@@ -122,8 +125,13 @@ jobs:
|
||||
case "$STATUS" in
|
||||
200|302|401) ;;
|
||||
*)
|
||||
echo "::error::Readiness probe failed within 180s (last HTTP status: $STATUS)"
|
||||
[ -f "$LOG" ] && tail -c +$((BEFORE+1)) "$LOG" | tail -100
|
||||
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
|
||||
|
||||
Vendored
+174
@@ -0,0 +1,174 @@
|
||||
pipeline {
|
||||
agent none
|
||||
|
||||
options {
|
||||
timestamps()
|
||||
disableConcurrentBuilds()
|
||||
skipDefaultCheckout() // stage별 agent의 자동 SCM checkout 방지 (Deploy 노드는 git 접근 불필요, unstash로만 WAR 수신)
|
||||
buildDiscarder(logRotator(numToKeepStr: '20', artifactNumToKeepStr: '20'))
|
||||
}
|
||||
|
||||
environment {
|
||||
GIT_SSH_COMMAND = 'ssh -o StrictHostKeyChecking=accept-new'
|
||||
WEBHOOK_URL = 'http://172.30.1.50:18000/api/hooks/01ba51b01d3c4c98ae8f3183a23a84ed713197857d024b5da4f303458195eb32'
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Build (djb-vm)') {
|
||||
agent { label 'djb-vm' }
|
||||
environment {
|
||||
JAVA_HOME = '/apps/opts/jdk8'
|
||||
GRADLE_HOME = '/apps/opts/gradle-8.7'
|
||||
GRADLE_USER_HOME = '/apps/opts/gradle-home'
|
||||
NODE_HOME = '/apps/opts/node-v24'
|
||||
PATH = "/apps/opts/jdk8/bin:/apps/opts/gradle-8.7/bin:/apps/opts/node-v24/bin:/apps/opts/bin:${env.PATH}"
|
||||
}
|
||||
stages {
|
||||
stage('Checkout') {
|
||||
steps { checkout scm }
|
||||
}
|
||||
|
||||
stage('Checkout dependencies') {
|
||||
steps {
|
||||
sh '''
|
||||
set -eu
|
||||
cd "$WORKSPACE/.."
|
||||
|
||||
mkdir -p eapim-online
|
||||
|
||||
for REPO in elink-online-core elink-online-core-jpa elink-online-transformer elink-online-common elink-online-emsclient; do
|
||||
TARGET="eapim-online/$REPO"
|
||||
if [ ! -d "$TARGET/.git" ]; then
|
||||
rm -rf "$TARGET"
|
||||
git clone --depth=1 --branch master \
|
||||
"ssh://git@172.30.1.50:2222/djb-eapim/$REPO.git" \
|
||||
"$TARGET"
|
||||
else
|
||||
git -C "$TARGET" fetch --depth=1 origin master
|
||||
git -C "$TARGET" reset --hard origin/master
|
||||
git -C "$TARGET" clean -fdx
|
||||
fi
|
||||
done
|
||||
|
||||
for REPO in elink-portal-common eapim-admin-djb; do
|
||||
if [ ! -d "$REPO/.git" ]; then
|
||||
rm -rf "$REPO"
|
||||
git clone --depth=1 --branch master \
|
||||
"ssh://git@172.30.1.50:2222/djb-eapim/$REPO.git" \
|
||||
"$REPO"
|
||||
else
|
||||
git -C "$REPO" fetch --depth=1 origin master
|
||||
git -C "$REPO" reset --hard origin/master
|
||||
git -C "$REPO" clean -fdx
|
||||
fi
|
||||
done
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
stage('Verify toolchain') {
|
||||
steps {
|
||||
sh '''
|
||||
set -eu
|
||||
java -version
|
||||
gradle --version
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
stage('Build WAR') {
|
||||
steps { sh 'gradle clean build -x test --no-daemon -Pprofile=weblogic' }
|
||||
post {
|
||||
success {
|
||||
sh '''
|
||||
set -eu
|
||||
cd build/libs
|
||||
sha256sum eapim-admin.war > eapim-admin.war.sha256
|
||||
'''
|
||||
archiveArtifacts artifacts: 'build/libs/eapim-admin.war,build/libs/eapim-admin.war.sha256', fingerprint: true
|
||||
stash name: 'war', includes: 'build/libs/eapim-admin.war'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Deploy (weblogic)') {
|
||||
agent { label 'weblogic' }
|
||||
environment {
|
||||
JENKINS_NODE_COOKIE = 'dontKillMe' // background weblogic를 빌드 종료 시 죽이지 않게
|
||||
WL_HOME = '/app/eapim/adminportal'
|
||||
WL_DEPLOY_DIR = '/app/eapim/adminportal'
|
||||
WL_WAR_NAME = 'eapim-admin.war'
|
||||
WL_HTTP_PORT = '39120'
|
||||
WL_NOHUP = '/logs/weblogic/domains/eapimDomain/nohup.emsSvr11.out'
|
||||
}
|
||||
stages {
|
||||
stage('Stop WebLogic') {
|
||||
steps {
|
||||
sh '"$WL_HOME/stopEms11.sh"' // 동기: 완전 종료까지 블록, 미기동 시에도 안전
|
||||
}
|
||||
}
|
||||
stage('Deploy WAR') {
|
||||
steps {
|
||||
unstash 'war'
|
||||
sh '''
|
||||
set -eu
|
||||
cp build/libs/eapim-admin.war "$WL_DEPLOY_DIR/$WL_WAR_NAME"
|
||||
ls -la "$WL_DEPLOY_DIR"
|
||||
'''
|
||||
}
|
||||
}
|
||||
stage('Start WebLogic and readiness') {
|
||||
steps {
|
||||
sh '''
|
||||
set -eu
|
||||
"$WL_HOME/startEms11.sh" # nohup & 로 백그라운드 기동, 즉시 리턴
|
||||
|
||||
DEADLINE=$(($(date +%s) + 300))
|
||||
STATUS=000
|
||||
while [ "$(date +%s)" -lt "$DEADLINE" ]; do
|
||||
STATUS=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 3 \
|
||||
"http://localhost:$WL_HTTP_PORT/monitoring/loginForm.do" 2>/dev/null || echo "000")
|
||||
case "$STATUS" in
|
||||
200|302|401) echo "Readiness OK (/monitoring/loginForm.do HTTP $STATUS)"; break ;;
|
||||
esac
|
||||
sleep 3
|
||||
done
|
||||
|
||||
case "$STATUS" in
|
||||
200|302|401) ;;
|
||||
*)
|
||||
echo "Readiness failed within 300s, last HTTP=$STATUS"
|
||||
[ -f "$WL_NOHUP" ] && tail -120 "$WL_NOHUP"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
success {
|
||||
node('weblogic') {
|
||||
sh '''
|
||||
set +e
|
||||
payload=$(printf '{"text":"[%s #%s](%s) SUCCESS"}' "$JOB_NAME" "$BUILD_NUMBER" "$BUILD_URL")
|
||||
curl -sS --fail --max-time 5 -H 'Content-Type: application/json' -X POST --data "$payload" "$WEBHOOK_URL" >/dev/null || echo "Webhook failed"
|
||||
'''
|
||||
}
|
||||
}
|
||||
failure {
|
||||
node('weblogic') {
|
||||
sh '''
|
||||
set +e
|
||||
payload=$(printf '{"text":"[%s #%s](%s) FAILURE"}' "$JOB_NAME" "$BUILD_NUMBER" "$BUILD_URL")
|
||||
curl -sS --fail --max-time 5 -H 'Content-Type: application/json' -X POST --data "$payload" "$WEBHOOK_URL" >/dev/null || echo "Webhook failed"
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ PortalTermsManController_약관관리_APIGW_INSERT,UPDATE,DELETE
|
||||
ProdClientManController_운영Client (키정보)관리_APIGW_INSERT,UPDATE,DELETE
|
||||
ApiSpecController_API스펙관리_APIGW_INSERT,DELETE
|
||||
MessageTemplateManController_메세지템플릿관리_APIGW_INSERT,UPDATE,DELETE
|
||||
PortalPartnershipManController_사업제휴신청관리_APIGW_UNMASK
|
||||
PortalPartnershipManController_피드백/개선요청관리_APIGW_UNMASK,DELETE
|
||||
PortalPropertyController_포탈 프로퍼티 관리_APIGW_UPDATE
|
||||
PortalUserTermsManController_약관동의 이력_APIGW_UNMASK
|
||||
PortalUserTermsManController_약관동의 이력_APIGW_UNMASK
|
||||
MessageRequestManController_메세지발송내역_APIGW_UPDATE_STATUS
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
@charset "utf-8";
|
||||
|
||||
.gnb .depth1 > li:hover > a{color:#008cc5;} /* theme */
|
||||
.gnb .sitemap-link:hover{color:#008cc5;} /* theme */
|
||||
.gnb .depth1 > li > .red_box{background:#008cc5;}/* theme */
|
||||
.gnb .depth2 > li > a:hover{color:#008cc5;}/* theme */
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
@charset "utf-8";
|
||||
|
||||
.gnb .depth1 > li:hover > a{color:#16a085;} /* theme */
|
||||
.gnb .sitemap-link:hover{color:#16a085;} /* theme */
|
||||
.gnb .depth1 > li > .red_box{background:#16a085;}/* theme */
|
||||
.gnb .depth2 > li > a:hover{color:#16a085;}/* theme */
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
@charset "utf-8";
|
||||
|
||||
.gnb .depth1 > li:hover > a{color:#e74c3c;} /* theme */
|
||||
.gnb .sitemap-link:hover{color:#e74c3c;} /* theme */
|
||||
.gnb .depth1 > li > .red_box{background:#e74c3c;}/* theme */
|
||||
.gnb .depth2 > li > a:hover{color:#e74c3c;}/* theme */
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
@charset "utf-8";
|
||||
|
||||
.gnb .depth1 > li:hover > a{color:#ff80c0;} /* theme */
|
||||
.gnb .sitemap-link:hover{color:#ff80c0;} /* theme */
|
||||
.gnb .depth1 > li > .red_box{background:#ff80c0;}/* theme */
|
||||
.gnb .depth2 > li > a:hover{color:#ff80c0;}/* theme */
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
@charset "utf-8";
|
||||
|
||||
.gnb .depth1 > li:hover > a{color:#be8200;} /* theme */
|
||||
.gnb .sitemap-link:hover{color:#be8200;} /* theme */
|
||||
.gnb .depth1 > li > .red_box{background:#be8200;}/* theme */
|
||||
.gnb .depth2 > li > a:hover{color:#be8200;}/* theme */
|
||||
|
||||
|
||||
@@ -62,8 +62,8 @@ header.sub{position:relative; padding-top:80px;}
|
||||
100% { color:rgba(255,96,78,0); }
|
||||
} */
|
||||
|
||||
.gnb_bg{position:absolute; top:80px; left:0; display:none; width:100%; height:382px; box-sizing:border-box; background:white; box-shadow: 0px 5px 5px 0px rgba(0,0,0,0.2);}
|
||||
.gnb{position:absolute; top:0px; left:230px; display:inline-block; height:80px; overflow-y:hidden;}
|
||||
.gnb_bg{position:absolute; top:80px; left:0; display:none; width:100%; height:282px; box-sizing:border-box; background:white; box-shadow: 0px 5px 5px 0px rgba(0,0,0,0.2);}
|
||||
.gnb{position:absolute; top:0px; left:180px; display:inline-block; height:80px; overflow-y:hidden;}
|
||||
.gnb:hover{height:auto; overflow-y:visible;}
|
||||
.gnb:hover + .gnb_bg{display:block;}
|
||||
.gnb h1{float:left; display:block; width:auto; height:100%; line-height:80px;}
|
||||
@@ -71,15 +71,15 @@ header.sub{position:relative; padding-top:80px;}
|
||||
|
||||
.gnb .depth1 > li{position:relative;}
|
||||
.gnb .depth1 > li > a{position:relative; display:block; box-sizing:border-box; color:#000; font-size:14px; width:160px; height:80px; text-align:center; line-height:80px; letter-spacing:-0.5px; opacity:0.7; transition:all 0.3s;}
|
||||
.gnb .depth1 > li > a:after{content:''; position:absolute; top:34px; right:0; display:inline-block; width:1px; height:13px; background:#ddd;}
|
||||
.gnb .depth1 > li:first-child > a:before{content:''; position:absolute; top:34px; left:0; display:inline-block; width:1px; height:13px; background:#ddd;}
|
||||
.gnb .depth1 > li > a:after{content:''; position:absolute; top:34px; right:0; display:inline-block; width:1px; height:13px; }
|
||||
.gnb .depth1 > li:first-child > a:before{content:''; position:absolute; top:34px; left:0; display:inline-block; width:1px; height:13px; }
|
||||
/*
|
||||
.gnb .depth1 > li:hover .red_box{transform:scaleX(1);}
|
||||
.gnb .depth1 > li:hover > a{opacity:1;}
|
||||
.gnb .depth1 > li > .red_box{display:block; position:absolute; width:100%; height:2px; top:78px; box-sizing:border-box; background:#ed1c24; transform-origin:center center; transform:scaleX(0); transition:all 0.3s;}
|
||||
*/
|
||||
.gnb .depth2{position:absolute; left:0; top:80px; width:160px; height:410px; box-sizing:border-box; padding:20px 0; border-right:1px solid #eee; text-align:center; z-index:9;}
|
||||
.gnb .depth2.first{border-left:1px solid #eee;}
|
||||
.gnb .depth2{position:absolute; left:0; top:80px; width:160px; height:310px; box-sizing:border-box; padding:20px 0; border-right:1px solid #eee; text-align:center; z-index:9;}
|
||||
.gnb .depth1 > li:first-child .depth2{border-left:1px solid #eee;}
|
||||
.gnb .depth2 > li > a{display:block; color:#000; font-size:13px; line-height:30px;}
|
||||
.gnb .depth2 > li > a:hover{color:#ff0000;}
|
||||
|
||||
@@ -94,9 +94,9 @@ header.sub{position:relative; padding-top:80px;}
|
||||
.left_box .depth3 > li:hover > a, .left_box .depth3 > li.on > a{color:#ed1c24;}
|
||||
|
||||
.content_top{position:fixed; top:0; left:0; padding:0 30px; width:100%; height:50px; line-height:50px; background:#eee; border-top:1px solid #ddd; border-left:1px solid #ddd; z-index:100;}
|
||||
.content_top .path{}
|
||||
.content_top .path > li{display:inline-block;}
|
||||
.content_top .path > li a{display:block; height:50px; line-height:50px; padding-left:10px; font-size:12px; color:#999;}
|
||||
.content_top .path{margin:0; padding:0;}
|
||||
.content_top .path > li{display:inline-block; margin:0; padding:0;}
|
||||
.content_top .path > li a{display:block; height:50px; line-height:50px; padding-left:10px; font-size:12px; color:#999; text-decoration:none;}
|
||||
.content_top .path > li a:hover{color:#666;}
|
||||
.content_top .path > li a:before{content:'>'; margin-right:10px;}
|
||||
.content_top .path > li:first-child a:before{content:'';}
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>
|
||||
<%@ page import="java.lang.reflect.Method" %>
|
||||
<%@ page import="org.apache.commons.lang3.StringUtils" %>
|
||||
<%@ page import="com.eactive.eai.rms.common.login.SessionManager" %>
|
||||
<%!
|
||||
// 최소 HTML 이스케이프 (진단 페이지 XSS 방지)
|
||||
private String esc(String s) {
|
||||
if (s == null) return "";
|
||||
StringBuilder b = new StringBuilder(s.length() + 16);
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
switch (c) {
|
||||
case '&': b.append("&"); break;
|
||||
case '<': b.append("<"); break;
|
||||
case '>': b.append(">"); break;
|
||||
case '"': b.append("""); break;
|
||||
case '\'': b.append("'"); break;
|
||||
default: b.append(c);
|
||||
}
|
||||
}
|
||||
return b.toString();
|
||||
}
|
||||
%>
|
||||
<%
|
||||
// 로그인 사용자만 접근 — real 모드에서 운영 키 암복호화 오라클이 되지 않도록 보호
|
||||
if (StringUtils.isBlank(SessionManager.getUserId(request))
|
||||
&& StringUtils.isBlank(SessionManager.getRoleIdString(request))) {
|
||||
response.sendRedirect(request.getContextPath() + "/loginForm.do");
|
||||
return;
|
||||
}
|
||||
|
||||
response.setHeader("Pragma", "No-cache");
|
||||
response.setHeader("Cache-Control", "no-cache");
|
||||
response.setHeader("Expires", "0");
|
||||
request.setCharacterEncoding("utf-8");
|
||||
|
||||
String op = request.getParameter("op");
|
||||
String input = request.getParameter("input");
|
||||
boolean run = request.getParameter("run") != null;
|
||||
if (op == null) op = "encrypt";
|
||||
if (input == null) input = "";
|
||||
|
||||
String mode = "UNKNOWN";
|
||||
String revision = "-";
|
||||
String returnCode = "-";
|
||||
String result = "";
|
||||
String error = "";
|
||||
boolean loaded = false;
|
||||
|
||||
try {
|
||||
Class<?> cls = Class.forName("com.eactive.ext.djb.DamoManager");
|
||||
Object damo = cls.getMethod("getInstance").invoke(null);
|
||||
loaded = true;
|
||||
|
||||
boolean bypass = (Boolean) cls.getMethod("isBypassMode").invoke(damo);
|
||||
boolean fake = (Boolean) cls.getMethod("isFakeMode").invoke(damo);
|
||||
mode = bypass ? "BYPASS" : (fake ? "FAKE" : "REAL");
|
||||
revision = String.valueOf(cls.getMethod("getRevision").invoke(damo));
|
||||
returnCode = String.valueOf(cls.getMethod("getReturnCode").invoke(damo));
|
||||
|
||||
if (run) {
|
||||
if ("encrypt".equals(op)) {
|
||||
result = (String) cls.getMethod("encrypt", String.class).invoke(damo, input);
|
||||
} else if ("decrypt".equals(op)) {
|
||||
result = (String) cls.getMethod("decrypt", String.class).invoke(damo, input);
|
||||
} else if ("sha256".equals(op)) {
|
||||
int t = cls.getField("SHA256").getInt(null);
|
||||
result = (String) cls.getMethod("hash", int.class, String.class).invoke(damo, t, input);
|
||||
} else if ("sha512".equals(op)) {
|
||||
int t = cls.getField("SHA512").getInt(null);
|
||||
result = (String) cls.getMethod("hash", int.class, String.class).invoke(damo, t, input);
|
||||
} else {
|
||||
error = "알 수 없는 op: " + op;
|
||||
}
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
error = "com.eactive.ext.djb.DamoManager 클래스를 찾을 수 없습니다. "
|
||||
+ "damo-manager.jar 를 WAS lib (운영) 또는 WEB-INF/lib 에 배포하세요.";
|
||||
} catch (Throwable t) {
|
||||
// require-real fail-fast(ExceptionInInitializerError) 또는 초기화 실패(NoClassDefFoundError) 포함
|
||||
Throwable c = (t.getCause() != null) ? t.getCause() : t;
|
||||
error = c.toString();
|
||||
if (c instanceof NoClassDefFoundError
|
||||
|| error.contains("require-real")
|
||||
|| error.contains("Could not initialize")) {
|
||||
error += " (※ -Ddamo-manager.require-real=true 인데 scpdb 가 없어 기동에 실패했을 수 있습니다. "
|
||||
+ "scpdb 를 갖추거나 옵션을 내리세요.)";
|
||||
}
|
||||
}
|
||||
|
||||
String badgeColor = "REAL".equals(mode) ? "#1a7f37"
|
||||
: "BYPASS".equals(mode) ? "#9a3412"
|
||||
: "FAKE".equals(mode) ? "#9a6700" : "#6e7781";
|
||||
%>
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>DamoManager 테스트</title>
|
||||
<style>
|
||||
body { font-family: -apple-system, "Malgun Gothic", sans-serif; margin: 24px; color: #24292f; }
|
||||
h1 { font-size: 20px; margin: 0 0 4px; }
|
||||
.sub { color: #6e7781; font-size: 13px; margin-bottom: 16px; }
|
||||
.badge { display:inline-block; color:#fff; padding:2px 10px; border-radius:12px; font-size:12px; font-weight:600; background:<%= badgeColor %>; }
|
||||
.meta { font-size:12px; color:#57606a; margin-left:8px; }
|
||||
.card { border:1px solid #d0d7de; border-radius:8px; padding:16px; max-width:760px; }
|
||||
label { display:block; font-size:13px; font-weight:600; margin:12px 0 4px; }
|
||||
textarea, select { width:100%; box-sizing:border-box; font-size:14px; padding:8px; border:1px solid #d0d7de; border-radius:6px; font-family:ui-monospace, Menlo, monospace; }
|
||||
textarea { height:90px; resize:vertical; }
|
||||
.row { display:flex; gap:12px; }
|
||||
.row > div { flex:1; }
|
||||
button { margin-top:14px; background:#1f6feb; color:#fff; border:0; border-radius:6px; padding:9px 18px; font-size:14px; font-weight:600; cursor:pointer; }
|
||||
.result { margin-top:16px; }
|
||||
.out { background:#f6f8fa; border:1px solid #d0d7de; border-radius:6px; padding:12px; white-space:pre-wrap; word-break:break-all; font-family:ui-monospace, Menlo, monospace; font-size:13px; min-height:20px; }
|
||||
.err { color:#cf222e; background:#fff5f5; border-color:#ffc1c1; }
|
||||
.hint { font-size:12px; color:#6e7781; margin-top:18px; line-height:1.6; }
|
||||
code { background:#eff1f3; padding:1px 5px; border-radius:4px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>DamoManager 테스트</h1>
|
||||
<div class="sub">웹에서 encrypt / decrypt / hash 동작을 즉시 확인 (eapim-admin)</div>
|
||||
|
||||
<div class="card">
|
||||
<div>
|
||||
<% if (loaded) { %>
|
||||
<span class="badge"><%= mode %> MODE</span>
|
||||
<span class="meta">revision=<%= esc(revision) %> · returnCode=<%= esc(returnCode) %></span>
|
||||
<% } else { %>
|
||||
<span class="badge" style="background:#cf222e;">NOT LOADED</span>
|
||||
<span class="meta">damo-manager.jar 미배포</span>
|
||||
<% } %>
|
||||
</div>
|
||||
|
||||
<form method="post" action="<%= request.getContextPath() %>/damo.jsp">
|
||||
<div class="row">
|
||||
<div>
|
||||
<label for="op">동작</label>
|
||||
<select id="op" name="op">
|
||||
<option value="encrypt" <%= "encrypt".equals(op) ? "selected" : "" %>>encrypt (암호화)</option>
|
||||
<option value="decrypt" <%= "decrypt".equals(op) ? "selected" : "" %>>decrypt (복호화)</option>
|
||||
<option value="sha256" <%= "sha256".equals(op) ? "selected" : "" %>>hash SHA-256</option>
|
||||
<option value="sha512" <%= "sha512".equals(op) ? "selected" : "" %>>hash SHA-512</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label for="input">입력</label>
|
||||
<textarea id="input" name="input" placeholder="암호화/복호화/해시할 문자열"><%= esc(input) %></textarea>
|
||||
|
||||
<input type="hidden" name="run" value="1">
|
||||
<button type="submit">실행</button>
|
||||
</form>
|
||||
|
||||
<% if (run || error.length() > 0) { %>
|
||||
<div class="result">
|
||||
<label>결과<% if (run && error.length() == 0) { %> <span class="meta">(<%= esc(op) %>)</span><% } %></label>
|
||||
<div class="out <%= error.length() > 0 ? "err" : "" %>"><%= error.length() > 0 ? esc(error) : esc(result) %></div>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<div class="hint">
|
||||
모드 의미 — <b>BYPASS</b>: 무변환 원문 통과(<b>기본값</b>, 옵션 없음) · <b>FAKE</b>: Base64/JRE 해시(<code>-Ddamo-manager.enabled=true</code>, scpdb 없음) · <b>REAL</b>: penta scpdb 실 암복호화(<code>enabled=true</code> + scpdb).<br>
|
||||
BYPASS 모드에서는 encrypt/decrypt/hash 모두 입력을 그대로 반환합니다.<br>
|
||||
운영은 <code>-Ddamo-manager.require-real=true</code> 로 real 강제(아니면 기동 실패). CLI 로도 동일 확인: <code>java -jar damo-manager.jar enc <text></code>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,6 +1,7 @@
|
||||
<%@ page language="java" contentType="text/html; charset=utf-8"%>
|
||||
<%@page import="org.apache.commons.lang3.StringUtils"%>
|
||||
<%@page import="com.eactive.eai.rms.common.login.SessionManager"%>
|
||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
|
||||
@@ -54,10 +54,18 @@ function detail(key){
|
||||
$('#systemCode').val(data.systemCode);
|
||||
$('#logTypeText').val(data.logTypeText);
|
||||
$('#remoteAddress').val(data.remoteAddress);
|
||||
$('#logMsg').val(data.message);
|
||||
$('#logSubMsg').val(data.command);
|
||||
$('#parameters').val(data.parameters);
|
||||
|
||||
// 사유(message)는 값이 있을 때만 노출
|
||||
if (data.message) {
|
||||
$('#logMsg').val(data.message);
|
||||
$('#messageRow').show();
|
||||
} else {
|
||||
$('#logMsg').val('');
|
||||
$('#messageRow').hide();
|
||||
}
|
||||
|
||||
},
|
||||
error:function(e){
|
||||
alert(e.responseText);
|
||||
@@ -121,6 +129,9 @@ $(document).ready(function() {
|
||||
<tr>
|
||||
<th><%= localeMessage.getString("auditLogMan.command") %></th><td><input type="text" id="logSubMsg" name="logSubMsg" readonly="readonly"/></td>
|
||||
</tr>
|
||||
<tr id="messageRow" style="display:none;">
|
||||
<th><%= localeMessage.getString("auditLogMan.message") %></th><td><textarea id="logMsg" name="logMsg" style="width:100%;height:80px" readonly="readonly"></textarea></td>
|
||||
</tr>
|
||||
<tr height="100px">
|
||||
<th><%= localeMessage.getString("auditLogMan.parameters") %></th><td><textarea id="parameters" name="parameters" style="width:100%;height:200px" readonly="readonly"></textarea></td>
|
||||
</tr>
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
<%@ page language="java" contentType="text/html; charset=utf-8"%>
|
||||
<%@ page import="java.util.List"%>
|
||||
<%@ page import="com.eactive.eai.rms.common.acl.sitemap.ui.SitemapNode"%>
|
||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
|
||||
<%!
|
||||
private void printNodeLabel(javax.servlet.jsp.JspWriter out, SitemapNode node) throws java.io.IOException {
|
||||
String url = node.getMenuUrl();
|
||||
boolean hasLink = url != null && !url.trim().isEmpty() && !"NAN".equalsIgnoreCase(url.trim());
|
||||
if (hasLink) {
|
||||
out.print("<a href=\"javascript:void(0);\" class=\"sitemap-name\" onclick=\"goPage('"
|
||||
+ escapeJs(url) + "','" + escapeJs(node.getMenuId()) + "');return false;\">"
|
||||
+ escapeHtml(node.getMenuName()) + "</a>");
|
||||
} else {
|
||||
out.print("<span class=\"sitemap-name\">" + escapeHtml(node.getMenuName()) + "</span>");
|
||||
}
|
||||
}
|
||||
|
||||
private void printSitemapNode(javax.servlet.jsp.JspWriter out, SitemapNode node) throws java.io.IOException {
|
||||
out.print("<li>");
|
||||
printNodeLabel(out, node);
|
||||
|
||||
List<SitemapNode> children = node.getChildren();
|
||||
if (children != null && !children.isEmpty()) {
|
||||
out.print("<ul>");
|
||||
for (SitemapNode child : children) {
|
||||
printSitemapNode(out, child);
|
||||
}
|
||||
out.print("</ul>");
|
||||
}
|
||||
out.print("</li>");
|
||||
}
|
||||
|
||||
private String escapeHtml(String value) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
return value.replace("&", "&").replace("<", "<").replace(">", ">").replace("\"", """);
|
||||
}
|
||||
|
||||
private String escapeJs(String value) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
return value.replace("\\", "\\\\").replace("'", "\\'");
|
||||
}
|
||||
%>
|
||||
<%
|
||||
response.setHeader("Pragma", "No-cache");
|
||||
response.setHeader("Cache-Control", "no-cache");
|
||||
response.setHeader("Expires", "0");
|
||||
|
||||
List<SitemapNode> sitemapTree = (List<SitemapNode>) request.getAttribute("sitemapTree");
|
||||
%>
|
||||
<html>
|
||||
<head>
|
||||
<title></title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<jsp:include page="/jsp/common/include/css.jsp"/>
|
||||
<jsp:include page="/jsp/common/include/script.jsp"/>
|
||||
<style>
|
||||
.sitemap-columns { display: flex; flex-wrap: wrap; gap: 40px; }
|
||||
.sitemap-column { min-width: 180px; }
|
||||
.sitemap-category { font-size: 1.1em; font-weight: bold; padding-bottom: 6px; margin-bottom: 8px; border-bottom: 2px solid #ccc; }
|
||||
.sitemap-tree, .sitemap-tree ul { list-style-type: disc; margin: 0; padding-left: 18px; }
|
||||
.sitemap-tree { padding-left: 0; list-style-type: none; }
|
||||
.sitemap-tree li { line-height: 1.8; }
|
||||
a.sitemap-name { text-decoration: none; cursor: pointer; }
|
||||
a.sitemap-name:hover { text-decoration: underline; }
|
||||
</style>
|
||||
<script language="javascript">
|
||||
function goPage(url, menuId) {
|
||||
var serviceType = sessionStorage["serviceType"];
|
||||
|
||||
var pageUrl = url;
|
||||
pageUrl += (pageUrl.indexOf("?") > -1 ? "&" : "?") + "menuId=" + menuId;
|
||||
pageUrl += "&serviceType=" + serviceType;
|
||||
|
||||
parent.leftFrame.location.href = '<c:url value="/leftMenu.do"/>?menuId=' + menuId + '&serviceType=' + serviceType;
|
||||
location.href = pageUrl;
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="right_box">
|
||||
<div class="content_top">
|
||||
<ul class="path">
|
||||
<li><a href="#">${rmsMenuPath}</a></li>
|
||||
</ul>
|
||||
</div><!-- end content_top -->
|
||||
<div class="content_middle">
|
||||
<!-- <div class="title">사이트맵</div> -->
|
||||
<div class="sitemap-columns">
|
||||
<%
|
||||
for (SitemapNode category : sitemapTree) {
|
||||
%>
|
||||
<div class="sitemap-column">
|
||||
<div class="sitemap-category">
|
||||
<%
|
||||
printNodeLabel(out, category);
|
||||
%>
|
||||
</div>
|
||||
<ul class="sitemap-tree">
|
||||
<%
|
||||
List<SitemapNode> children = category.getChildren();
|
||||
if (children != null) {
|
||||
for (SitemapNode child : children) {
|
||||
printSitemapNode(out, child);
|
||||
}
|
||||
}
|
||||
%>
|
||||
</ul>
|
||||
</div>
|
||||
<%
|
||||
}
|
||||
%>
|
||||
</div>
|
||||
</div><!-- end content_middle -->
|
||||
</div><!-- end right_box -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -531,6 +531,88 @@
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* 사유 입력 모달 - 확인 시 입력한 사유 문자열을 onConfirm(reason)으로 전달
|
||||
* @param {string} message - 안내 메시지
|
||||
* @param {object} options - (title, confirmText, cancelText, placeholder, required, onConfirm, onCancel)
|
||||
* required 기본 true (빈 사유 차단)
|
||||
*/
|
||||
function showReasonPrompt(message, options) {
|
||||
options = options || {};
|
||||
var title = options.title || '사유 입력';
|
||||
var confirmText = options.confirmText || '확인';
|
||||
var cancelText = options.cancelText || '취소';
|
||||
var placeholder = options.placeholder || '사유를 입력하세요.';
|
||||
var required = options.required !== false;
|
||||
var onConfirm = options.onConfirm || function(){};
|
||||
var onCancel = options.onCancel || function(){};
|
||||
|
||||
// 기존 모달 제거
|
||||
$('#commonReasonModal').remove();
|
||||
|
||||
var iconHtml = getAlertIcon('warning');
|
||||
|
||||
var modalHtml =
|
||||
'<div id="commonReasonModal" class="common-alert-overlay">' +
|
||||
'<div class="common-alert-modal alert-warning">' +
|
||||
'<div class="common-alert-header">' +
|
||||
'<span class="common-alert-icon">' + iconHtml + '</span>' +
|
||||
'<span class="common-alert-title">' + title + '</span>' +
|
||||
'</div>' +
|
||||
'<div class="common-alert-body">' +
|
||||
'<p class="common-alert-message">' + message + '</p>' +
|
||||
'<textarea id="commonReasonInput" maxlength="200" placeholder="' + placeholder + '" style="width:100%;height:80px;margin-top:10px;box-sizing:border-box;font-size:13px;padding:8px;border:1px solid #ddd;border-radius:4px;resize:vertical;"></textarea>' +
|
||||
'<p id="commonReasonError" style="display:none;color:#f44336;font-size:12px;margin:6px 0 0;">사유를 입력해 주세요.</p>' +
|
||||
'</div>' +
|
||||
'<div class="common-alert-footer">' +
|
||||
'<button type="button" class="common-confirm-cancel-btn" id="commonReasonCancelBtn">' + cancelText + '</button>' +
|
||||
'<button type="button" class="common-alert-btn" id="commonReasonOkBtn">' + confirmText + '</button>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
|
||||
$('body').append(modalHtml);
|
||||
|
||||
setTimeout(function() {
|
||||
$('#commonReasonModal').addClass('show');
|
||||
$('#commonReasonInput').focus();
|
||||
}, 10);
|
||||
|
||||
// 확인 버튼
|
||||
$('#commonReasonOkBtn').on('click', function() {
|
||||
var reason = $.trim($('#commonReasonInput').val());
|
||||
if (required && reason === '') {
|
||||
$('#commonReasonError').show();
|
||||
$('#commonReasonInput').focus();
|
||||
return;
|
||||
}
|
||||
closeCommonReason(function() { onConfirm(reason); });
|
||||
});
|
||||
|
||||
// 취소 버튼
|
||||
$('#commonReasonCancelBtn').on('click', function() {
|
||||
closeCommonReason(onCancel);
|
||||
});
|
||||
|
||||
// ESC 키로 취소
|
||||
$(document).on('keydown.commonReason', function(e) {
|
||||
if (e.keyCode === 27) {
|
||||
closeCommonReason(onCancel);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function closeCommonReason(callback) {
|
||||
$('#commonReasonModal').removeClass('show');
|
||||
setTimeout(function() {
|
||||
$('#commonReasonModal').remove();
|
||||
$(document).off('keydown.commonReason');
|
||||
if (typeof callback === 'function') {
|
||||
callback();
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- 공용 Alert 모달 스타일 -->
|
||||
|
||||
@@ -91,7 +91,10 @@
|
||||
data: $('#loginForm').serialize(),
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
if (response.smsAuthRequired) {
|
||||
if (response.changePassword) {
|
||||
showChangeInitPassword(response);
|
||||
}
|
||||
else if (response.smsAuthRequired) {
|
||||
// SMS 인증 필요
|
||||
showSmsAuthModal(response);
|
||||
} else if (response.success) {
|
||||
@@ -108,6 +111,13 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showChangeInitPassword(data) {
|
||||
$('input[name="resetUserId"]').val($('input[name="userId"]').val());
|
||||
$('input[name="resetPassword"]').val($('input[name="password"]').val());
|
||||
$('#changeInitPassword').hide();
|
||||
$('#pwdChgModal').modal('show');
|
||||
}
|
||||
|
||||
// SMS 인증 모달 표시
|
||||
function showSmsAuthModal(data) {
|
||||
@@ -218,44 +228,39 @@
|
||||
clearInterval(resendTimer);
|
||||
$('#smsAuthModal').modal('hide');
|
||||
}
|
||||
function changePwd(){
|
||||
|
||||
function checkPwd(){
|
||||
if ($("input[name=resetUserId]").val() == null || $("input[name=resetUserId]").val().length == 0 ){
|
||||
alert("<%= localeMessage.getString("login.checkid") %>");
|
||||
$("input[name=resetUserId]").trigger("focus");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if ($("input[name=resetPassword]").val() == null || $("input[name=resetPassword]").val().trim().length == 0 ){
|
||||
alert("<%= localeMessage.getString("login.checkpwd1") %>");
|
||||
$("input[name=resetPassword]").trigger("focus");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if ($("input[name=changePassword]").val() == null || $("input[name=changePassword]").val().trim().length == 0 ){
|
||||
alert("<%= localeMessage.getString("login.checkpwd2") %>");
|
||||
$("input[name=changePassword]").trigger("focus");
|
||||
return ;
|
||||
return false;
|
||||
}
|
||||
if ($("input[name=confirmPassword]").val() == null || $("input[name=confirmPassword]").val().trim().length == 0 ){
|
||||
alert("<%= localeMessage.getString("login.checkpwd3") %>");
|
||||
return ;
|
||||
return false;
|
||||
}
|
||||
if ($("input[name=confirmPassword]").val() != $("input[name=confirmPassword]").val()){
|
||||
if ($("input[name=changePassword]").val() != $("input[name=confirmPassword]").val()){
|
||||
alert("<%= localeMessage.getString("login.checkpwd4") %>");
|
||||
$("input[name=confirmPassword]").trigger("focus");
|
||||
return ;
|
||||
return false;
|
||||
}
|
||||
if ($("input[name=resetUserId]").val() == $("input[name=changePassword]").val()){
|
||||
alert("<%= localeMessage.getString("login.checkpwd5") %>");
|
||||
$("input[name=changePassword]").trigger("focus");
|
||||
return ;
|
||||
return false;
|
||||
}
|
||||
|
||||
//if (idCheck.checked){
|
||||
// setCookie("key",id);
|
||||
//}
|
||||
<%-- $("form").attr("action","<c:url value="/changePassword.do"/>");
|
||||
//$("form").action = "<%=request.getContextPath()%>/changePassword.do";
|
||||
--%>
|
||||
$("form").submit();
|
||||
return true;
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
@@ -271,14 +276,22 @@
|
||||
});
|
||||
$("input[name=changePassword]").keydown(function(event){
|
||||
if ( event.which == 13 ) {
|
||||
changePwd();
|
||||
$('#modalLoginForm').submit();
|
||||
}
|
||||
});
|
||||
$("input[name=confirmPassword]").keydown(function(event){
|
||||
if ( event.which == 13 ) {
|
||||
changePwd();
|
||||
$('#modalLoginForm').submit();
|
||||
}
|
||||
});
|
||||
// Password Change 링크로 직접 열 때만 초기화 (JS에서 modal('show') 호출 시 relatedTarget 없음)
|
||||
$('#pwdChgModal').on('show.bs.modal', function(event) {
|
||||
if (event.relatedTarget) {
|
||||
$('#modalLoginForm')[0].reset();
|
||||
$('#changeInitPassword').show();
|
||||
}
|
||||
});
|
||||
|
||||
$("select[name=serviceType]").change(function(){
|
||||
fncPreMain();
|
||||
});
|
||||
@@ -286,9 +299,7 @@
|
||||
$("#btn_login").click(function(){
|
||||
fncPreMain();
|
||||
});
|
||||
$("#changePwd").click(function(){
|
||||
changePwd();
|
||||
});
|
||||
|
||||
$("#btn_sso_login").click(function(){
|
||||
fncSsoLogin();
|
||||
});
|
||||
@@ -366,7 +377,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 비밀번호 변경 모달 -->
|
||||
<div class="modal fade" id="pwdChgModal" tabindex="-1" role="dialog" aria-labelledby="pwdChgModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
@@ -376,13 +387,15 @@
|
||||
<span aria-hidden="true">X</span>
|
||||
</button>
|
||||
</div>
|
||||
<form name="form" id="modalLoginForm" action="<c:url value="/changePassword.do"/>" method="post">
|
||||
<form id="modalLoginForm" action="<c:url value="/changePassword.do"/>" method="post" onsubmit="return checkPwd()" novalidate>
|
||||
<div class="modal-body">
|
||||
<div class="form-group d-flex">
|
||||
<input type="text" name="resetUserId" class="form-control rounded-left" placeholder="User Id" required>
|
||||
</div>
|
||||
<div class="form-group d-flex">
|
||||
<input type="password" name="resetPassword" class="form-control rounded-left" placeholder="<%= localeMessage.getString("login.placeholderCurrentPassword") %>" autocomplete="off" required>
|
||||
<div id="changeInitPassword">
|
||||
<div class="form-group d-flex">
|
||||
<input type="text" name="resetUserId" class="form-control rounded-left" placeholder="User Id" required>
|
||||
</div>
|
||||
<div class="form-group d-flex">
|
||||
<input type="password" name="resetPassword" class="form-control rounded-left" placeholder="<%= localeMessage.getString("login.placeholderCurrentPassword") %>" autocomplete="off" required>
|
||||
</div>
|
||||
</div>
|
||||
<p><%= localeMessage.getString("login.toChangePasswordMessage") %></p>
|
||||
<div class="form-group d-flex">
|
||||
@@ -391,9 +404,12 @@
|
||||
<div class="form-group d-flex">
|
||||
<input type="password" name="confirmPassword" class="form-control rounded-left" placeholder="<%= localeMessage.getString("login.placeholderConfirmationPassword") %>" autocomplete="off" required>
|
||||
</div>
|
||||
<span style="font-size:0.8em; color:red">
|
||||
※ 7글자 이상 & 영문/숫자/특수문자 2종류 이상
|
||||
</span>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<input type="button" id ="changePwd" class="form-control btn btn-primary rounded submit px-3" value="변경">
|
||||
<button type="submit" id="changePwd" class="form-control btn btn-primary rounded submit px-3">변경</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -52,6 +52,13 @@
|
||||
var urlParam = window.document.location.search;
|
||||
var mod = "";
|
||||
var strServiceType = "<%=request.getParameter("serviceType")%>";
|
||||
function getServiceType() {
|
||||
var serviceType = sessionStorage.getItem("serviceType");
|
||||
if (!serviceType || serviceType == "undefined" || serviceType == "null") {
|
||||
serviceType = localStorage.getItem("serviceType") || "";
|
||||
}
|
||||
return serviceType;
|
||||
}
|
||||
if(urlParam != ""){
|
||||
if(urlParam.indexOf("mod=") > -1){
|
||||
mod = urlParam.substring((urlParam.indexOf("mod=") + 4), urlParam.lastIndexOf("&"));
|
||||
@@ -67,15 +74,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$(document).ready(function() {
|
||||
var url = '<%=topPage%>';
|
||||
if (url.indexOf("?")>=0){
|
||||
url = url + "&serviceType="+ sessionStorage["serviceType"];
|
||||
}else{
|
||||
url = url + "?serviceType="+ sessionStorage["serviceType"];
|
||||
var serviceType = getServiceType();
|
||||
if (serviceType) {
|
||||
if (url.indexOf("?")>=0){
|
||||
url = url + "&serviceType="+ serviceType;
|
||||
}else{
|
||||
url = url + "?serviceType="+ serviceType;
|
||||
}
|
||||
}
|
||||
|
||||
$("#topFrame").attr('src',url);
|
||||
|
||||
$("#topFrame").attr('src',url);
|
||||
|
||||
$(".topMenu").load(function(){ // iframe이 모두 load된후 제어
|
||||
$(".topMenu").contents().find('.gnb').on("mouseenter", function(e){
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
<%@page import="com.eactive.eai.rms.common.util.StringUtils" %>
|
||||
<%@page import="com.eactive.eai.rms.common.datasource.DataSourceType" %>
|
||||
<%@page import="com.eactive.eai.rms.common.datasource.DataSourceTypeManager" %>
|
||||
<%@page import="com.eactive.eai.common.util.ApplicationContextProvider" %>
|
||||
<%@page import="com.eactive.eai.rms.data.entity.man.role.RoleMenuAuthService" %>
|
||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
|
||||
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
|
||||
<%@ include file="/jsp/common/include/localemessage.jsp" %>
|
||||
@@ -16,6 +18,12 @@
|
||||
<%
|
||||
String serviceKey = SessionManager.getServiceTypeKey(request);
|
||||
String serviceText = DataSourceTypeManager.getDataSourceType(serviceKey).getText();
|
||||
|
||||
final String SITEMAP_MENU_ID = "0510013";
|
||||
RoleMenuAuthService roleMenuAuthService = ApplicationContextProvider.getContext().getBean(RoleMenuAuthService.class);
|
||||
String sitemapMenuAuth = roleMenuAuthService.getRoleList(SessionManager.getUserId(request), SITEMAP_MENU_ID, serviceKey);
|
||||
boolean showSitemapLink = "R".equals(sitemapMenuAuth) || "W".equals(sitemapMenuAuth);
|
||||
|
||||
String roleScreenId = (String) request.getAttribute("roleScreenId");
|
||||
String mainPage = (String) request.getAttribute("mainPage");
|
||||
String menuId = (String) request.getAttribute("menuId");
|
||||
@@ -240,6 +248,12 @@
|
||||
|
||||
<link rel="stylesheet" type="text/css" media="screen" href="<c:url value="/css/web_ui.css"/>"/>
|
||||
<link rel="stylesheet" type="text/css" media="screen" href="<c:url value="/css/theme_${themeColor}.css"/>"/>
|
||||
<style>
|
||||
.topmenu_box .sitemap-link { display: inline-block; width: 80px; height: 80px; line-height: 80px; box-sizing: border-box; font-size: 14px; color: #666; text-align: center; opacity: 0.7; transition: all 0.3s; margin-top: 0px; }
|
||||
.topmenu_box .sitemap-link:hover { opacity: 1; }
|
||||
.topmenu_box .logout-link { line-height: 80px; margin-top:0; }
|
||||
.topmenu_box > ul li:last-child > a:before { display: none; }
|
||||
</style>
|
||||
<script language="javascript" src="<c:url value="/js/jquery-1.12.1.min.js"/>"></script>
|
||||
<script language="javascript" src="<c:url value="/js/prefixfree.min.js"/>"></script>
|
||||
<script language="javascript" src="<c:url value="/js/jquery.cookie.js"/>"></script>
|
||||
@@ -303,11 +317,11 @@
|
||||
}
|
||||
|
||||
//왼쪽메뉴, 메인 페이지 이동
|
||||
function goPage2(page1, page2, page2_id) {
|
||||
function goPage2(page1, page2, page2_id) {
|
||||
parent.leftFrame.location.href = goNavUrl(page1);
|
||||
parent.mainFrame.location.href = goNavUrl(page2);
|
||||
}
|
||||
|
||||
|
||||
var sessionAjax = createAjaxRequest();
|
||||
|
||||
function getAjaxData() {
|
||||
@@ -529,6 +543,7 @@
|
||||
console.log("selectLocaleValue : " + selectLocaleValue);
|
||||
parent.changeLocale($(this).val());
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
$(function() {
|
||||
@@ -559,13 +574,20 @@
|
||||
<span style="display:block;font-size:10px;"><%=localeMessage.getString("screen.lastLogin") %> : <%=LastLoginYms%> [ <%=LastLoginIp%> ]</span>
|
||||
<% } %>
|
||||
</a></li>
|
||||
<li>
|
||||
<select id="selectLocale" name="locale" style="margin-top: 20px; width:80px">
|
||||
<li style="width:100px; line-height:80px; text-align:center;">
|
||||
<select id="selectLocale" name="locale" style="width:60px; padding: 2px; text-align:center;">
|
||||
<option value="en">English</option>
|
||||
<option value="ko">한글</option>
|
||||
</select>
|
||||
</li>
|
||||
<li onclick="logout()"><a href="#" class=""><img src="<c:url value="/img/icon_logout.png"/>"
|
||||
<% if (showSitemapLink) { %>
|
||||
<li>
|
||||
<a href="<c:url value="/common/acl/sitemap/sitemapMan.view"><c:param name="menuId" value="<%=SITEMAP_MENU_ID%>"/><c:param name="serviceType" value="<%=serviceKey%>"/></c:url>" target="mainFrame" class="sitemap-link">
|
||||
<%= localeMessage.getString("screen.sitemap") %>
|
||||
</a>
|
||||
</li>
|
||||
<% } %>
|
||||
<li onclick="logout()"><a href="#" class="logout-link"><img src="<c:url value="/img/icon_logout.png"/>"
|
||||
alt=""/><%= localeMessage.getString("screen.logout") %>
|
||||
</a></li>
|
||||
</ul>
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
/**
|
||||
* 문자열 마스킹 처리를 위한 유틸리티
|
||||
*
|
||||
* 보안아키텍처 표준 마스킹 규칙(Java MaskingUtils)과 동일한 결과를 내도록 구현한다.
|
||||
* 이름 2자: 뒤 1자리(가나→가*) / 3자 이상: 앞뒤 제외 가운데 전체(가나다라→가**라)
|
||||
* 이메일 아이디 앞2·뒤2 마스킹, 4자 이하 전체(test→****, test123→**st1**)
|
||||
* 전화 2·3번째 세그먼트 각각 뒤 2자리(010-1234-1234→010-12**-12**)
|
||||
*/
|
||||
const StringMaskingUtil = {
|
||||
|
||||
@@ -8,41 +13,51 @@ const StringMaskingUtil = {
|
||||
return str != null && str !== '';
|
||||
},
|
||||
|
||||
// 이름 마스킹 처리
|
||||
// '*' 반복 생성
|
||||
_stars: function(count) {
|
||||
return count > 0 ? '*'.repeat(count) : '';
|
||||
},
|
||||
|
||||
// 세그먼트 뒤 count 자리 마스킹
|
||||
_maskSegmentTail: function(segment, count) {
|
||||
if (segment.length <= count) {
|
||||
return this._stars(segment.length);
|
||||
}
|
||||
return segment.substring(0, segment.length - count) + this._stars(count);
|
||||
},
|
||||
|
||||
// 이름 마스킹 처리 (2자: 뒤 1자리, 3자 이상: 앞뒤 제외 가운데 전체)
|
||||
maskName: function(name) {
|
||||
if (!this.isValidString(name)) {
|
||||
return name;
|
||||
}
|
||||
return name.length > 1 ?
|
||||
name.substring(0, name.length - 1) + "*" :
|
||||
name;
|
||||
const len = name.length;
|
||||
if (len === 1) {
|
||||
return name;
|
||||
}
|
||||
if (len === 2) {
|
||||
return name.charAt(0) + '*';
|
||||
}
|
||||
return name.charAt(0) + this._stars(len - 2) + name.charAt(len - 1);
|
||||
},
|
||||
|
||||
// 이메일 마스킹 처리
|
||||
// 이메일 마스킹 처리 (아이디 앞2·뒤2, 4자 이하 전체)
|
||||
maskEmail: function(email) {
|
||||
if (!this.isValidString(email) || !email.includes('@')) {
|
||||
return email;
|
||||
}
|
||||
|
||||
const parts = email.split('@');
|
||||
if (parts.length !== 2) {
|
||||
return email;
|
||||
const atIdx = email.indexOf('@');
|
||||
const local = email.substring(0, atIdx);
|
||||
const domain = email.substring(atIdx);
|
||||
|
||||
if (local.length <= 4) {
|
||||
return this._stars(local.length) + domain;
|
||||
}
|
||||
|
||||
const localPart = parts[0];
|
||||
let maskedLocal;
|
||||
|
||||
if (localPart.length <= 3) {
|
||||
maskedLocal = localPart.substring(0, 1) +
|
||||
'*'.repeat(localPart.length - 1);
|
||||
} else {
|
||||
maskedLocal = localPart.substring(0, localPart.length - 3) + '***';
|
||||
}
|
||||
|
||||
return maskedLocal + '@' + parts[1];
|
||||
return this._stars(2) + local.substring(2, local.length - 2) + this._stars(2) + domain;
|
||||
},
|
||||
|
||||
// 휴대폰 번호 마스킹 처리
|
||||
// 휴대폰/전화 번호 마스킹 처리 (2·3번째 세그먼트 각각 뒤 2자리)
|
||||
maskMobileNumber: function(number) {
|
||||
if (!this.isValidString(number)) {
|
||||
return number;
|
||||
@@ -50,8 +65,10 @@ const StringMaskingUtil = {
|
||||
|
||||
const parts = number.split('-');
|
||||
if (parts.length === 3) {
|
||||
return parts[0] + '-****-' + parts[2];
|
||||
return parts[0] + '-' +
|
||||
this._maskSegmentTail(parts[1], 2) + '-' +
|
||||
this._maskSegmentTail(parts[2], 2);
|
||||
}
|
||||
return number;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
var url = '<c:url value="/onl/admin/authserver/scopeMan.json" />';
|
||||
var url_view = '<c:url value="/onl/admin/authserver/scopeMan.view" />';
|
||||
var api_url_view = '<c:url value="/onl/apim/apigroup/apiGroupMan.view" />';
|
||||
var mapping_url = '<c:url value="/onl/admin/authserver/apiScopeMan.json" />';
|
||||
|
||||
var isDetail = false;
|
||||
function isValid(){
|
||||
@@ -65,9 +64,9 @@ function mappingInfo(key){
|
||||
|
||||
$.ajax({
|
||||
type : "POST",
|
||||
url: mapping_url,
|
||||
url: url,
|
||||
dataType: "json",
|
||||
data: {cmd: 'LIST', searchScopeId : key},
|
||||
data: {cmd: 'API_LIST', searchScopeId : key},
|
||||
success: function(json){
|
||||
var mappingData = json.rows;
|
||||
|
||||
@@ -131,45 +130,6 @@ function init(key, callback){
|
||||
}
|
||||
}
|
||||
|
||||
function saveApiGridData(scopeId, returnUrl) {
|
||||
var postData = [];
|
||||
var rows = $("#selectedApiGrid").jqGrid('getRowData');
|
||||
|
||||
$.each(rows, function(index, item) {
|
||||
item.scopeId = scopeId;
|
||||
});
|
||||
|
||||
postData.push({
|
||||
name: "apiList",
|
||||
value: JSON.stringify(rows)
|
||||
});
|
||||
|
||||
postData.push({ name: "cmd" , value:"INSERT_APILIST" });
|
||||
postData.push({ name: "scopeId" , value:scopeId });
|
||||
|
||||
$.ajax({
|
||||
type : "POST",
|
||||
url:mapping_url,
|
||||
data: postData,
|
||||
beforeSend: function() {
|
||||
$("[id^='btn_']").prop("disabled", true);
|
||||
$("#btn_modify").text("처리중 . . . . .");
|
||||
},
|
||||
success:function(args){
|
||||
alert("저장 되었습니다.");
|
||||
|
||||
goNav(returnUrl);//LIST로 이동
|
||||
},
|
||||
error:function(e){
|
||||
alert(e.responseText);
|
||||
},
|
||||
complete: function() {
|
||||
$("[id^='btn_']").prop("disabled", false);
|
||||
$("#btn_modify").text("수정");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
var returnUrl = getReturnUrlForReturn();
|
||||
var key ="${param.scopeId}";
|
||||
@@ -182,28 +142,39 @@ $(document).ready(function() {
|
||||
if (!isValid()){
|
||||
return;
|
||||
}
|
||||
var scopeId = $('input[name=scopeId]').val();
|
||||
var postData = $('#ajaxForm').serializeArray();
|
||||
|
||||
var rows = $("#selectedApiGrid").jqGrid('getRowData');
|
||||
$.each(rows, function(index, item) {
|
||||
item.scopeId = scopeId;
|
||||
});
|
||||
postData.push({ name: "apiList" , value: JSON.stringify(rows) });
|
||||
|
||||
if (isDetail){
|
||||
postData.push({ name: "cmd" , value:"UPDATE"});
|
||||
}else{
|
||||
postData.push({ name: "cmd" , value:"INSERT"});
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
type : "POST",
|
||||
url:url,
|
||||
data:postData,
|
||||
beforeSend: function() {
|
||||
$("[id^='btn_']").prop("disabled", true);
|
||||
$("#btn_modify").text("처리중 . . . . .");
|
||||
},
|
||||
success:function(args){
|
||||
alert("SCOPE 정보가 저장 되었습니다. 이어서 API 리스트를 저장합니다.");
|
||||
|
||||
if(!isDetail) { // INSERT 일 경우 ScopeId를 업데이트
|
||||
key = $('input[name=scopeId]').val();
|
||||
}
|
||||
saveApiGridData(key, returnUrl); // API LIST 저장.
|
||||
|
||||
|
||||
alert("저장 되었습니다.");
|
||||
goNav(returnUrl);//LIST로 이동
|
||||
},
|
||||
error:function(e){
|
||||
alert(e.responseText);
|
||||
},
|
||||
complete: function() {
|
||||
$("[id^='btn_']").prop("disabled", false);
|
||||
$("#btn_modify").text("수정");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -250,6 +250,29 @@ var url_view = '<c:url value="/onl/admin/common/propertyMan.view" />';
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
$("#btn_pop_encrypt").click(function() {
|
||||
var prpty2Val = $("input[name=prpty2Val]").val();
|
||||
if (prpty2Val == '') {
|
||||
alert("프라퍼티 값을 입력하세요");
|
||||
return;
|
||||
}
|
||||
var postData = {
|
||||
cmd : "ENCRYPT",
|
||||
prpty2Val : prpty2Val,
|
||||
};
|
||||
$.ajax({
|
||||
type : "POST",
|
||||
url : url,
|
||||
data : postData,
|
||||
success : function(args) {
|
||||
$("input[name=prpty2Val]").val(args);
|
||||
},
|
||||
error : function(e) {
|
||||
alert(e.responseText);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
buttonControl(key);
|
||||
titleControl(key);
|
||||
@@ -289,12 +312,17 @@ var url_view = '<c:url value="/onl/admin/common/propertyMan.view" />';
|
||||
<table class="table_row" cellspacing="0">
|
||||
<tr>
|
||||
<th style="width:20%;"><%= localeMessage.getString("propertyDetail.propertyKey") %></th>
|
||||
<td><input type="text" name="prptyName" style="width:calc(100% - 85px);" /> <!-- <img src="<c:url value="/img/btn_pop_input.png"/>" id="btn_pop_input" /></td> -->
|
||||
<button type="button" class="cssbtn smallBtn2" id="btn_pop_input" style="vertical-align:middle; font-weight:bold;"><i class="material-icons">input</i><%= localeMessage.getString("button.input") %></button>
|
||||
<td>
|
||||
<input type="text" name="prptyName" style="width:calc(100% - 85px);" /> <!-- <img src="<c:url value="/img/btn_pop_input.png"/>" id="btn_pop_input" /></td> -->
|
||||
<button type="button" class="cssbtn smallBtn2" id="btn_pop_input" style="vertical-align:middle; font-weight:bold;"><i class="material-icons">input</i><%= localeMessage.getString("button.input") %></button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><%= localeMessage.getString("propertyDetail.propertyValue") %></th>
|
||||
<td><input type="text" name="prpty2Val"/></td>
|
||||
<th style="width:20%;"><%= localeMessage.getString("propertyDetail.propertyValue") %></th>
|
||||
<td>
|
||||
<input type="text" name="prpty2Val" style="width:calc(100% - 85px);"/>
|
||||
<button type="button" class="cssbtn smallBtn2" id="btn_pop_encrypt" style="vertical-align:middle; font-weight:bold;"><i class="material-icons">lock</i><%= localeMessage.getString("button.encode") %></button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- grid -->
|
||||
|
||||
@@ -71,11 +71,11 @@
|
||||
|
||||
$('#grid').children().remove();
|
||||
|
||||
const itemTypes = ['Field', 'Grid', 'Group', 'Attr'];
|
||||
const itemTypes = ['Field', 'Grid', 'Group', 'Attr', 'Array'];
|
||||
|
||||
columns = [ // for columnData prop
|
||||
{ title: '<%= localeMessage.getString("layoutMan.itmeEn") %>', name: 'loutItemName', type: 'text', width:280, resize:true, minWidth:120 },
|
||||
{ title: '<%= localeMessage.getString("layoutMan.itemDesc") %>', name: 'loutItemDesc', type: 'text', width:280, resize:true, minWidth:120 },
|
||||
{ title: '<%= localeMessage.getString("layoutMan.itmeEn") %>', name: 'loutItemName', type: 'text', width:280, resize:true, minWidth:120 },
|
||||
{ title: '<%=localeMessage.getString("standardLayout.itemLevel")%>', name: 'loutItemDepth', type: 'text', width:50 },
|
||||
{ title: '<%= localeMessage.getString("standardLayout.itemType") %>', name: 'loutItemType', type: 'autocomplete', source: itemTypes, width:70 },
|
||||
{ title: '<%= localeMessage.getString("standardLayout.arraySize") %>', name: 'loutItemOccCnt', type: 'text', width:90 },
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
const LAYOUT_ITEM_TYPE_GROUP = "Group";
|
||||
const LAYOUT_ITEM_TYPE_GRID = "Grid";
|
||||
const LAYOUT_ITEM_TYPE_FIELD = "Field";
|
||||
const LAYOUT_ITEM_TYPE_ARRAY = "Array";
|
||||
|
||||
var isDetail = false;
|
||||
|
||||
@@ -550,6 +551,14 @@
|
||||
return data;
|
||||
}
|
||||
|
||||
// 항목 자신이 Array인 경우, 경로/변환명령 끝에 반복 표시 [*]를 추가
|
||||
function addArrayTag(loutItemType, data) {
|
||||
if (loutItemType == LAYOUT_ITEM_TYPE_ARRAY && !data.endsWith("[*]")) {
|
||||
data = data + "[*]";
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
//변환명에 있는 서비스코드와 if서비스 코드 매칭
|
||||
function validation(){
|
||||
if ($('input[name=cnvsnName]').val() == '') {
|
||||
@@ -889,10 +898,10 @@
|
||||
let selectValue = "";
|
||||
let targetValue = "";
|
||||
for ( var i = 0; i < sourceData.length; i++) {
|
||||
if (sourceData[i]['loutItemType'] != "Field" && sourceData[i]['loutItemType'] != "Attr") continue;
|
||||
if (sourceData[i]['loutItemType'] != "Field" && sourceData[i]['loutItemType'] != "Attr" && sourceData[i]['loutItemType'] != "Array") continue;
|
||||
selectValue = (sourceData[i]['LOUTITEMPATH']).substr(sourceData[i]['loutName'].length+1);
|
||||
for (var j = 0; j <targetData.length; j++) {
|
||||
if (targetData[j]['loutItemType'] != "Field" && targetData[j]['loutItemType'] != "Attr") continue;
|
||||
if (targetData[j]['loutItemType'] != "Field" && targetData[j]['loutItemType'] != "Attr" && targetData[j]['loutItemType'] != "Array") continue;
|
||||
targetValue = (targetData[j]['LOUTITEMPATH']).substr(targetData[j]['loutName'].length+1);
|
||||
if (targetValue == selectValue ) {
|
||||
var data = sourceData[i]['LOUTITEMPATH'];
|
||||
@@ -916,8 +925,8 @@
|
||||
//field, ATTR 여부 판단
|
||||
var sourceRow = sourceGrid.getRowData( srcRowId );
|
||||
var targetRow = targetGrid.getRowData( tgtRowId );
|
||||
if ( (sourceRow["loutItemType"] == 'Field' || sourceRow["loutItemType"] == 'Attr') &&
|
||||
(targetRow["loutItemType"] == 'Field' || targetRow["loutItemType"] == 'Attr') ){
|
||||
if ( (sourceRow["loutItemType"] == 'Field' || sourceRow["loutItemType"] == 'Attr' || sourceRow["loutItemType"] == 'Array') &&
|
||||
(targetRow["loutItemType"] == 'Field' || targetRow["loutItemType"] == 'Attr' || targetRow["loutItemType"] == 'Array') ){
|
||||
;
|
||||
}else{
|
||||
alert('<%=localeMessage.getString("transformManDetail.alert3")%>');
|
||||
@@ -936,15 +945,15 @@
|
||||
var targetValue = "";
|
||||
var j=tgtIndex-1-1;
|
||||
for ( var i = srcIndex-1; i < sourceData.length; i++) {
|
||||
if (sourceData[i]['loutItemType'] != LAYOUT_ITEM_TYPE_FIELD) continue;
|
||||
if (sourceData[i]['loutItemType'] != LAYOUT_ITEM_TYPE_FIELD && sourceData[i]['loutItemType'] != LAYOUT_ITEM_TYPE_ARRAY) continue;
|
||||
j++;
|
||||
while (targetData.length -1 >= j &&targetData[j]["loutItemType"] != LAYOUT_ITEM_TYPE_FIELD){
|
||||
while (targetData.length -1 >= j && targetData[j]["loutItemType"] != LAYOUT_ITEM_TYPE_FIELD && targetData[j]["loutItemType"] != LAYOUT_ITEM_TYPE_ARRAY){
|
||||
j++;
|
||||
}
|
||||
if (targetData.length -1 < j) break;
|
||||
var targetRowId = targetData[j]['id'];
|
||||
targetGrid.jqGrid('setCell',targetRowId,'CNVSNCMDNAME',addGroupTag(srcGroup,sourceData[i]['LOUTITEMPATH']));
|
||||
targetGrid.jqGrid('setCell',targetRowId,'CNVSNRSULTITEMPATHNAME',addGroupTag(tgtGroup,targetData[j]['LOUTITEMPATH']));
|
||||
targetGrid.jqGrid('setCell',targetRowId,'CNVSNCMDNAME',addArrayTag(sourceData[i]['loutItemType'], addGroupTag(srcGroup,sourceData[i]['LOUTITEMPATH'])));
|
||||
targetGrid.jqGrid('setCell',targetRowId,'CNVSNRSULTITEMPATHNAME',addArrayTag(targetData[j]['loutItemType'], addGroupTag(tgtGroup,targetData[j]['LOUTITEMPATH'])));
|
||||
targetGrid.jqGrid('setCell',targetRowId,'CNVSNITEMSERNO',targetData[j]['loutItemSerno']);
|
||||
|
||||
const sourceEndpointElementId = sourceGrid.getEndpointElementId(sourceData[i]['id']);
|
||||
@@ -1015,7 +1024,7 @@
|
||||
var targetData = targetGrid.getRowData();
|
||||
var gridData = new Array();
|
||||
for (var i = 0; i <targetData.length; i++) {
|
||||
if ((targetData[i]['loutItemType'] != "Field") && (targetData[i]['loutItemType'] != "Attr")) {
|
||||
if ((targetData[i]['loutItemType'] != "Field") && (targetData[i]['loutItemType'] != "Attr") && (targetData[i]['loutItemType'] != "Array")) {
|
||||
continue;
|
||||
}
|
||||
if (targetData[i]['CNVSNCMDNAME']==null
|
||||
@@ -1158,7 +1167,7 @@
|
||||
function srcformatterFunction(cellvalue,options,rowObject){
|
||||
var rowId = options["rowId"];
|
||||
var loutItemType = rowObject["loutItemType"];
|
||||
if (loutItemType == 'Field' || loutItemType == 'Attr'){
|
||||
if (loutItemType == 'Field' || loutItemType == 'Attr' || loutItemType == 'Array'){
|
||||
return "<div id='div_src_"+rowId+"' name='src_"+rowId+"' style='width:28px;height:17px;background-color: #D12F35; border-color: #ab1717; border-style: solid; border-width: 1px; border-radius: 3px;' />";
|
||||
}else{
|
||||
return "<div id='div_srcgroup_"+rowId+"' name='srcgroup_"+rowId+"' style='width:28px;height:17px;' />";
|
||||
@@ -1170,7 +1179,7 @@
|
||||
function tgtformatterFunction(cellvalue,options,rowObject){
|
||||
var rowId = options["rowId"];
|
||||
var loutItemType = rowObject["loutItemType"];
|
||||
if (loutItemType == 'Field' || loutItemType == 'Attr'){
|
||||
if (loutItemType == 'Field' || loutItemType == 'Attr' || loutItemType == 'Array'){
|
||||
return "<div id='div_tgt_"+rowId+"_base' name='div_tgt_"+rowId+"_base' style='width:30px;height:20px; background-color:gray;' >"
|
||||
+ "<div id='div_tgt_"+rowId+"' name='tgt_"+rowId+"' style='width:28px;height:17px; background-color: #3E7E9C; border-color: #217ca7; border-style: solid; border-width: 1px; border-radius: 3px;' onclick='jqGridOnTargetGridEndpointClick(this)'>"
|
||||
+ "</div></div>";
|
||||
@@ -1669,8 +1678,8 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if ((sourceRow["loutItemType"] != 'Field' && sourceRow["loutItemType"] != 'Attr')
|
||||
|| (targetRow["loutItemType"] != 'Field' && targetRow["loutItemType"] != 'Attr')) {
|
||||
if ((sourceRow["loutItemType"] != 'Field' && sourceRow["loutItemType"] != 'Attr' && sourceRow["loutItemType"] != 'Array')
|
||||
|| (targetRow["loutItemType"] != 'Field' && targetRow["loutItemType"] != 'Attr' && targetRow["loutItemType"] != 'Array')) {
|
||||
alert('<%=localeMessage.getString("transformManDetail.alert3")%>');
|
||||
return ;
|
||||
}
|
||||
@@ -1684,7 +1693,7 @@
|
||||
|
||||
if (targetRow['CNVSNCMDNAME'] == '') {
|
||||
// 변환 명령 값이 한번 설정 되면 변경 되지 않게 함.
|
||||
var cnvsnCmdName = addGroupTag(srcGroup,sourceRow["LOUTITEMPATH"]);
|
||||
var cnvsnCmdName = addArrayTag(sourceRow["loutItemType"], addGroupTag(srcGroup,sourceRow["LOUTITEMPATH"]));
|
||||
// function까지 처리되도록 수정했으나, parameter가 하나인 경우에만 정상처리됨
|
||||
if($('#functionCombo').val() != "") {
|
||||
cnvsnCmdName = $('#functionCombo').val() + "(" + cnvsnCmdName + ")";
|
||||
@@ -1693,7 +1702,7 @@
|
||||
targetGrid.jqGrid('setCell',tgtRowId,'CNVSNCMDNAME', cnvsnCmdName);
|
||||
}
|
||||
|
||||
targetGrid.jqGrid('setCell',tgtRowId,'CNVSNRSULTITEMPATHNAME',addGroupTag(tgtGroup,targetRow["LOUTITEMPATH"]));
|
||||
targetGrid.jqGrid('setCell',tgtRowId,'CNVSNRSULTITEMPATHNAME',addArrayTag(targetRow["loutItemType"], addGroupTag(tgtGroup,targetRow["LOUTITEMPATH"])));
|
||||
targetGrid.jqGrid('setCell',tgtRowId,'CNVSNITEMSERNO',targetRow['loutItemSerno']);
|
||||
targetGrid.jqGrid('setCell',tgtRowId,'CNVSNSRCITEMS', cnvsnSrcItems);
|
||||
targetGrid.jqGrid('setCell',tgtRowId,'ISVALIDMAPPING', true);
|
||||
@@ -1885,8 +1894,8 @@
|
||||
if (loutItemType == LAYOUT_ITEM_TYPE_GRID || loutItemType == LAYOUT_ITEM_TYPE_GROUP) {
|
||||
// group 인 경우
|
||||
items = createGridGroupContextMenuItems(grid, rowId);
|
||||
} else if(loutItemType == LAYOUT_ITEM_TYPE_FIELD) {
|
||||
// field 인 경우
|
||||
} else if(loutItemType == LAYOUT_ITEM_TYPE_FIELD || loutItemType == LAYOUT_ITEM_TYPE_ARRAY) {
|
||||
// field, array 인 경우
|
||||
items = createFieldContextMenuItems(grid, positionElement.attr('id'));
|
||||
} else {
|
||||
return false;
|
||||
@@ -1989,7 +1998,7 @@
|
||||
break;
|
||||
}
|
||||
|
||||
if (loutItemType == LAYOUT_ITEM_TYPE_FIELD) {
|
||||
if (loutItemType == LAYOUT_ITEM_TYPE_FIELD || loutItemType == LAYOUT_ITEM_TYPE_ARRAY) {
|
||||
loutItemPath = loutItemPath.replace(new RegExp("^" + prefix), "");
|
||||
console.debug(grid.getId() + " : loutItemPath : " + loutItemPath);
|
||||
rowData['GROUP_LOUTITEMPATH'] = loutItemPath;
|
||||
|
||||
@@ -300,6 +300,10 @@
|
||||
|
||||
<div class="title">Client 정보</div>
|
||||
<table id="client" class="table_row" cellspacing="0">
|
||||
<colgroup>
|
||||
<col style="width:20%"/>
|
||||
<col style="width:80%"/>
|
||||
</colgroup>
|
||||
<tr>
|
||||
<th style="width:20%;">이름</th>
|
||||
<td><span id="clientName"></span></td>
|
||||
|
||||
@@ -131,6 +131,24 @@
|
||||
// var reverseNumber = records - ((page - 1) * rowNum + i);
|
||||
$(this).setCell(rows[i], 'rowNum', number);
|
||||
}
|
||||
|
||||
// 승인 상태별 행 배경색: 요청됨(#fff3cd) / 진행중(#d1ecf1) 강조
|
||||
if (d && d.rows) {
|
||||
for (var j = 0; j < d.rows.length; j++) {
|
||||
var st = d.rows[j].approvalStatus || {};
|
||||
var code = st.code || '';
|
||||
var desc = st.description || '';
|
||||
var bg = '';
|
||||
if (code === 'REQUESTED' || desc === '요청됨') {
|
||||
bg = '#fff3cd';
|
||||
} else if (code === 'PROCESSING' || desc === '진행중') {
|
||||
bg = '#d1ecf1';
|
||||
}
|
||||
if (bg) {
|
||||
$('#grid tr#' + $.jgrid.jqID(d.rows[j].id)).css('background-color', bg);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
loadError: function(jqXHR, textStatus, errorThrown) {
|
||||
var location = '<%=request.getContextPath()%>/';
|
||||
@@ -176,7 +194,7 @@
|
||||
<div class="title">승인 요청 목록<span class="tooltip">승인 요청을 관리합니다.</span></div>
|
||||
<c:if test="${hardDeleteEnabled}">
|
||||
<div style="background:#fff3cd; border:1px solid #ffc107; padding:8px 15px; margin:5px 0; border-radius:4px; color:#856404;">
|
||||
<strong>[주의]</strong> 선택 삭제 시 승인 요청과 승인자 이력이 DB에서 영구 삭제됩니다.
|
||||
<strong>[주의]</strong> 삭제 기능은 <strong>개발(dev) 환경에서만</strong> 동작합니다. 선택 삭제 시 승인 요청과 승인자 이력이 DB에서 영구 삭제됩니다.
|
||||
</div>
|
||||
</c:if>
|
||||
<form id="ajaxForm" onsubmit="return false;">
|
||||
|
||||
@@ -392,15 +392,21 @@
|
||||
|
||||
<div class="title">사용자 정보</div>
|
||||
<table class="table_row" cellspacing="0">
|
||||
<colgroup>
|
||||
<col style="width:20%"/>
|
||||
<col style="width:30%"/>
|
||||
<col style="width:20%"/>
|
||||
<col style="width:30%"/>
|
||||
</colgroup>
|
||||
<tr>
|
||||
<th style="width:20%;">이메일 아이디</th>
|
||||
<td style="width:30%;" colspan="3"><span id="loginId"></span></td>
|
||||
<th>이메일 아이디</th>
|
||||
<td colspan="3"><span id="loginId"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="width:20%;">이름</th>
|
||||
<td style="width:30%;"><span id="userName"></span></td>
|
||||
<th style="width:20%;">권한</th>
|
||||
<td style="width:30%;"><span id="roleCodeDescription"></span></td>
|
||||
<th>이름</th>
|
||||
<td><span id="userName"></span></td>
|
||||
<th>권한</th>
|
||||
<td><span id="roleCodeDescription"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>핸드폰 번호</th>
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
<%@ page language="java" contentType="text/html; charset=utf-8" %>
|
||||
<%@ page import="java.io.*" %>
|
||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
|
||||
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
|
||||
<%@ include file="/jsp/common/include/localemessage.jsp" %>
|
||||
<%
|
||||
response.setHeader("Pragma", "No-cache");
|
||||
response.setHeader("Cache-Control", "no-cache");
|
||||
response.setHeader("Expires", "0");
|
||||
%>
|
||||
<html>
|
||||
<head>
|
||||
<title></title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<jsp:include page="/jsp/common/include/css.jsp"/>
|
||||
<jsp:include page="/jsp/common/include/script.jsp"/>
|
||||
<style>
|
||||
.mask-email { color:#1a73e8; font-weight:bold; }
|
||||
.mask-phone { color:#d93025; font-weight:bold; }
|
||||
.mask-digits { color:#9334e6; font-weight:bold; }
|
||||
.msg-cell { display:inline-block; max-width:380px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; vertical-align:middle; }
|
||||
</style>
|
||||
<script language="javascript">
|
||||
var url = '<c:url value="/onl/apim/messagerequest/messageRequestMan.json" />';
|
||||
var url_view = '<c:url value="/onl/apim/messagerequest/messageRequestMan.view" />';
|
||||
|
||||
function escapeHtmlJs(s) { return $('<div>').text(s == null ? '' : s).html(); }
|
||||
function escapeAttr(s) { return escapeHtmlJs(s).replace(/"/g, '"'); }
|
||||
|
||||
// 제목: 없으면 messageCode, 마우스오버에 코드명(코드) 표기
|
||||
function formatSubject(cellvalue, options, rowObject) {
|
||||
var code = rowObject.messageCode || '';
|
||||
var subject = (cellvalue && cellvalue !== '') ? cellvalue : '';
|
||||
var title = rowObject.messageCodeName ? (rowObject.messageCodeName + ' (' + code + ')') : code;
|
||||
if (subject) {
|
||||
// 제목이 있으면 2줄: 제목 + 하단에 메세지코드
|
||||
var codeLine = code ? '<br><span style="color:#888;font-size:0.85em;">' + escapeHtmlJs(code) + '</span>' : '';
|
||||
return '<span title="' + escapeAttr(title) + '">' + escapeHtmlJs(subject) + codeLine + '</span>';
|
||||
}
|
||||
// 제목이 없으면 메세지코드만
|
||||
return '<span title="' + escapeAttr(title) + '">' + escapeHtmlJs(code) + '</span>';
|
||||
}
|
||||
|
||||
// 본문: 서버에서 마스킹+escape 된 안전 HTML 그대로 렌더
|
||||
function formatMessage(cellvalue, options, rowObject) {
|
||||
return '<span class="msg-cell">' + (rowObject.messageHtml || '') + '</span>';
|
||||
}
|
||||
|
||||
// 수신자 정보: 휴대폰 / 이메일 / 메신저ID (모두 마스킹됨)
|
||||
function formatReceiverInfo(cellvalue, options, rowObject) {
|
||||
var parts = [];
|
||||
if (rowObject.phone) parts.push(escapeHtmlJs(rowObject.phone));
|
||||
if (rowObject.email) parts.push(escapeHtmlJs(rowObject.email));
|
||||
if (rowObject.messengerId) parts.push(escapeHtmlJs(rowObject.messengerId));
|
||||
return parts.join('<br>');
|
||||
}
|
||||
|
||||
function formatRequestDate(cellvalue, options, rowObject) {
|
||||
return '<span title="' + escapeAttr(rowObject.requestDateFull || '') + '">' + escapeHtmlJs(rowObject.requestDateShort || '') + '</span>';
|
||||
}
|
||||
function formatSentDate(cellvalue, options, rowObject) {
|
||||
return '<span title="' + escapeAttr(rowObject.sentDateFull || '') + '">' + escapeHtmlJs(rowObject.sentDateShort || '') + '</span>';
|
||||
}
|
||||
|
||||
function init() {
|
||||
$.ajax({
|
||||
type: "POST", url: url, dataType: "json",
|
||||
data: {cmd: 'LIST_INIT_COMBO'},
|
||||
success: function (json) {
|
||||
new makeOptions("CODE", "NAME").setObj($("select[name=searchRequestStatus]"))
|
||||
.setNoValueInclude(true).setNoValue("", "<%=localeMessage.getString("combo.all")%>")
|
||||
.setData(json.statusList).rendering();
|
||||
new makeOptions("CODE", "NAME").setObj($("select[name=searchMessageCode]"))
|
||||
.setNoValueInclude(true).setNoValue("", "<%=localeMessage.getString("combo.all")%>")
|
||||
.setData(json.messageCodeList).rendering();
|
||||
|
||||
putSelectFromParam();
|
||||
|
||||
// 콤보 로드 후 그리드 생성 (경합 방지)
|
||||
buildGrid();
|
||||
},
|
||||
error: function (e) { alert(e.responseText); }
|
||||
});
|
||||
}
|
||||
|
||||
function buildGrid() {
|
||||
$('#grid').jqGrid({
|
||||
datatype: "json", mtype: 'POST', url: url,
|
||||
postData: getSearchForJqgrid("cmd", "LIST"),
|
||||
colNames: ['id', 'No.', '제목', '메세지 내용', '수신자명', '수신자 정보', '유형', '상태', '요청시간', '발송시간'],
|
||||
colModel: [
|
||||
{name: 'id', align: 'center', key: true, hidden: true},
|
||||
{name: 'rowNum', align: 'center', width: 45, sortable: false},
|
||||
{name: 'subject', align: 'left', width: 160, formatter: formatSubject},
|
||||
{name: 'messageHtml', align: 'left', width: 300, formatter: formatMessage},
|
||||
{name: 'username', align: 'center', width: 80},
|
||||
{name: 'receiverInfo', align: 'left', width: 150, formatter: formatReceiverInfo},
|
||||
{name: 'messageType', align: 'center', width: 60},
|
||||
{name: 'requestStatus', align: 'center', width: 70},
|
||||
{name: 'requestDateShort', align: 'center', width: 110, formatter: formatRequestDate},
|
||||
{name: 'sentDateShort', align: 'center', width: 110, formatter: formatSentDate}
|
||||
],
|
||||
jsonReader: {repeatitems: false},
|
||||
pager: $('#pager'),
|
||||
page: '${param.page}',
|
||||
rowNum: '${rmsDefaultRowNum}',
|
||||
autoheight: true,
|
||||
height: $("#container").height(),
|
||||
autowidth: true,
|
||||
viewrecords: true,
|
||||
rowList: eval('[${rmsDefaultRowList}]'),
|
||||
ondblClickRow: function (rowId) {
|
||||
var rowData = $(this).getRowData(rowId);
|
||||
var id = rowData['id'];
|
||||
var url2 = url_view + '?cmd=DETAIL';
|
||||
url2 += '&page=' + $(this).getGridParam("page");
|
||||
url2 += '&returnUrl=' + getReturnUrl();
|
||||
url2 += '&menuId=' + '${param.menuId}';
|
||||
url2 += '&id=' + id;
|
||||
goNav(url2);
|
||||
},
|
||||
loadComplete: function () {
|
||||
var page = $(this).getGridParam('page');
|
||||
var rowNum = $(this).getGridParam('rowNum');
|
||||
var rows = $(this).getDataIDs();
|
||||
var colModel = $(this).getGridParam("colModel");
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
var number = ((page - 1) * rowNum + i) + 1;
|
||||
$(this).setCell(rows[i], 'rowNum', number);
|
||||
}
|
||||
// 서버 고정 정렬(요청일 역순) — 컬럼 정렬 비활성
|
||||
for (var i = 0; i < colModel.length; i++) {
|
||||
$(this).setColProp(colModel[i].name, {sortable: false});
|
||||
}
|
||||
},
|
||||
loadError: function (jqXHR, textStatus, errorThrown) {
|
||||
var location = '<%=request.getContextPath()%>/';
|
||||
comloadError(jqXHR, textStatus, errorThrown, location);
|
||||
}
|
||||
});
|
||||
resizeJqGridWidth('grid', 'content_middle', '1000');
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
init();
|
||||
|
||||
$("#btn_search").click(function () {
|
||||
var postData = getSearchForJqgrid("cmd", "LIST");
|
||||
$("#grid").setGridParam({url: url, postData: postData, page: 1}).trigger("reloadGrid");
|
||||
});
|
||||
|
||||
$("select[name^=search]").change(function () { $("#btn_search").click(); });
|
||||
$("input[name^=search]").keydown(function (key) {
|
||||
if (key.keyCode == 13) { $("#btn_search").click(); }
|
||||
});
|
||||
|
||||
buttonControl();
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="right_box">
|
||||
<div class="content_top">
|
||||
<ul class="path"><li><a href="#">${rmsMenuPath}</a></li></ul>
|
||||
</div><!-- end content_top -->
|
||||
<div class="content_middle" id="content_middle">
|
||||
<div class="search_wrap">
|
||||
<button type="button" class="cssbtn" id="btn_search" level="R"><i class="material-icons">search</i> <%= localeMessage.getString("button.search") %></button>
|
||||
</div>
|
||||
<div class="title">메세지 발송 내역<span class="tooltip">발송 요청 이력을 조회합니다. 개인정보는 마스킹되어 표시됩니다.</span></div>
|
||||
<form id="ajaxForm" onsubmit="return false;">
|
||||
<table class="search_condition" cellspacing=0;>
|
||||
<colgroup>
|
||||
<col style="width:120px;"><col style="width:200px;">
|
||||
<col style="width:120px;"><col style="width:200px;">
|
||||
<col style="width:120px;"><col style="width:200px;">
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>상태</th>
|
||||
<td><div class="select-style"><select name="searchRequestStatus"></select></div></td>
|
||||
<th>메세지 코드</th>
|
||||
<td><div class="select-style"><select name="searchMessageCode"></select></div></td>
|
||||
<th>수신자명</th>
|
||||
<td><input type="text" name="searchRecipient" autocomplete="off"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>유형</th>
|
||||
<td>
|
||||
<div class="select-style">
|
||||
<select name="searchMessageType">
|
||||
<option value="">전체</option>
|
||||
<option value="EMAIL">EMAIL</option>
|
||||
<option value="SMS">SMS</option>
|
||||
<option value="KAKAO">KAKAO</option>
|
||||
<option value="MESSENGER">MESSENGER</option>
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
<th>요청기간</th>
|
||||
<td colspan="3">
|
||||
<input type="date" name="searchFromDate"> ~ <input type="date" name="searchToDate">
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
<table id="grid"></table>
|
||||
<div id="pager"></div>
|
||||
</div><!-- end content_middle -->
|
||||
</div><!-- end right_box -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,149 @@
|
||||
<%@ page language="java" contentType="text/html; charset=utf-8" %>
|
||||
<%@ page import="java.io.*" %>
|
||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
|
||||
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
|
||||
<%@ include file="/jsp/common/include/localemessage.jsp" %>
|
||||
<%
|
||||
response.setHeader("Pragma", "No-cache");
|
||||
response.setHeader("Cache-Control", "no-cache");
|
||||
response.setHeader("Expires", "0");
|
||||
%>
|
||||
<html>
|
||||
<head>
|
||||
<title></title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<jsp:include page="/jsp/common/include/css.jsp"/>
|
||||
<jsp:include page="/jsp/common/include/script.jsp"/>
|
||||
<style>
|
||||
.mask-email { color:#1a73e8; font-weight:bold; }
|
||||
.mask-phone { color:#d93025; font-weight:bold; }
|
||||
.mask-digits { color:#9334e6; font-weight:bold; }
|
||||
.msg-detail { white-space:pre-wrap; word-break:break-all; min-height:80px; line-height:1.6; }
|
||||
</style>
|
||||
<script language="javascript">
|
||||
var url = '<c:url value="/onl/apim/messagerequest/messageRequestMan.json" />';
|
||||
var url_view = '<c:url value="/onl/apim/messagerequest/messageRequestMan.view" />';
|
||||
var key = '${param.id}';
|
||||
|
||||
function detail() {
|
||||
if (!key) return;
|
||||
$.ajax({
|
||||
type: "POST", url: url, dataType: "json",
|
||||
data: {cmd: 'DETAIL', id: key},
|
||||
success: function (data) {
|
||||
var codeText = (data.messageCodeName || '') + (data.messageCode ? ' (' + data.messageCode + ')' : '');
|
||||
$('#messageCodeName').text(codeText);
|
||||
$('#subject').text((data.subject && data.subject !== '') ? data.subject : (data.messageCode || ''));
|
||||
$('#messageType').text(data.messageType || '');
|
||||
$('#requestStatus').text(data.requestStatus || '');
|
||||
$('#username').text(data.username || '');
|
||||
$('#email').text(data.email || '');
|
||||
$('#phone').text(data.phone || '');
|
||||
$('#messengerId').text(data.messengerId || '');
|
||||
$('#umsUid').text(data.umsUid || '');
|
||||
$('#eaiInterfaceId').text(data.eaiInterfaceId || '');
|
||||
$('#serviceId').text(data.serviceId || '');
|
||||
$('#requestDate').text(data.requestDateFull || '');
|
||||
$('#sentDate').text(data.sentDateFull || '');
|
||||
// 서버에서 마스킹+escape 된 안전 HTML
|
||||
$('#messageHtml').html(data.messageHtml || '');
|
||||
|
||||
// PENDING 건만 '실패 처리' 노출 (권한 제어는 buttonControl 이 담당)
|
||||
if (data.requestStatus !== 'PENDING') {
|
||||
$('#btn_fail').hide();
|
||||
}
|
||||
},
|
||||
error: function (e) { alert(e.responseText); }
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
var returnUrl = getReturnUrlForReturn();
|
||||
|
||||
buttonControl();
|
||||
detail();
|
||||
|
||||
$('#btn_fail').click(function () {
|
||||
if (!confirm('이 건을 FAILED(발송 실패) 상태로 변경하시겠습니까?')) return;
|
||||
$.ajax({
|
||||
type: "POST", url: url, dataType: "json",
|
||||
data: {cmd: 'UPDATE_STATUS', id: key},
|
||||
success: function () {
|
||||
alert("변경되었습니다.");
|
||||
detail();
|
||||
},
|
||||
error: function (e) { alert(e.responseText); }
|
||||
});
|
||||
});
|
||||
|
||||
$('#btn_previous').click(function () { goNav(returnUrl); });
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="right_box">
|
||||
<div class="content_top">
|
||||
<ul class="path"><li><a href="#">${rmsMenuPath}</a></li></ul>
|
||||
</div>
|
||||
<div class="content_middle">
|
||||
<div class="search_wrap">
|
||||
<button type="button" class="cssbtn" id="btn_fail" level="W" status="DETAIL" style="background-color:#dc3545;border-color:#dc3545;color:#fff;"><i class="material-icons">report</i> 실패 처리</button>
|
||||
<button type="button" class="cssbtn" id="btn_previous" level="R" status="DETAIL,NEW"><i class="material-icons">arrow_back</i> <%= localeMessage.getString("button.previous") %></button>
|
||||
</div>
|
||||
<div class="title" id="title">메세지 발송 내역 상세<span class="tooltip">개인정보는 마스킹되어 표시됩니다.</span></div>
|
||||
<table class="table_row" cellspacing="0">
|
||||
<colgroup>
|
||||
<col style="width:12%"/><col style="width:38%"/>
|
||||
<col style="width:12%"/><col style="width:38%"/>
|
||||
</colgroup>
|
||||
<tr>
|
||||
<th>메세지 코드</th>
|
||||
<td colspan="3"><span id="messageCodeName"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>제목</th>
|
||||
<td colspan="3"><span id="subject"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>유형</th>
|
||||
<td><span id="messageType"></span></td>
|
||||
<th>상태</th>
|
||||
<td><span id="requestStatus"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>수신자명</th>
|
||||
<td><span id="username"></span></td>
|
||||
<th>메신저 ID</th>
|
||||
<td><span id="messengerId"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>이메일</th>
|
||||
<td><span id="email"></span></td>
|
||||
<th>휴대폰</th>
|
||||
<td><span id="phone"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>요청 시간</th>
|
||||
<td><span id="requestDate"></span></td>
|
||||
<th>발송 시간</th>
|
||||
<td><span id="sentDate"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>EAI 인터페이스 ID</th>
|
||||
<td><span id="eaiInterfaceId"></span></td>
|
||||
<th>서비스 ID</th>
|
||||
<td><span id="serviceId"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>UMS UID</th>
|
||||
<td colspan="3"><span id="umsUid"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>메세지 내용</th>
|
||||
<td colspan="3"><div id="messageHtml" class="msg-detail"></div></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -22,6 +22,8 @@
|
||||
function formatInquiryStatus(cellvalue, options, rowObject) {
|
||||
if (cellvalue == 'PENDING') {
|
||||
return '<span style="color: red;">문의중</span>';
|
||||
} else if (cellvalue == 'REVIEWING') {
|
||||
return '<span >문의검토중</span>'
|
||||
} else if (cellvalue == 'RESPONDED') {
|
||||
return '<span >답변완료</span>'
|
||||
} else if (cellvalue == 'CLOSED') {
|
||||
@@ -207,7 +209,9 @@
|
||||
<select name="searchInquiryStatus">
|
||||
<option value="">전체</option>
|
||||
<option value="PENDING">문의중</option>
|
||||
<option value="REVIEWING">문의검토중</option>
|
||||
<option value="RESPONDED">답변완료</option>
|
||||
<option value="CLOSED">답변종료</option>
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@@ -108,7 +108,9 @@
|
||||
|
||||
inquiryStatus = data.inquiryStatus;
|
||||
if (data.inquiryStatus == 'PENDING') {
|
||||
$("#inquiryStatus").text('문의중');
|
||||
$("#inquiryStatus").text('문의중');
|
||||
} else if (data.inquiryStatus == 'REVIEWING') {
|
||||
$("#inquiryStatus").text('문의검토중');
|
||||
} else if (data.inquiryStatus == 'RESPONDED') {
|
||||
$("#inquiryStatus").text('답변완료');
|
||||
} else if (data.inquiryStatus == 'CLOSED') {
|
||||
|
||||
@@ -20,7 +20,9 @@
|
||||
var combo;
|
||||
|
||||
function formatNoticeType(cellvalue, options, rowObject) {
|
||||
var name = "";
|
||||
if (!combo || !combo.noticeTypeList) {
|
||||
return cellvalue;
|
||||
}
|
||||
for (var i = 0; i < combo.noticeTypeList.length; i++) {
|
||||
if (combo.noticeTypeList[i].CODE == cellvalue) {
|
||||
return combo.noticeTypeList[i].NAME;
|
||||
@@ -46,8 +48,39 @@
|
||||
var icon = rowObject.hasAttachment ? '<img src="' + iconPath + '" alt="File Attached" style="vertical-align: middle; margin-left: 5px; width: 16px; height: 16px;">' : '';
|
||||
return cellvalue + icon;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function deleteSelectedRows() {
|
||||
var selectedIds = $("#grid").jqGrid('getGridParam', 'selarrrow');
|
||||
|
||||
if (selectedIds.length === 0) {
|
||||
alert('항목을 선택하세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm('선택된 ' + selectedIds.length + '개 항목을 삭제하시겠습니까?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: url,
|
||||
dataType: "json",
|
||||
data: {
|
||||
cmd: 'DELETE_BULK',
|
||||
ids: selectedIds.join(',')
|
||||
},
|
||||
success: function(data) {
|
||||
alert('삭제되었습니다.');
|
||||
$("#grid").jqGrid('clearGridData', true);
|
||||
$("#grid").trigger("reloadGrid");
|
||||
},
|
||||
error: function(e) {
|
||||
alert('삭제 중 오류가 발생했습니다.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function init(){
|
||||
$.ajax({
|
||||
type : "POST",
|
||||
@@ -58,8 +91,11 @@
|
||||
console.log('init', json);
|
||||
combo = json;
|
||||
new makeOptions("CODE","NAME").setObj($("select[name=searchNoticeType]")).setNoValueInclude(true).setNoValue('','<%=localeMessage.getString("combo.all")%>').setData(json.noticeTypeList).rendering();
|
||||
|
||||
|
||||
putSelectFromParam();
|
||||
|
||||
// 콤보(combo)가 채워진 뒤에 그리드를 생성해야 formatNoticeType 경합이 발생하지 않음
|
||||
buildGrid();
|
||||
},
|
||||
error:function(e){
|
||||
alert(e.responseText);
|
||||
@@ -67,8 +103,8 @@
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
function buildGrid(){
|
||||
|
||||
$('#grid').jqGrid({
|
||||
datatype:"json",
|
||||
mtype: 'POST',
|
||||
@@ -103,6 +139,8 @@
|
||||
autowidth: true,
|
||||
viewrecords: true,
|
||||
rowList : eval('[${rmsDefaultRowList}]'),
|
||||
multiselect: true,
|
||||
multiboxonly: true,
|
||||
ondblClickRow: function(rowId) {
|
||||
var rowData = $(this).getRowData(rowId);
|
||||
var id = rowData['id'];
|
||||
@@ -139,9 +177,13 @@
|
||||
}
|
||||
});
|
||||
|
||||
init();
|
||||
|
||||
resizeJqGridWidth('grid','content_middle','1000');
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
// 콤보 로드 → 콤보 성공 콜백에서 buildGrid() 호출 (경합 방지)
|
||||
init();
|
||||
|
||||
$("#btn_search").click(function(){
|
||||
var postData = getSearchForJqgrid("cmd","LIST");
|
||||
@@ -157,6 +199,10 @@
|
||||
goNav(url2);
|
||||
});
|
||||
|
||||
$("#btn_delete_selected").click(function(){
|
||||
deleteSelectedRows();
|
||||
});
|
||||
|
||||
$("select[name=searchUseYn], input[name^=search]").keydown(function(key){
|
||||
if (key.keyCode == 13){
|
||||
$("#btn_search").click();
|
||||
@@ -179,6 +225,7 @@
|
||||
<div class="search_wrap">
|
||||
<button type="button" class="cssbtn" id="btn_new" level="W"><i class="material-icons">add</i> <%= localeMessage.getString("button.new") %></button>
|
||||
<button type="button" class="cssbtn" id="btn_search" level="R"><i class="material-icons">search</i> <%= localeMessage.getString("button.search") %></button>
|
||||
<button type="button" class="cssbtn" id="btn_delete_selected" level="W" style="background-color: #dc3545; border-color: #dc3545; color: white;"><i class="material-icons">delete_forever</i> 선택 삭제</button>
|
||||
</div>
|
||||
<div class="title">공지사항 목록<span class="tooltip">공지사항을 관리합니다.</span></div>
|
||||
<form id="ajaxForm" onsubmit="return false;">
|
||||
|
||||
@@ -312,7 +312,9 @@ $(document).ready(function() {
|
||||
<div class="content_middle" id="content_middle">
|
||||
<div class="search_wrap">
|
||||
<button type="button" class="cssbtn" id="btn_new" level="W"><i class="material-icons">add</i> <%= localeMessage.getString("button.new") %></button>
|
||||
<c:if test="${hardDeleteEnabled}">
|
||||
<button type="button" class="cssbtn" id="btn_delete_selected" level="W" style="background-color: #dc3545; border-color: #dc3545; color: white;"><i class="material-icons">delete_forever</i> 선택 삭제</button>
|
||||
</c:if>
|
||||
<button type="button" class="cssbtn" id="btn_search" level="R"><i class="material-icons">search</i> <%= localeMessage.getString("button.search") %></button>
|
||||
</div>
|
||||
<%--법인정보관리--%>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
|
||||
<%@ page import="java.io.*"%>
|
||||
<%@ page import="com.eactive.eai.rms.common.util.CommonUtil"%>
|
||||
<%@ page import="com.eactive.eai.rms.common.context.MonitoringContext"%>
|
||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
|
||||
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring"%>
|
||||
<%@ include file="/jsp/common/include/localemessage.jsp" %>
|
||||
@@ -7,6 +9,9 @@
|
||||
response.setHeader("Pragma", "No-cache");
|
||||
response.setHeader("Cache-Control", "no-cache");
|
||||
response.setHeader("Expires", "0");
|
||||
|
||||
MonitoringContext monitoringContext = (MonitoringContext)CommonUtil.getBean(request, "monitoringContext");
|
||||
String reverseProxyUrl = monitoringContext.getStringProperty(MonitoringContext.RMS_WEBHOOK_REVERSE_PROXY_URL, "");
|
||||
%>
|
||||
<%!
|
||||
public String getRequiredLabel(String label) {
|
||||
@@ -133,10 +138,15 @@
|
||||
$("#btn_delete").hide();
|
||||
}
|
||||
|
||||
$("#approvalStatus").val(portalOrgData.approvalStatus);
|
||||
$("select[name=approvalStatus]").val(portalOrgData.approvalStatus);
|
||||
// 승인상태는 읽기전용(수정 불가) - 값은 그대로 제출되도록 disabled 대신 잠금 처리
|
||||
$("select[name=approvalStatus]")
|
||||
.css({'pointer-events':'none', 'background-color':'#eee'})
|
||||
.attr('tabindex', '-1');
|
||||
$("#compRegNo").val(formatCompanyRegNo(portalOrgData.compRegNo));
|
||||
$("#corpRegNo").val(formatCorpRegNo(portalOrgData.corpRegNo));
|
||||
$("#createdDate").inputmask("9999-99-99 99:99:99", {'autoUnmask': true});
|
||||
$("#reverseProxyPath").val(portalOrgData.reverseProxyPath);
|
||||
|
||||
// 전화번호 처리
|
||||
if (portalOrgData.scPhoneNumber) {
|
||||
@@ -404,47 +414,6 @@
|
||||
downloadFile(fileId, '1'); // fileSn은 '1'로 가정
|
||||
});
|
||||
|
||||
// OBP 파트너코드 조회 버튼 클릭
|
||||
$("#btn_query_obp").click(function() {
|
||||
var compRegNo = $("#compRegNo").val();
|
||||
if (!compRegNo) {
|
||||
jSuites.notification({ name: '알림', message: '사업자등록번호를 먼저 입력해주세요.', error: true });
|
||||
$("#compRegNo").focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// 하이픈 제거
|
||||
compRegNo = compRegNo.replace(/-/g, '');
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: url,
|
||||
dataType: "json",
|
||||
data: {
|
||||
cmd: 'QUERY_OBP_PARTNER_CODE',
|
||||
compRegNo: compRegNo
|
||||
},
|
||||
beforeSend: function() {
|
||||
$("#btn_query_obp").prop("disabled", true).html('<i class="material-icons">hourglass_empty</i> 조회중...');
|
||||
},
|
||||
success: function(json) {
|
||||
if (json.success) {
|
||||
$("#orgCode").val(json.partnerCode);
|
||||
jSuites.notification({ name: '성공', message: '파트너코드가 조회되었습니다: ' + json.partnerCode });
|
||||
} else {
|
||||
jSuites.notification({ name: '실패', message: json.message, error: true });
|
||||
}
|
||||
},
|
||||
error: function(e) {
|
||||
jSuites.notification({ name: '오류', message: '파트너코드 조회 중 오류가 발생했습니다.', error: true });
|
||||
console.error(e.responseText);
|
||||
},
|
||||
complete: function() {
|
||||
$("#btn_query_obp").prop("disabled", false).html('<i class="material-icons">search</i> 파트너코드 조회');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#btn_modify").click(function(){
|
||||
if (!checkRequired("ajaxForm")) return;
|
||||
|
||||
@@ -515,8 +484,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
if (confirm("<%= localeMessage.getString("common.checkSave")%>") !== true)
|
||||
return;
|
||||
// 저장 확인/사유 입력은 유효성 검사 및 FormData 구성 후 처리 (하단 submit 분기)
|
||||
|
||||
// FormData 생성 전에 분리된 전화번호 필드 제거
|
||||
$("#scPhonePrefix, #scPhoneMiddle, #scPhoneLast, #orgPhonePrefix, #orgPhoneMiddle, #orgPhoneLast").removeAttr('name');
|
||||
@@ -551,23 +519,47 @@
|
||||
}
|
||||
|
||||
|
||||
// cmd 파라미터 추가
|
||||
formData.append("cmd", isDetail ? "UPDATE" : "INSERT");
|
||||
|
||||
$.ajax({
|
||||
type : "POST",
|
||||
url:url,
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
success:function(json){
|
||||
alert("<%= localeMessage.getString("common.saveMsg") %>");
|
||||
goNav(returnUrl);
|
||||
},
|
||||
error:function(e){
|
||||
alert(e.responseText);
|
||||
// cmd 및 사유(auditReason) 첨부 후 전송
|
||||
function submitOrg(cmd, auditReason) {
|
||||
formData.append("cmd", cmd);
|
||||
// 감사로그(AUDIT_LOG) 발화를 위한 systemCode. 법인/가입자 리포지토리는 고정 EMS 스키마라 스키마 전환 영향 없음
|
||||
formData.append("serviceType", "APIGW");
|
||||
if (auditReason) {
|
||||
formData.append("auditReason", auditReason);
|
||||
}
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
type : "POST",
|
||||
url:url,
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
success:function(json){
|
||||
alert("<%= localeMessage.getString("common.saveMsg") %>");
|
||||
goNav(returnUrl);
|
||||
},
|
||||
error:function(e){
|
||||
alert(e.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (isDetail) {
|
||||
// 수정: 상태를 REMOVED로 바꾼 경우 삭제(cmd=DELETE)로 감사 기록 + 익명화
|
||||
var targetCmd = ($("#orgStatus").val() === 'REMOVED') ? "DELETE" : "UPDATE";
|
||||
var isRemove = (targetCmd === "DELETE");
|
||||
showReasonPrompt(
|
||||
isRemove ? "해당 법인을 삭제(탈퇴) 처리합니다. 사유를 입력해 주세요." : "법인 정보를 수정합니다. 사유를 입력해 주세요.",
|
||||
{
|
||||
title: isRemove ? "삭제 사유" : "수정 사유",
|
||||
onConfirm: function(reason) { submitOrg(targetCmd, reason); }
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// 신규 등록: 사유 불필요
|
||||
if (confirm("<%= localeMessage.getString("common.checkSave")%>") !== true) return;
|
||||
submitOrg("INSERT", null);
|
||||
}
|
||||
});
|
||||
|
||||
$("#btn_previous").click(function() {
|
||||
@@ -576,25 +568,28 @@
|
||||
|
||||
$("#btn_delete").click(function(){
|
||||
|
||||
if(confirm("<%= localeMessage.getString("common.confirmMsg")%>")){
|
||||
var postData = $('#ajaxForm').serializeArray();
|
||||
postData.push({name: "cmd" , value:"DELETE"});
|
||||
showReasonPrompt("해당 법인을 삭제 처리합니다. 사유를 입력해 주세요.", {
|
||||
title: "삭제 사유",
|
||||
onConfirm: function(reason) {
|
||||
var postData = $('#ajaxForm').serializeArray();
|
||||
postData.push({name: "cmd" , value:"DELETE"});
|
||||
postData.push({name: "serviceType", value: "APIGW"});
|
||||
postData.push({name: "auditReason", value: reason});
|
||||
|
||||
$.ajax({
|
||||
type : "POST",
|
||||
url:url,
|
||||
data:postData,
|
||||
success:function(args){
|
||||
alert("<%= localeMessage.getString("common.deleteMsg") %>");
|
||||
goNav(returnUrl);
|
||||
},
|
||||
error:function(e){
|
||||
alert(e.responseText);
|
||||
}
|
||||
});
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
$.ajax({
|
||||
type : "POST",
|
||||
url:url,
|
||||
data:postData,
|
||||
success:function(args){
|
||||
alert("<%= localeMessage.getString("common.deleteMsg") %>");
|
||||
goNav(returnUrl);
|
||||
},
|
||||
error:function(e){
|
||||
alert(e.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -630,10 +625,7 @@
|
||||
<tr>
|
||||
<th style="width:15%;"><%= localeMessage.getString("portalOrg.orgCode") %></th>
|
||||
<td colspan="3" style="width:35%;">
|
||||
<div style="display: flex; align-items: center;">
|
||||
<input type="text" name="orgCode" id="orgCode" style="width: 200px; margin-right: 10px;"/>
|
||||
<button type="button" class="cssbtn smallBtn" id="btn_query_obp"><i class="material-icons">search</i> 파트너코드 조회</button>
|
||||
</div>
|
||||
<input type="text" name="orgCode" id="orgCode"/>
|
||||
</td>
|
||||
<th><%= localeMessage.getString("portalOrg.orgIndustryType") %></th> <%--업종--%>
|
||||
<td><input type="text" name="orgIndustryType" id="orgIndustryType" maxlength="50" /></td>
|
||||
@@ -738,6 +730,16 @@
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><%= localeMessage.getString("portalOrg.reverseProxyPath") %></th>
|
||||
<td colspan="3">
|
||||
<%=reverseProxyUrl %>/<input type="text" name="reverseProxyPath" id="reverseProxyPath" style="width:100px; "/>
|
||||
</td>
|
||||
<th></th>
|
||||
<td>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr><%--사업자등록증--%>
|
||||
<th><%= localeMessage.getString("portalOrg.compRegFile") %></th>
|
||||
<td colspan="5" style="padding-top: 5px; padding-bottom: 5px;">
|
||||
|
||||
@@ -26,7 +26,38 @@ function formatFile(cellvalue, options, rowObject) {
|
||||
return cellvalue + icon;
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
function deleteSelectedRows() {
|
||||
var selectedIds = $("#grid").jqGrid('getGridParam', 'selarrrow');
|
||||
|
||||
if (selectedIds.length === 0) {
|
||||
alert('항목을 선택하세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm('선택된 ' + selectedIds.length + '개 항목을 삭제하시겠습니까?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: url,
|
||||
dataType: "json",
|
||||
data: {
|
||||
cmd: 'DELETE_BULK',
|
||||
ids: selectedIds.join(',')
|
||||
},
|
||||
success: function(data) {
|
||||
alert('삭제되었습니다.');
|
||||
$("#grid").jqGrid('clearGridData', true);
|
||||
$("#grid").trigger("reloadGrid");
|
||||
},
|
||||
error: function(e) {
|
||||
alert('삭제 중 오류가 발생했습니다.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$('#grid').jqGrid({
|
||||
datatype:"json",
|
||||
mtype: 'POST',
|
||||
@@ -45,7 +76,7 @@ $(document).ready(function() {
|
||||
],
|
||||
jsonReader: {
|
||||
repeatitems:false
|
||||
},
|
||||
},
|
||||
pager : $('#pager'),
|
||||
page : '${param.page}',
|
||||
rowNum : '${rmsDefaultRowNum}',
|
||||
@@ -54,6 +85,8 @@ $(document).ready(function() {
|
||||
autowidth: true,
|
||||
viewrecords: true,
|
||||
rowList : eval('[${rmsDefaultRowList}]'),
|
||||
multiselect: true,
|
||||
multiboxonly: true,
|
||||
ondblClickRow: function(rowId) {
|
||||
var rowData = $(this).getRowData(rowId);
|
||||
var id = rowData['id'];
|
||||
@@ -94,10 +127,14 @@ $(document).ready(function() {
|
||||
resizeJqGridWidth('grid','content_middle','1000');
|
||||
|
||||
$("#btn_search").click(function(){
|
||||
var postData = getSearchForJqgrid("cmd","LIST"); //jqgrid에서는 object 로
|
||||
var postData = getSearchForJqgrid("cmd","LIST"); //jqgrid에서는 object 로
|
||||
$("#grid").setGridParam({ url:url,postData: postData ,page:1 }).trigger("reloadGrid");
|
||||
});
|
||||
|
||||
$("#btn_delete_selected").click(function(){
|
||||
deleteSelectedRows();
|
||||
});
|
||||
|
||||
$("input[name^=search]").keydown(function(key){
|
||||
if (key.keyCode == 13){
|
||||
$("#btn_search").click();
|
||||
@@ -120,8 +157,9 @@ $(document).ready(function() {
|
||||
<div class="content_middle" id="content_middle">
|
||||
<div class="search_wrap">
|
||||
<button type="button" class="cssbtn" id="btn_search" level="R" ><i class="material-icons">search</i> <%= localeMessage.getString("button.search") %></button>
|
||||
<button type="button" class="cssbtn" id="btn_delete_selected" level="W" style="background-color: #dc3545; border-color: #dc3545; color: white;"><i class="material-icons">delete_forever</i> 선택 삭제</button>
|
||||
</div>
|
||||
<div class="title">사업제휴 신청 목록</div>
|
||||
<div class="title">피드백/개선요청 신청 목록</div>
|
||||
<form id="ajaxForm" onsubmit="return false;">
|
||||
<table class="search_condition" cellspacing=0;>
|
||||
<tbody>
|
||||
|
||||
@@ -92,6 +92,27 @@ $(document).ready(function() {
|
||||
goNav(returnUrl);//LIST로 이동
|
||||
});
|
||||
|
||||
$("#btn_delete").click(function(){
|
||||
if (!confirm('선택한 항목을 삭제하시겠습니까?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: url,
|
||||
dataType: "json",
|
||||
data: { cmd: 'DELETE', id: key },
|
||||
success: function(data) {
|
||||
alert('삭제되었습니다.');
|
||||
var returnUrl = getReturnUrlForReturn();
|
||||
goNav(returnUrl);//LIST로 이동
|
||||
},
|
||||
error: function(e) {
|
||||
alert('삭제 중 오류가 발생했습니다.');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#btn_unmask").click(function () {
|
||||
const reason = prompt("마스킹 해제 사유를 입력하세요:");
|
||||
|
||||
@@ -134,9 +155,10 @@ $(document).ready(function() {
|
||||
<div class="content_middle">
|
||||
<div class="search_wrap">
|
||||
<button type="button" class="cssbtn" id="btn_unmask" level="W" status="DETAIL"><i class="material-icons">lock_open</i> 마스킹해제</button>
|
||||
<button type="button" class="cssbtn" id="btn_delete" level="W" status="DETAIL" style="background-color: #dc3545; border-color: #dc3545; color: white;"><i class="material-icons">delete_forever</i> 삭제</button>
|
||||
<button type="button" class="cssbtn" id="btn_previous" level="R" status="DETAIL,NEW"><i class="material-icons">arrow_back</i> <%= localeMessage.getString("button.previous") %></button>
|
||||
</div>
|
||||
<div class="title">사업제휴 신청 상세</div>
|
||||
<div class="title">피드백/개선요청 신청 상세</div>
|
||||
|
||||
<table id="grid" ></table>
|
||||
<div id="pager"></div>
|
||||
|
||||
@@ -29,6 +29,16 @@
|
||||
// 원본 행 데이터를 ID로 조회하기 위한 맵
|
||||
var gridRowDataMap = {};
|
||||
|
||||
// 휴대폰번호 중복 조회 모드 여부
|
||||
var isDupView = false;
|
||||
|
||||
// 현재 검색조건 + 중복 조회 모드 플래그를 합쳐 LIST 요청 파라미터를 생성
|
||||
function buildListPostData() {
|
||||
var postData = getSearchForJqgrid("cmd", "LIST");
|
||||
postData.onlyDuplicateMobile = isDupView;
|
||||
return postData;
|
||||
}
|
||||
|
||||
/* 선택된 사용자 삭제 - 상태에 따라 Soft/Hard Delete 자동 분기 */
|
||||
function deleteSelectedUsers() {
|
||||
var selectedIds = $("#grid").jqGrid('getGridParam', 'selarrrow');
|
||||
@@ -166,6 +176,13 @@
|
||||
|
||||
$('#grid').jqGrid('setColProp', 'approvalStatus', {editoptions: {value: select_approvalStatus}});
|
||||
$('#grid').jqGrid('setColProp', 'userStatus', {editoptions: {value: select_userStatus}});
|
||||
|
||||
// 휴대폰번호 중복 허용안함(기능 ON) + 중복 사용자 존재 시 배너 노출
|
||||
if (json.mobileDuplicateCheckEnabled && json.mobileDuplicateUserCount > 0) {
|
||||
$("#dupMobileCount").text(json.mobileDuplicateUserCount);
|
||||
$("#dupMobileBanner").show();
|
||||
}
|
||||
|
||||
$('#grid').trigger('reloadGrid');
|
||||
|
||||
},
|
||||
@@ -176,7 +193,7 @@
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
var gridPostData = getSearchForJqgrid("cmd", "LIST");
|
||||
var gridPostData = buildListPostData();
|
||||
|
||||
$('#grid').jqGrid({
|
||||
datatype: "json",
|
||||
@@ -188,7 +205,7 @@
|
||||
'<%= localeMessage.getString("portalUser.orgName") %>',
|
||||
'<%= localeMessage.getString("portalUser.name") %>',
|
||||
'<%= localeMessage.getString("portalUser.userId") %>',
|
||||
<%--'<%= localeMessage.getString("portalUser.mobile") %>',--%>
|
||||
'<%= localeMessage.getString("portalUser.mobileNumber") %>',
|
||||
'<%= localeMessage.getString("portalUser.roleName") %>',
|
||||
'<%= localeMessage.getString("portalUser.userStatus") %>',
|
||||
'<%= localeMessage.getString("portalUser.createOn") %>',
|
||||
@@ -201,7 +218,7 @@
|
||||
{name: 'portalOrgUIs.orgName', align: 'center', width: "120"},
|
||||
{name: 'userName', align: 'center', width: "80" },
|
||||
{name: 'loginId', align: 'center', width: "150" },
|
||||
// {name: 'mobileNumber', align: 'center', width: "100" },
|
||||
{name: 'mobileNumber', align: 'center', width: "110", hidden: true},
|
||||
{name: 'roleCodeDescription', align: 'center', width: "80"},
|
||||
{name: 'userStatusDescription', align: 'center', width: "50"},
|
||||
{name: 'createdDate', align: 'center', width: "100", formatter: timeStampFormat},
|
||||
@@ -274,9 +291,20 @@
|
||||
init();
|
||||
|
||||
$("#btn_search").click(function () {
|
||||
var postData = getSearchForJqgrid("cmd", "LIST");
|
||||
$("#grid").setGridParam({url: url, postData: buildListPostData(), page: 1}).trigger("reloadGrid");
|
||||
});
|
||||
|
||||
$("#grid").setGridParam({url: url, postData: postData, page: 1}).trigger("reloadGrid");
|
||||
// 중복 휴대폰번호 사용자만 보기 / 전체 보기 토글
|
||||
$("#btn_dup_toggle").click(function () {
|
||||
isDupView = !isDupView;
|
||||
if (isDupView) {
|
||||
$(this).text("전체 보기");
|
||||
$("#grid").jqGrid('showCol', 'mobileNumber');
|
||||
} else {
|
||||
$(this).text("중복만 보기");
|
||||
$("#grid").jqGrid('hideCol', 'mobileNumber');
|
||||
}
|
||||
$("#grid").setGridParam({url: url, postData: buildListPostData(), page: 1}).trigger("reloadGrid");
|
||||
});
|
||||
|
||||
$("#btn_new").click(function () {
|
||||
@@ -319,9 +347,11 @@
|
||||
<button type="button" class="cssbtn" id="btn_new" level="W"><i
|
||||
class="material-icons">add</i> <%= localeMessage.getString("button.new") %>
|
||||
</button>
|
||||
<c:if test="${hardDeleteEnabled}">
|
||||
<button type="button" class="cssbtn" id="btn_delete_selected" level="W" style="background-color: #dc3545; border-color: #dc3545; color: white;"><i
|
||||
class="material-icons">delete_forever</i> 선택 삭제
|
||||
</button>
|
||||
</c:if>
|
||||
<button type="button" class="cssbtn" id="btn_search" level="R"><i
|
||||
class="material-icons">search</i> <%= localeMessage.getString("button.search") %>
|
||||
</button>
|
||||
@@ -334,6 +364,11 @@
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<div id="dupMobileBanner" style="display:none; background:#f8d7da; border:1px solid #dc3545; padding:8px 15px; margin:5px 0; border-radius:4px; color:#721c24;">
|
||||
<strong>⚠ 중복 휴대폰번호 사용자 <span id="dupMobileCount">0</span>건 발견</strong>
|
||||
<button type="button" class="cssbtn" id="btn_dup_toggle" style="margin-left:10px;">중복만 보기</button>
|
||||
</div>
|
||||
|
||||
<table class="search_condition" cellspacing=0;>
|
||||
<tbody>
|
||||
<tr>
|
||||
@@ -365,15 +400,16 @@
|
||||
<td><input type="text" name="searchUserName"></td>
|
||||
|
||||
<th style="width:10%; min-width:100px;">
|
||||
<%= localeMessage.getString("portalUser.userId") %>
|
||||
<%= localeMessage.getString("portalUser.userId") %><span style="color:#dc3545;">(*)</span>
|
||||
</th>
|
||||
<td><input type="text" name="searchUserId" value="${param.searchUserId}"></td>
|
||||
<th style="width:10%; min-width:100px;"><%= localeMessage.getString("portalUser.mobileNumber") %>
|
||||
<th style="width:10%; min-width:100px;"><%= localeMessage.getString("portalUser.mobileNumber") %><span style="color:#dc3545;">(*)</span>
|
||||
</th>
|
||||
<td><input type="text" name="searchMobileNumber" value="${param.searchMobileNumber}"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div style="color:#dc3545; font-size:12px; margin:4px 2px;">(*) 암호화 항목 · 부분검색 불가(전체 일치)</div>
|
||||
|
||||
<table id="grid"></table>
|
||||
<div id="pager"></div>
|
||||
|
||||
@@ -165,6 +165,10 @@
|
||||
$("#userStatus").val(data.userStatus);
|
||||
$("#roleCode").val(data.roleCode).trigger('change');
|
||||
$("#approvalStatus").val(data.approvalStatus);
|
||||
// 승인상태는 읽기전용(수정 불가) - 값은 그대로 제출되도록 disabled 대신 잠금 처리
|
||||
$("#approvalStatus")
|
||||
.css({'pointer-events':'none', 'background-color':'#eee'})
|
||||
.attr('tabindex', '-1');
|
||||
|
||||
// 탈퇴 상태 처리
|
||||
if (originalUserStatus === STATUS.REMOVED) {
|
||||
@@ -488,29 +492,49 @@
|
||||
formData.push({name: 'mobileNumber', value: mobileNumber});
|
||||
}
|
||||
|
||||
formData.push({
|
||||
name: 'cmd',
|
||||
value: isDetail ? "UPDATE" : "INSERT"
|
||||
});
|
||||
|
||||
if (!confirm("<%= localeMessage.getString("common.checkSave")%>")) return;
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: url,
|
||||
data: formData,
|
||||
success: function (json) {
|
||||
if (json.status === "fail") {
|
||||
alert(json.errorMsg || "저장에 실패했습니다.");
|
||||
return;
|
||||
}
|
||||
alert("<%= localeMessage.getString("common.saveMsg") %>");
|
||||
goNav(returnUrl);
|
||||
},
|
||||
error: function (e) {
|
||||
alert(e.responseText);
|
||||
// cmd 및 사유(auditReason) 첨부 후 전송
|
||||
function submitUser(cmd, auditReason) {
|
||||
formData.push({ name: 'cmd', value: cmd });
|
||||
// 감사로그(AUDIT_LOG) 발화를 위한 systemCode. 가입자 리포지토리는 고정 EMS 스키마라 스키마 전환 영향 없음
|
||||
formData.push({ name: 'serviceType', value: 'APIGW' });
|
||||
if (auditReason) {
|
||||
formData.push({ name: 'auditReason', value: auditReason });
|
||||
}
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: url,
|
||||
data: formData,
|
||||
success: function (json) {
|
||||
if (json.status === "fail") {
|
||||
alert(json.errorMsg || "저장에 실패했습니다.");
|
||||
return;
|
||||
}
|
||||
alert("<%= localeMessage.getString("common.saveMsg") %>");
|
||||
goNav(returnUrl);
|
||||
},
|
||||
error: function (e) {
|
||||
alert(e.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (isDetail) {
|
||||
// 수정: 상태를 REMOVED로 바꾼 경우 삭제(cmd=DELETE)로 감사 기록 + 익명화
|
||||
var targetCmd = ($("#userStatus").val() === 'REMOVED') ? "DELETE" : "UPDATE";
|
||||
var isRemove = (targetCmd === "DELETE");
|
||||
showReasonPrompt(
|
||||
isRemove ? "해당 가입자를 삭제(탈퇴) 처리합니다. 사유를 입력해 주세요." : "가입자 정보를 수정합니다. 사유를 입력해 주세요.",
|
||||
{
|
||||
title: isRemove ? "삭제 사유" : "수정 사유",
|
||||
onConfirm: function(reason) { submitUser(targetCmd, reason); }
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// 신규 등록: 사유 불필요
|
||||
if (!confirm("<%= localeMessage.getString("common.checkSave")%>")) return;
|
||||
submitUser("INSERT", null);
|
||||
}
|
||||
});
|
||||
|
||||
$("#btn_previous").click(function () {
|
||||
@@ -518,21 +542,26 @@
|
||||
});
|
||||
|
||||
$("#btn_delete").click(function () {
|
||||
if (!confirm("<%= localeMessage.getString("common.confirmMsg")%>")) return;
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: url,
|
||||
data: {
|
||||
cmd: "DELETE",
|
||||
id: $('input[name=id]').val()
|
||||
},
|
||||
success: function () {
|
||||
alert("<%= localeMessage.getString("common.deleteMsg") %>");
|
||||
goNav(returnUrl);
|
||||
},
|
||||
error: function (e) {
|
||||
alert(e.responseText);
|
||||
showReasonPrompt("해당 가입자를 삭제 처리합니다. 사유를 입력해 주세요.", {
|
||||
title: "삭제 사유",
|
||||
onConfirm: function(reason) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: url,
|
||||
data: {
|
||||
cmd: "DELETE",
|
||||
id: $('input[name=id]').val(),
|
||||
serviceType: "APIGW",
|
||||
auditReason: reason
|
||||
},
|
||||
success: function () {
|
||||
alert("<%= localeMessage.getString("common.deleteMsg") %>");
|
||||
goNav(returnUrl);
|
||||
},
|
||||
error: function (e) {
|
||||
alert(e.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
<%@ page language="java" contentType="text/html; charset=utf-8" %>
|
||||
<%@ page import="java.io.*" %>
|
||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
|
||||
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
|
||||
<%@ include file="/jsp/common/include/localemessage.jsp" %>
|
||||
<%
|
||||
response.setHeader("Pragma", "No-cache");
|
||||
response.setHeader("Cache-Control", "no-cache");
|
||||
response.setHeader("Expires", "0");
|
||||
%>
|
||||
<html>
|
||||
<head>
|
||||
<title></title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<jsp:include page="/jsp/common/include/css.jsp"/>
|
||||
<jsp:include page="/jsp/common/include/script.jsp"/>
|
||||
<style>
|
||||
.url-cell { display:inline-block; max-width:380px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; vertical-align:middle; }
|
||||
.succ-y { color:#1a73e8; font-weight:bold; }
|
||||
.succ-n { color:#d93025; font-weight:bold; }
|
||||
</style>
|
||||
<script language="javascript">
|
||||
var url = '<c:url value="/onl/apim/webhook/webhookSendLogMan.json" />';
|
||||
var url_view = '<c:url value="/onl/apim/webhook/webhookSendLogMan.view" />';
|
||||
var combo;
|
||||
|
||||
function escapeHtmlJs(s) { return $('<div>').text(s == null ? '' : s).html(); }
|
||||
function escapeAttr(s) { return escapeHtmlJs(s).replace(/"/g, '"'); }
|
||||
|
||||
function formatTargetUrl(cellvalue, options, rowObject) {
|
||||
var v = cellvalue || '';
|
||||
return '<span class="url-cell" title="' + escapeAttr(v) + '">' + escapeHtmlJs(v) + '</span>';
|
||||
}
|
||||
|
||||
// 이벤트유형 코드 → 모니터링 공통코드(EVENT_TYPE) 코드명
|
||||
function formatEventType(cellvalue, options, rowObject) {
|
||||
if (!combo || !combo.eventTypeList) {
|
||||
return escapeHtmlJs(cellvalue);
|
||||
}
|
||||
for (var i = 0; i < combo.eventTypeList.length; i++) {
|
||||
if (combo.eventTypeList[i].CODE == cellvalue) {
|
||||
return escapeHtmlJs(combo.eventTypeList[i].NAME);
|
||||
}
|
||||
}
|
||||
return escapeHtmlJs(cellvalue);
|
||||
}
|
||||
|
||||
function formatSuccess(cellvalue, options, rowObject) {
|
||||
if (cellvalue === true) return '<span class="succ-y">성공</span>';
|
||||
if (cellvalue === false) return '<span class="succ-n">실패</span>';
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
function init() {
|
||||
|
||||
$("#startDatepicker").datepicker().inputmask("yyyy-mm-dd",{'autoUnmask':true});
|
||||
$("#endDatepicker").datepicker().inputmask("yyyy-mm-dd",{'autoUnmask':true});
|
||||
|
||||
var today = getToday();
|
||||
|
||||
$("input[name=searchStartYYYYMMDD]").val(today.substring(0,6)+"01");
|
||||
$("input[name=searchEndYYYYMMDD]").val(today);
|
||||
$("input[name=searchStartDate]").val(today.substring(0,6)+"01");
|
||||
$("input[name=searchEndDate]").val(today);
|
||||
|
||||
$.ajax({
|
||||
type: "POST", url: url, dataType: "json",
|
||||
data: {cmd: 'LIST_INIT_COMBO'},
|
||||
success: function (json) {
|
||||
combo = json;
|
||||
new makeOptions("CODE", "NAME").setObj($("select[name=searchEventType]"))
|
||||
.setNoValueInclude(true).setNoValue("", "<%=localeMessage.getString("combo.all")%>")
|
||||
.setData(json.eventTypeList).rendering();
|
||||
|
||||
// 뒤로가기 등으로 이전 검색조건 파라미터가 있으면 기본값을 덮어씀
|
||||
putSelectFromParam();
|
||||
|
||||
// 콤보(이벤트유형) 로드 후 그리드 생성 — formatEventType 경합 방지
|
||||
buildGrid();
|
||||
},
|
||||
error: function (e) { alert(e.responseText); }
|
||||
});
|
||||
}
|
||||
|
||||
function buildGrid() {
|
||||
$('#grid').jqGrid({
|
||||
datatype: "json",
|
||||
mtype: 'POST',
|
||||
url: url,
|
||||
postData: getSearchForJqgrid("cmd", "LIST"),
|
||||
colNames: ['id', 'No.', '기관명', '이벤트유형', 'Target URL', 'Proxy URL', 'Status', '성공여부', '발송서버', '발송일시'],
|
||||
colModel: [
|
||||
{name: 'id', align: 'center', key: true, hidden: true},
|
||||
{name: 'rowNum', align: 'center', width: 45, sortable: false},
|
||||
{name: 'orgName', align: 'left', width: 140},
|
||||
{name: 'eventType', align: 'center', width: 120, formatter: formatEventType},
|
||||
{name: 'targetUrl', align: 'left', width: 300, formatter: formatTargetUrl},
|
||||
{name: 'proxyUrl', align: 'left', width: 300, formatter: formatTargetUrl},
|
||||
{name: 'statusCode', align: 'center', width: 60},
|
||||
{name: 'success', align: 'center', width: 70, formatter: formatSuccess},
|
||||
{name: 'sendBy', align: 'center', width: 100},
|
||||
{name: 'sentAt', align: 'center', width: 140}
|
||||
],
|
||||
jsonReader: {repeatitems: false},
|
||||
pager: $('#pager'),
|
||||
page: '${param.page}',
|
||||
rowNum: '${rmsDefaultRowNum}',
|
||||
autoheight: true,
|
||||
height: $("#container").height(),
|
||||
autowidth: true,
|
||||
viewrecords: true,
|
||||
rowList: eval('[${rmsDefaultRowList}]'),
|
||||
ondblClickRow: function (rowId) {
|
||||
var rowData = $(this).getRowData(rowId);
|
||||
var id = rowData['id'];
|
||||
var url2 = url_view + '?cmd=DETAIL';
|
||||
url2 += '&page=' + $(this).getGridParam("page");
|
||||
url2 += '&returnUrl=' + getReturnUrl();
|
||||
url2 += '&menuId=' + '${param.menuId}';
|
||||
url2 += '&id=' + id;
|
||||
url2 += '&' + getSearchUrl();
|
||||
goNav(url2);
|
||||
},
|
||||
loadComplete: function () {
|
||||
var page = $(this).getGridParam('page');
|
||||
var rowNum = $(this).getGridParam('rowNum');
|
||||
var rows = $(this).getDataIDs();
|
||||
var colModel = $(this).getGridParam("colModel");
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
var number = ((page - 1) * rowNum + i) + 1;
|
||||
$(this).setCell(rows[i], 'rowNum', number);
|
||||
}
|
||||
// 서버 고정 정렬(등록일시 역순) — 컬럼 정렬 비활성
|
||||
for (var i = 0; i < colModel.length; i++) {
|
||||
$(this).setColProp(colModel[i].name, {sortable: false});
|
||||
}
|
||||
},
|
||||
loadError: function (jqXHR, textStatus, errorThrown) {
|
||||
var location = '<%=request.getContextPath()%>/';
|
||||
comloadError(jqXHR, textStatus, errorThrown, location);
|
||||
}
|
||||
});
|
||||
resizeJqGridWidth('grid', 'content_middle', '1000');
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
init();
|
||||
|
||||
$("#btn_search").click(function () {
|
||||
var start = $("input[name=searchStartYYYYMMDD]").val().replace(/-/gi, "");
|
||||
var end = $("input[name=searchEndYYYYMMDD]").val().replace(/-/gi, "");
|
||||
|
||||
if (start && start > getToday()) {
|
||||
alert("시작일이 오늘 이후입니다. 시작일을 확인해주세요.");
|
||||
$("input[name=searchStartYYYYMMDD]").focus();
|
||||
return false;
|
||||
}
|
||||
if (start && end && start > end) {
|
||||
alert("조회기간 종료일은 시작일보다 커야합니다.");
|
||||
$("input[name=searchEndYYYYMMDD]").focus();
|
||||
return false;
|
||||
}
|
||||
$("input[name=searchStartDate]").val(start);
|
||||
$("input[name=searchEndDate]").val(end);
|
||||
|
||||
var postData = getSearchForJqgrid("cmd", "LIST");
|
||||
$("#grid").setGridParam({url: url, postData: postData, page: 1}).trigger("reloadGrid");
|
||||
});
|
||||
|
||||
$("select[name^=search]").change(function () { $("#btn_search").click(); });
|
||||
$("input[name^=search]").keydown(function (key) {
|
||||
if (key.keyCode == 13) { $("#btn_search").click(); }
|
||||
});
|
||||
|
||||
|
||||
|
||||
buttonControl();
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="right_box">
|
||||
<div class="content_top">
|
||||
<ul class="path"><li><a href="#">${rmsMenuPath}</a></li></ul>
|
||||
</div><!-- end content_top -->
|
||||
<div class="content_middle" id="content_middle">
|
||||
<div class="search_wrap">
|
||||
<button type="button" class="cssbtn" id="btn_search" level="R"><i class="material-icons">search</i> <%= localeMessage.getString("button.search") %></button>
|
||||
</div>
|
||||
<div class="title">웹훅 발송 로그<span class="tooltip">외부로 발송된 웹훅 이력을 조회합니다.</span></div>
|
||||
<form id="ajaxForm" onsubmit="return false;">
|
||||
<table class="search_condition" cellspacing=0;>
|
||||
<colgroup>
|
||||
<col style="width:120px;"><col style="width:200px;">
|
||||
<col style="width:120px;"><col style="width:200px;">
|
||||
<col style="width:120px;"><col style="width:200px;">
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>기관명</th>
|
||||
<td><input type="text" name="searchOrgName" autocomplete="off"></td>
|
||||
<th>이벤트유형</th>
|
||||
<td>
|
||||
<div class="select-style">
|
||||
<select name="searchEventType"></select>
|
||||
</div>
|
||||
</td>
|
||||
<th>성공여부</th>
|
||||
<td>
|
||||
<div class="select-style">
|
||||
<select name="searchSuccess">
|
||||
<option value="">전체</option>
|
||||
<option value="Y">성공</option>
|
||||
<option value="N">실패</option>
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Target URL</th>
|
||||
<td><input type="text" name="searchTargetUrl" autocomplete="off" style="width:95%;"></td>
|
||||
<th>발송기간</th>
|
||||
<td>
|
||||
<input type="text" name="searchStartYYYYMMDD" id="startDatepicker" readonly="readonly" value="" size="10" style="width:40%; border:1px solid #ebebec;">
|
||||
~
|
||||
<input type="text" name="searchEndYYYYMMDD" value="" id="endDatepicker" size="10" readonly="readonly" style="width:40%; border:1px solid #ebebec;">
|
||||
<input type="hidden" name="searchStartDate" value="">
|
||||
<input type="hidden" name="searchEndDate" value="">
|
||||
</td>
|
||||
<th></th>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
<table id="grid"></table>
|
||||
<div id="pager"></div>
|
||||
</div><!-- end content_middle -->
|
||||
</div><!-- end right_box -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,137 @@
|
||||
<%@ page language="java" contentType="text/html; charset=utf-8" %>
|
||||
<%@ page import="java.io.*" %>
|
||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
|
||||
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
|
||||
<%@ include file="/jsp/common/include/localemessage.jsp" %>
|
||||
<%
|
||||
response.setHeader("Pragma", "No-cache");
|
||||
response.setHeader("Cache-Control", "no-cache");
|
||||
response.setHeader("Expires", "0");
|
||||
%>
|
||||
<html>
|
||||
<head>
|
||||
<title></title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<jsp:include page="/jsp/common/include/css.jsp"/>
|
||||
<jsp:include page="/jsp/common/include/script.jsp"/>
|
||||
<style>
|
||||
.succ-y { color:#1a73e8; font-weight:bold; }
|
||||
.succ-n { color:#d93025; font-weight:bold; }
|
||||
.pre-wrap { white-space:pre-wrap; word-break:break-all; min-height:60px; line-height:1.5; }
|
||||
</style>
|
||||
<script language="javascript">
|
||||
var url = '<c:url value="/onl/apim/webhook/webhookSendLogMan.json" />';
|
||||
var url_view = '<c:url value="/onl/apim/webhook/webhookSendLogMan.view" />';
|
||||
var key = '${param.id}';
|
||||
|
||||
function escapeHtmlJs(s) { return $('<div>').text(s == null ? '' : s).html(); }
|
||||
|
||||
function detail() {
|
||||
if (!key) return;
|
||||
$.ajax({
|
||||
type: "POST", url: url, dataType: "json",
|
||||
data: {cmd: 'DETAIL', id: key},
|
||||
success: function (data) {
|
||||
$('#id').text(data.id != null ? data.id : '');
|
||||
$('#orgName').text(data.orgName || '');
|
||||
$('#eventType').text(data.eventTypeName || data.eventType || '')
|
||||
.attr('title', data.eventType || '');
|
||||
$('#targetUrl').text(data.targetUrl || '');
|
||||
$('#proxyUrl').text(data.proxyUrl || '');
|
||||
$('#statusCode').text(data.statusCode != null ? data.statusCode : '');
|
||||
$('#retryCount').text(data.retryCount != null ? data.retryCount : '');
|
||||
$('#sendBy').text(data.sendBy || '');
|
||||
$('#sentAt').text(data.sentAt || '');
|
||||
$('#signature').text(data.signature || '');
|
||||
$('#errorMessage').text(data.errorMessage || '');
|
||||
$('#payload').text(data.payload || '');
|
||||
$('#responseBody').text(data.responseBody || '');
|
||||
|
||||
if (data.success === true) {
|
||||
$('#success').html('<span class="succ-y">성공</span>');
|
||||
} else if (data.success === false) {
|
||||
$('#success').html('<span class="succ-n">실패</span>');
|
||||
} else {
|
||||
$('#success').text('');
|
||||
}
|
||||
},
|
||||
error: function (e) { alert(e.responseText); }
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
var returnUrl = getReturnUrlForReturn();
|
||||
|
||||
buttonControl();
|
||||
detail();
|
||||
|
||||
$('#btn_previous').click(function () { goNav(returnUrl); });
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="right_box">
|
||||
<div class="content_top">
|
||||
<ul class="path"><li><a href="#">${rmsMenuPath}</a></li></ul>
|
||||
</div>
|
||||
<div class="content_middle">
|
||||
<div class="search_wrap">
|
||||
<button type="button" class="cssbtn" id="btn_previous" level="R" status="DETAIL,NEW"><i class="material-icons">arrow_back</i> <%= localeMessage.getString("button.previous") %></button>
|
||||
</div>
|
||||
<div class="title" id="title">웹훅 발송 로그 상세</div>
|
||||
<table class="table_row" cellspacing="0">
|
||||
<colgroup>
|
||||
<col style="width:12%"/><col style="width:38%"/>
|
||||
<col style="width:12%"/><col style="width:38%"/>
|
||||
</colgroup>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<td><span id="id"></span></td>
|
||||
<th>기관명</th>
|
||||
<td><span id="orgName"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>이벤트유형</th>
|
||||
<td><span id="eventType"></span></td>
|
||||
<th>Status Code</th>
|
||||
<td><span id="statusCode"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Target URL</th>
|
||||
<td><span id="targetUrl"></span></td>
|
||||
<th>Proxy URL</th>
|
||||
<td><span id="proxyUrl"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>성공여부</th>
|
||||
<td><span id="success"></span></td>
|
||||
<th>재시도횟수</th>
|
||||
<td><span id="retryCount"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>발송서버</th>
|
||||
<td><span id="sendBy"></span></td>
|
||||
<th>발송일시</th>
|
||||
<td><span id="sentAt"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Signature</th>
|
||||
<td colspan="3"><span id="signature"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Payload</th>
|
||||
<td colspan="3"><div id="payload" class="pre-wrap"></div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Response Body</th>
|
||||
<td colspan="3"><div id="responseBody" class="pre-wrap"></div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>에러 메세지</th>
|
||||
<td colspan="3"><div id="errorMessage" class="pre-wrap"></div></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -525,35 +525,26 @@
|
||||
$("input[name=searchStartDate], input[name=searchEndDate]").inputmask("9999-99-99", { 'autoUnmask': true });
|
||||
$("input[name=searchStartTime], input[name=searchEndTime]").inputmask("99:99", { 'autoUnmask': true });
|
||||
|
||||
// 기본값 설정 (현재 시간 기준 1시간 전 정각 ~ 현재 정각)
|
||||
// 기본값 설정 (현재 시간(분) 기준 1시간 전 ~ 현재 시간(분))
|
||||
var now = new Date();
|
||||
var endHour = now.getHours();
|
||||
var startHour = endHour - 1;
|
||||
var startDate, endDate;
|
||||
var oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000);
|
||||
|
||||
if (startHour < 0) {
|
||||
startHour = 23;
|
||||
var yesterday = new Date(now);
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
startDate = yesterday.getFullYear() + '-' +
|
||||
String(yesterday.getMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(yesterday.getDate()).padStart(2, '0');
|
||||
endDate = now.getFullYear() + '-' +
|
||||
String(now.getMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(now.getDate()).padStart(2, '0');
|
||||
} else {
|
||||
var today = getToday();
|
||||
startDate = today;
|
||||
endDate = today;
|
||||
function formatDate(d) {
|
||||
return d.getFullYear() + '-' +
|
||||
String(d.getMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(d.getDate()).padStart(2, '0');
|
||||
}
|
||||
function formatTime(d) {
|
||||
return String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0');
|
||||
}
|
||||
|
||||
if (!$("input[name=searchStartDate]").val()) {
|
||||
$("input[name=searchStartDate]").val(startDate);
|
||||
$("input[name=searchStartTime]").val(String(startHour).padStart(2, '0') + ':00');
|
||||
$("input[name=searchStartDate]").val(formatDate(oneHourAgo));
|
||||
$("input[name=searchStartTime]").val(formatTime(oneHourAgo));
|
||||
}
|
||||
if (!$("input[name=searchEndDate]").val()) {
|
||||
$("input[name=searchEndDate]").val(endDate);
|
||||
$("input[name=searchEndTime]").val(String(endHour).padStart(2, '0') + ':00');
|
||||
$("input[name=searchEndDate]").val(formatDate(now));
|
||||
$("input[name=searchEndTime]").val(formatTime(now));
|
||||
}
|
||||
|
||||
initCharts();
|
||||
|
||||
@@ -222,7 +222,7 @@
|
||||
$("input[name=searchStartDate], input[name=searchEndDate]").datepicker();
|
||||
|
||||
var today = getToday();
|
||||
var startDate = today;
|
||||
var startDate = today.substring(0,6)+"01";
|
||||
var endDate = today;
|
||||
|
||||
|
||||
@@ -234,6 +234,7 @@
|
||||
}
|
||||
|
||||
list();
|
||||
search();
|
||||
|
||||
resizeJqGridWidth('grid', 'content_middle', '1200');
|
||||
|
||||
|
||||
@@ -298,7 +298,7 @@
|
||||
if(!isStandardMessage){
|
||||
$(adapterSettings.adapterUrlId).text(adapterInfo.urlPath);
|
||||
} else {
|
||||
$(adapterSettings.methodSelectId).val('');
|
||||
//$(adapterSettings.methodSelectId).val('');
|
||||
$(adapterSettings.restPathInputId).val('');
|
||||
$(adapterSettings.adapterUrlId).text('');
|
||||
}
|
||||
@@ -486,6 +486,7 @@
|
||||
}
|
||||
|
||||
if(data.errResponseTransform != null && data.errResponseTransform != '' && data.errTransformYn == 'Y') {
|
||||
$('#toggleTransformYn').bootstrapToggle('on');
|
||||
$('#toggleErrTransformYn').bootstrapToggle('on');
|
||||
$('#rowErrResponseTransform').show();
|
||||
} else {
|
||||
@@ -871,6 +872,10 @@
|
||||
});
|
||||
|
||||
var postData = $('#ajaxForm').serializeArray();
|
||||
|
||||
console.log('modify', postData);
|
||||
console.log('method', $('#inboundHttpMethod').val());
|
||||
|
||||
if (isDetail){
|
||||
postData.push({ name: "cmd" , value:"UPDATE"});
|
||||
}else{
|
||||
|
||||
+3
-2
@@ -91,8 +91,9 @@ dependencies {
|
||||
//implementation project(':kjb-safedb')
|
||||
implementation project(":eapim-admin-djb")
|
||||
|
||||
/* Custom Libs */
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
/* Custom Libs (damo-manager.jar 는 Tomcat lib 가 런타임 제공 → compileOnly, WAR 미포함) */
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'], exclude: ['damo-manager.jar'])
|
||||
compileOnly files('libs/damo-manager.jar')
|
||||
|
||||
//implementation "org.dom4j:dom4j:2.1.3"
|
||||
//implementation "com.rabbitmq:amqp-client:3.6.6"
|
||||
|
||||
+61
-34
@@ -1,9 +1,9 @@
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id 'eclipse'
|
||||
id 'eclipse-wtp'
|
||||
id 'idea'
|
||||
id 'war'
|
||||
id 'project-report'
|
||||
id 'checkstyle'
|
||||
}
|
||||
|
||||
group 'com.eactive'
|
||||
@@ -14,17 +14,19 @@ def quartzVersion = "2.2.1"
|
||||
def queryDslVersion = "5.0.0"
|
||||
def hibernateVersion = "5.6.15.Final"
|
||||
|
||||
/*def nexusUrl = "https://nexus.eactive.synology.me:8090"*/
|
||||
def nexusUrl = "https://nexus.eactive.synology.me:8090"
|
||||
//def useOnJboss = false
|
||||
|
||||
def generatedJavaDir = "$buildDir/generated/java"
|
||||
|
||||
/*repositories {
|
||||
maven {
|
||||
url "${nexusUrl}/repository/maven-public/"
|
||||
allowInsecureProtocol = true
|
||||
}
|
||||
}*/
|
||||
allprojects {
|
||||
repositories {
|
||||
maven {
|
||||
url "${nexusUrl}/repository/maven-public/"
|
||||
allowInsecureProtocol = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
project.webAppDirName = 'WebContent'
|
||||
|
||||
@@ -34,10 +36,6 @@ java {
|
||||
}
|
||||
}
|
||||
|
||||
checkstyle {
|
||||
toolVersion = '8.45.1'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
@@ -48,10 +46,7 @@ sourceSets {
|
||||
|
||||
compileJava {
|
||||
options.encoding = 'UTF-8'
|
||||
options.compilerArgs = [
|
||||
'-parameters',
|
||||
'-Aquerydsl.generatedAnnotationClass=com.querydsl.core.annotations.Generated'
|
||||
]
|
||||
options.compilerArgs = ['-parameters']
|
||||
options.generatedSourceOutputDirectory = project.file(generatedJavaDir)
|
||||
}
|
||||
|
||||
@@ -67,7 +62,6 @@ war {
|
||||
// rootSpec.exclude '**/persistence.xml'
|
||||
exclude '**/context.xml'
|
||||
exclude '**/context-*.xml'
|
||||
exclude '**/context_*.xml'
|
||||
archiveFileName = "eapim-admin.war"
|
||||
duplicatesStrategy = DuplicatesStrategy.WARN
|
||||
}
|
||||
@@ -89,11 +83,12 @@ dependencies {
|
||||
implementation project(':elink-online-common')
|
||||
implementation project(':elink-online-emsclient')
|
||||
implementation project(':elink-portal-common')
|
||||
implementation project(':kjb-safedb')
|
||||
implementation project(":eapim-admin-kjb")
|
||||
//implementation project(':kjb-safedb')
|
||||
implementation project(":eapim-admin-djb")
|
||||
|
||||
/* Custom Libs */
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
/* Custom Libs (damo-manager.jar 는 Tomcat lib 가 런타임 제공 → compileOnly, WAR 미포함) */
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'], exclude: ['damo-manager.jar'])
|
||||
compileOnly files('libs/damo-manager.jar')
|
||||
|
||||
//implementation "org.dom4j:dom4j:2.1.3"
|
||||
//implementation "com.rabbitmq:amqp-client:3.6.6"
|
||||
@@ -126,7 +121,7 @@ dependencies {
|
||||
implementation "org.snmp4j:snmp4j:1.10.1"
|
||||
implementation "com.googlecode.json-simple:json-simple:1.1.1"
|
||||
|
||||
implementation "io.netty:netty-all:4.1.0.Final"
|
||||
implementation "io.netty:netty-all:4.1.94.Final"
|
||||
|
||||
compileOnly "org.apache.mina:mina-filter-ssl:1.1.6"
|
||||
compileOnly "org.apache.mina:mina-core:1.1.6"
|
||||
@@ -203,13 +198,12 @@ dependencies {
|
||||
implementation 'software.amazon.awssdk:sso:2.20.142'
|
||||
implementation 'software.amazon.awssdk:sts:2.20.142'
|
||||
|
||||
implementation ('io.kubernetes:client-java:18.0.1') {
|
||||
exclude group: 'org.slf4j', module: 'slf4j-api'
|
||||
exclude group: 'org.slf4j', module: 'logback-classic'
|
||||
}
|
||||
|
||||
implementation group: 'commons-net', name: 'commons-net', version: '3.5'
|
||||
|
||||
// JDK 8 의 rt.jar 에는 org.w3c.dom.ElementTraversal 이 없어 xercesImpl 가 NCDFE 를 일으킴.
|
||||
// xml-apis 1.4.01 에 그 클래스가 포함됨. 직접 의존성으로 묶어 WAR 에 패키징되도록 함.
|
||||
implementation 'xml-apis:xml-apis:1.4.01'
|
||||
|
||||
testRuntimeOnly 'com.h2database:h2:2.1.214'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
|
||||
@@ -229,18 +223,51 @@ configurations.all {
|
||||
cacheDynamicVersionsFor 10, 'minutes'
|
||||
// Do not cache changing modules
|
||||
cacheChangingModulesFor 0, 'seconds'
|
||||
|
||||
// JDK 8 의 rt.jar 에는 org.w3c.dom.ElementTraversal 이 없음.
|
||||
// 일부 transitive 가 끌어오는 xml-apis:1.0.b2 (2002, DOM L2) 는 이 클래스를 포함하지 않아
|
||||
// xercesImpl 가 NoClassDefFoundError 를 일으킴 → 1.4.01 로 강제 통일.
|
||||
force 'xml-apis:xml-apis:1.4.01'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
task settingEclipseEncoding {
|
||||
if (project.file('.settings').exists()) {
|
||||
File f = file('.settings/org.eclipse.core.resources.prefs')
|
||||
f.write('eclipse.preferences.version=1\n')
|
||||
f.append('encoding/<project>=utf-8')
|
||||
}
|
||||
}
|
||||
|
||||
task initDirs() {
|
||||
file(generatedJavaDir).mkdirs()
|
||||
}
|
||||
|
||||
|
||||
tasks.withType(Checkstyle) {
|
||||
reports {
|
||||
xml.required = false
|
||||
html.required = true
|
||||
}
|
||||
eclipse {
|
||||
wtp {
|
||||
component {
|
||||
contextPath = 'monitoring'
|
||||
}
|
||||
}
|
||||
synchronizationTasks settingEclipseEncoding, initDirs
|
||||
jdt {
|
||||
apt {
|
||||
// generated 된 패스 경로를 .setting/org.eclipse.jdt.apt.core.prefs 의 org.eclipse.jdt.apt.genSrcDir에 적용한다.
|
||||
// project > properties > Java Compiler > Annoation Processing 화면에서 확인 가능하다.
|
||||
genSrcDir = file(generatedJavaDir)
|
||||
}
|
||||
}
|
||||
project {
|
||||
if (!natures.contains('org.eclipse.buildship.core.gradleprojectnature')) {
|
||||
natures.add('org.eclipse.buildship.core.gradleprojectnature')
|
||||
buildCommand 'org.eclipse.buildship.core.gradleprojectbuilder'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
options.fork = true // 컴파일을 별도 프로세스로 분리
|
||||
options.forkOptions.memoryMaximumSize = '4g' // 컴파일러에 2GB 할당 (필요시 4g로 증량)
|
||||
options.encoding = 'UTF-8'
|
||||
}
|
||||
Binary file not shown.
+3
-1
@@ -41,10 +41,12 @@ public class AuditLogInterceptor implements HandlerInterceptor {
|
||||
|
||||
if (isAuditableUsecase.isAuditable(userId, command, logType, systemCode)) {
|
||||
String parameters = getParametersAsString(request);
|
||||
String message = request.getParameter("auditReason");
|
||||
log
|
||||
.debug("audit log save : logType={}, cmd={}, user={}, parameters={}", logType, command,
|
||||
userId, parameters);
|
||||
saveAuditLogUseCase.log(systemCode, logType, command, remoteAddress, userId, parameters);
|
||||
saveAuditLogUseCase
|
||||
.log(systemCode, logType, command, remoteAddress, userId, parameters, message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ public class AuditLog {
|
||||
|
||||
private String logTypeText;
|
||||
|
||||
private String message;
|
||||
|
||||
private String parameters;
|
||||
|
||||
private LocalDateTime lastModifiedDate;
|
||||
|
||||
@@ -3,6 +3,6 @@ package com.eactive.eai.rms.common.acl.audit.port.in;
|
||||
public interface SaveAuditLogUseCase {
|
||||
|
||||
public void log(String systemCode, String logType, String command, String remoteAddress, String userId,
|
||||
String parameters);
|
||||
String parameters, String message);
|
||||
|
||||
}
|
||||
|
||||
@@ -59,15 +59,16 @@ class AuditLogService
|
||||
|
||||
@Override
|
||||
public void log(String systemCode, String logType, String command, String remoteAddress, String userId,
|
||||
String parameters) {
|
||||
String parameters, String message) {
|
||||
|
||||
AuditLog auditLog = new AuditLog(systemCode, logType, command, userId, remoteAddress);
|
||||
String auditPointKey = AuditPoint.generateKey(logType, systemCode, command);
|
||||
AuditPoint auditPoint = auditPoints.get(auditPointKey);
|
||||
|
||||
|
||||
auditLog.setLogTypeText(auditPoint.getLogTypeText());
|
||||
auditLog.setLastModifiedDate(LocalDateTime.now());
|
||||
auditLog.setParameters(parameters);
|
||||
auditLog.setMessage(message);
|
||||
|
||||
saveAuditLogPort.save(auditLog);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.eactive.eai.rms.common.acl.sitemap;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
import com.eactive.eai.rms.common.base.BaseAnnotationController;
|
||||
|
||||
@Controller
|
||||
@RequiredArgsConstructor
|
||||
public class SitemapController extends BaseAnnotationController {
|
||||
|
||||
private final SitemapService sitemapService;
|
||||
|
||||
@GetMapping(value = "/common/acl/sitemap/sitemapMan.view")
|
||||
public String view(@RequestParam(value = "serviceType", required = false) String serviceType, Model model) {
|
||||
model.addAttribute("sitemapTree", sitemapService.getSitemapTree(serviceType));
|
||||
return "/common/acl/sitemap/sitemapMan";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.eactive.eai.rms.common.acl.sitemap;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.eactive.eai.rms.common.acl.sitemap.ui.SitemapNode;
|
||||
|
||||
public interface SitemapService {
|
||||
|
||||
public List<SitemapNode> getSitemapTree(String serviceType);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package com.eactive.eai.rms.common.acl.sitemap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.eactive.eai.rms.common.acl.sitemap.ui.SitemapNode;
|
||||
import com.eactive.eai.rms.common.base.BaseService;
|
||||
import com.eactive.eai.rms.data.entity.man.menu.Menu;
|
||||
import com.eactive.eai.rms.data.entity.man.menu.MenuService;
|
||||
import com.eactive.eai.rms.data.entity.man.role.RoleMenuAuthService;
|
||||
|
||||
@Service("sitemapService")
|
||||
@Transactional(transactionManager = "transactionManagerForEMS")
|
||||
public class SitemapServiceImpl extends BaseService implements SitemapService {
|
||||
|
||||
@Autowired
|
||||
private MenuService menuService;
|
||||
|
||||
@Autowired
|
||||
private RoleMenuAuthService roleMenuAuthService;
|
||||
|
||||
public List<SitemapNode> getSitemapTree(String serviceType) {
|
||||
Menu root = menuService.getById("ROOT_DEV");
|
||||
Menu langRoot = findLangRoot(root);
|
||||
List<SitemapNode> tree = new ArrayList<>();
|
||||
if (langRoot == null) {
|
||||
return tree;
|
||||
}
|
||||
|
||||
Set<String> serviceTypeMenuIds = StringUtils.isBlank(serviceType)
|
||||
? null
|
||||
: new HashSet<>(roleMenuAuthService.findMenuIdsByServiceType(serviceType));
|
||||
|
||||
for (Menu menu : sortedChildren(langRoot)) {
|
||||
if (isVisible(menu) && matchesServiceType(menu, serviceTypeMenuIds)) {
|
||||
tree.add(buildNode(menu, serviceTypeMenuIds));
|
||||
}
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
|
||||
// 엔티티의 childMenus는 menuId 기준으로 정렬되어 있어, 실제 메뉴(운영화면 GNB)와 동일하게 sortOrder 기준으로 재정렬한다.
|
||||
private List<Menu> sortedChildren(Menu menu) {
|
||||
List<Menu> children = new ArrayList<>(menu.getChildMenus());
|
||||
children.sort(Comparator.comparing(Menu::getSortOrder, Comparator.nullsLast(Comparator.naturalOrder())));
|
||||
return children;
|
||||
}
|
||||
|
||||
// MenuService.getMenuList()의 GNB 렌더링과 동일하게 "ROOT"(한글)/"ROOT_EN"(영문) 이름의 언어별 루트를 찾는다.
|
||||
private Menu findLangRoot(Menu root) {
|
||||
String lang = LocaleContextHolder.getLocale().getLanguage();
|
||||
String langRootName = (StringUtils.isBlank(lang) || "ko".equalsIgnoreCase(lang))
|
||||
? "ROOT"
|
||||
: "ROOT_" + lang.toUpperCase();
|
||||
|
||||
return root.getChildMenus().stream()
|
||||
.filter(m -> langRootName.equalsIgnoreCase(m.getMenuName()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private SitemapNode buildNode(Menu menu, Set<String> serviceTypeMenuIds) {
|
||||
SitemapNode node = new SitemapNode(menu);
|
||||
for (Menu child : sortedChildren(menu)) {
|
||||
if (isVisible(child) && matchesServiceType(child, serviceTypeMenuIds)) {
|
||||
node.getChildren().add(buildNode(child, serviceTypeMenuIds));
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
private boolean isVisible(Menu menu) {
|
||||
return "Y".equalsIgnoreCase(menu.getDisplayYn()) && "Y".equalsIgnoreCase(menu.getUseYn());
|
||||
}
|
||||
|
||||
// serviceType 필터가 없으면 통과. 있으면 자신이나 하위메뉴 중 하나라도 해당 serviceType 권한이 있어야 통과(상위메뉴 누락 방지).
|
||||
private boolean matchesServiceType(Menu menu, Set<String> serviceTypeMenuIds) {
|
||||
if (serviceTypeMenuIds == null) {
|
||||
return true;
|
||||
}
|
||||
if (serviceTypeMenuIds.contains(menu.getMenuId())) {
|
||||
return true;
|
||||
}
|
||||
return menu.getChildMenus().stream().anyMatch(child -> matchesServiceType(child, serviceTypeMenuIds));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.eactive.eai.rms.common.acl.sitemap.ui;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.eactive.eai.rms.data.entity.man.menu.Menu;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class SitemapNode {
|
||||
|
||||
private String menuId;
|
||||
|
||||
private String menuName;
|
||||
|
||||
private String menuUrl;
|
||||
|
||||
private List<SitemapNode> children = new ArrayList<>();
|
||||
|
||||
public SitemapNode(Menu menu) {
|
||||
this.menuId = menu.getMenuId();
|
||||
this.menuName = menu.getMenuName();
|
||||
this.menuUrl = menu.getMenuUrl();
|
||||
}
|
||||
|
||||
public SitemapNode() {
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,7 @@ import com.eactive.eai.rms.data.entity.man.role.Role;
|
||||
import com.eactive.eai.rms.data.entity.man.role.RoleService;
|
||||
import com.eactive.eai.rms.data.entity.onl.bzwkdstcd.UserBusinessService;
|
||||
import com.eactive.eai.rms.onl.common.exception.BizException;
|
||||
import com.eactive.ext.djb.DamoManager;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@@ -40,6 +41,8 @@ import lombok.RequiredArgsConstructor;
|
||||
@Transactional(transactionManager = "transactionManagerForEMS")
|
||||
@RequiredArgsConstructor
|
||||
public class UserManService extends BaseService {
|
||||
|
||||
|
||||
|
||||
private final UserInfoService userInfoService;
|
||||
private final UserRoleService userRoleService;
|
||||
@@ -97,9 +100,11 @@ public class UserManService extends BaseService {
|
||||
|
||||
UserInfo userInfo = userUIMapper.toEntity(userUI);
|
||||
userInfo.setRoleidnfiname(CommonConstants.DEPT_DEVELOPER);
|
||||
|
||||
String subfix = monitoringContext.getStringProperty(MonitoringContext.RMS_PASSWORD_INIT_SUBFIX, "@!");
|
||||
|
||||
try {
|
||||
userInfo.setPassword(Seed.encrypt(userUI.getUserId()));
|
||||
userInfo.setPassword(DamoManager.getInstance().hash(DamoManager.SHA256,userUI.getUserId()+subfix));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error encrypting password", e);
|
||||
}
|
||||
@@ -139,8 +144,9 @@ public class UserManService extends BaseService {
|
||||
}
|
||||
|
||||
public void updatePassword(UserUI userUI) throws Exception {
|
||||
String subfix = monitoringContext.getStringProperty(MonitoringContext.RMS_PASSWORD_INIT_SUBFIX, "@!");
|
||||
UserInfo userInfo = userInfoService.getById(userUI.getUserId());
|
||||
userInfo.setPassword(Seed.encrypt(userUI.getUserId()));
|
||||
userInfo.setPassword(DamoManager.getInstance().hash(DamoManager.SHA256, userUI.getUserId()+subfix));
|
||||
userUIMapper.updateToEntity(userUI, userInfo);
|
||||
userInfoService.save(userInfo);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.eactive.eai.rms.data.entity.man.user.UserServiceType;
|
||||
import com.eactive.eai.rms.data.entity.man.user.UserServiceTypeId;
|
||||
import com.eactive.eai.rms.data.entity.man.user.UserServiceTypeService;
|
||||
import com.eactive.eai.rms.data.entity.onl.bzwkdstcd.UserBusinessService;
|
||||
import com.eactive.ext.djb.DamoManager;
|
||||
|
||||
@Service
|
||||
@Transactional
|
||||
@@ -124,7 +125,7 @@ public class UserSyncService extends BaseService {
|
||||
UserInfo userInfo = new UserInfo();
|
||||
userInfo.setUserid(userId);
|
||||
userInfo.setUsername(userInfoMap.get("EMPY_NM").toString());
|
||||
userInfo.setPassword(Seed.encrypt(userId));
|
||||
userInfo.setPassword(DamoManager.getInstance().hash(DamoManager.SHA256,userId));
|
||||
userInfo.setRoleidnfiname(CommonConstants.DEPT_DEVELOPER);
|
||||
|
||||
userInfoService.save(userInfo);
|
||||
|
||||
@@ -258,9 +258,15 @@ public interface MonitoringContext {
|
||||
// 자동 로그아웃 타임아웃 (단위: 분, 기본값: 10)
|
||||
public static final String RMS_AUTO_LOGOUT_TIMEOUT = "rms.auto.logout.timeout";
|
||||
|
||||
// 웹훅 재전송 설정
|
||||
public static final String API_WEBHOOK_RETRY_COUNT = "api.webhook.retry_count";
|
||||
public static final String API_WEBHOOK_RETRY_TIME = "api.webhook.retry_time";
|
||||
// 웹훅
|
||||
public static final String RMS_WEBHOOK_REVERSE_PROXY_URL = "rms.webhook.reverse_proxy.url";
|
||||
public static final String RMS_WEBHOOK_RETRY_COUNT = "rms.webhook.retry_count";
|
||||
public static final String RMS_WEBHOOK_RETRY_TIME = "rms.webhook.retry_time";
|
||||
|
||||
//비밀번호 초기화 접미사
|
||||
public static final String RMS_PASSWORD_INIT_SUBFIX = "rms.password.init.subfix";
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -53,9 +53,11 @@ class RequestWrapper extends HttpServletRequestWrapper {
|
||||
"^data:image/.*",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
|
||||
// XSS 문자 변환을 건너뛸 파라미터 목록 (리치 텍스트 에디터 콘텐츠 등)
|
||||
// XSS 패턴 필터링은 적용하지만 <, > 등으로 변환하지 않음
|
||||
private static final String[] SKIP_CHAR_CONVERT_PARAMS = {"contents", "content", "description"};
|
||||
// XSS 문자 변환을 건너뛸 파라미터 목록 (리치 텍스트 에디터 콘텐츠, 메시지 템플릿 본문 등)
|
||||
// XSS 패턴 필터링은 적용하지만 <, >, & 등으로 변환하지 않음
|
||||
// (메시지 템플릿은 SMS/메신저 평문으로 전송되므로 '&'가 '&'로 깨지면 안 됨)
|
||||
private static final String[] SKIP_CHAR_CONVERT_PARAMS = {"contents", "content", "description",
|
||||
"subjectTemplate", "smsTemplate", "emailTemplate", "messengerTemplate"};
|
||||
|
||||
// 현재 처리 중인 파라미터명 (cleanXSS에서 문자 변환 여부 결정에 사용)
|
||||
private ThreadLocal<String> currentParameter = new ThreadLocal<>();
|
||||
|
||||
@@ -45,4 +45,9 @@ public class LoginResponseDto {
|
||||
* 에러 메시지 (로그인 실패 시)
|
||||
*/
|
||||
private String errorMessage;
|
||||
|
||||
/**
|
||||
* 초기 비밀번호 변경 필요 여부
|
||||
*/
|
||||
private boolean changePassword;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.springframework.web.servlet.View;
|
||||
|
||||
import com.eactive.eai.common.seed.Seed;
|
||||
import com.eactive.eai.rms.ext.kjb.smsauth.SmsAuthService;
|
||||
import com.eactive.ext.djb.DamoManager;
|
||||
import com.eactive.eai.data.converter.LocalDateTimeFormatters;
|
||||
import com.eactive.eai.rms.common.context.MonitoringContext;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceType;
|
||||
@@ -72,7 +73,8 @@ public class MainController implements InterceptorSkipController {
|
||||
private final LoginService loginService;
|
||||
private final MonitoringPropertyService monitoringPropertyService;
|
||||
private final UserLoginHistoryService userLoginHistoryService;
|
||||
|
||||
private final SmsAuthService smsAuthService;
|
||||
|
||||
@Autowired
|
||||
public MainController(LocaleMessage localeMessage,
|
||||
MonitoringContext monitoringContext,
|
||||
@@ -81,7 +83,8 @@ public class MainController implements InterceptorSkipController {
|
||||
UserRoleService userRoleService,
|
||||
UserServiceTypeService userServiceTypeService,
|
||||
MonitoringPropertyService monitoringPropertyService,
|
||||
UserLoginHistoryService userLoginHistoryService) {
|
||||
UserLoginHistoryService userLoginHistoryService,
|
||||
SmsAuthService smsAuthService) {
|
||||
this.localeMessage = localeMessage;
|
||||
this.monitoringContext = monitoringContext;
|
||||
this.userInfoService = userInfoService;
|
||||
@@ -90,6 +93,7 @@ public class MainController implements InterceptorSkipController {
|
||||
this.userServiceTypeService = userServiceTypeService;
|
||||
this.monitoringPropertyService = monitoringPropertyService;
|
||||
this.userLoginHistoryService = userLoginHistoryService;
|
||||
this.smsAuthService = smsAuthService;
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/loginForm.do")
|
||||
@@ -187,6 +191,19 @@ public class MainController implements InterceptorSkipController {
|
||||
.redirectUrl(redirectUrl)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
// 2.1 초기 비밀번호인 경우, 비밀번호 변경해야 함
|
||||
String subfix = monitoringContext.getStringProperty(MonitoringContext.RMS_PASSWORD_INIT_SUBFIX, "@!");
|
||||
if (userInfo.getPassword().equals(DamoManager.getInstance().hash(DamoManager.SHA256, userInfo.getUserid()+subfix))
|
||||
|| userInfo.getPassword().equals(DamoManager.getInstance().hash(DamoManager.SHA256, userInfo.getUserid()))) {
|
||||
return LoginResponseDto.builder()
|
||||
.success(false)
|
||||
.changePassword(true)
|
||||
.errorMessage("비밀번호 변경후 사용가능합니다.")
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
// 3. SMS 인증 필요 여부 체크
|
||||
SmsAuthService smsAuthService = getSmsAuthService();
|
||||
@@ -202,7 +219,7 @@ public class MainController implements InterceptorSkipController {
|
||||
|
||||
// SMS 인증번호 생성 및 발송
|
||||
String authCode = smsAuthService.generateAuthCode();
|
||||
boolean sent = smsAuthService.sendAuthCode(userInfo.getCphnno(), authCode);
|
||||
boolean sent = smsAuthService.sendAuthCode(userInfo, authCode);
|
||||
|
||||
if (!sent) {
|
||||
return LoginResponseDto.builder()
|
||||
@@ -263,16 +280,8 @@ public class MainController implements InterceptorSkipController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SmsAuthService 인스턴스 조회 (singleton)
|
||||
*/
|
||||
private SmsAuthService getSmsAuthService() {
|
||||
try {
|
||||
return SmsAuthService.getInstance();
|
||||
} catch (Exception e) {
|
||||
logger.warn("SmsAuthService 초기화 실패 - SMS 인증 비활성화 처리", e);
|
||||
return null;
|
||||
}
|
||||
return smsAuthService;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -363,7 +372,7 @@ public class MainController implements InterceptorSkipController {
|
||||
String loginMode = System.getProperty("login.mode");
|
||||
if("db".equals(loginMode) || "DB".equals(loginMode)) {
|
||||
/* 기존로직 - LDAP 사용 x */
|
||||
if (!userInfo.getPassword().equals(Seed.encrypt(dto.getPassword()))) {
|
||||
if (!userInfo.getPassword().equals(DamoManager.getInstance().hash(DamoManager.SHA256, dto.getPassword()))) {
|
||||
// password 다름.
|
||||
setLoginFailure(request, session, "login.loginfail1",
|
||||
"login.loginfail2");
|
||||
@@ -762,6 +771,9 @@ public class MainController implements InterceptorSkipController {
|
||||
String changePassword, String confirmPassword,
|
||||
String resetUserId, String resetPassword) {
|
||||
HttpSession session = request.getSession();
|
||||
// AJAX 로그인 인증 후 초기비밀번호 변경 경로에서 세션에 부분 로그인 상태가 남아
|
||||
// root emergency.jsp가 main.do로 잘못 리다이렉트하는 것을 방지
|
||||
session.removeAttribute(CommonConstants.LOGIN);
|
||||
|
||||
try {
|
||||
|
||||
@@ -799,9 +811,9 @@ public class MainController implements InterceptorSkipController {
|
||||
.getString("login.pwdmismatch4"));
|
||||
return REDIRECT_CHANGE_PASSWORD_URL;
|
||||
}
|
||||
|
||||
|
||||
// 3. 비밀번호 체크
|
||||
if (!userInfo.getPassword().equals(Seed.encrypt(resetPassword))
|
||||
if (!userInfo.getPassword().equals(DamoManager.getInstance().hash(DamoManager.SHA256,resetPassword))
|
||||
|| !userInfo.getUserid().equals(resetUserId)) {
|
||||
UserAccessLogger.log(request, LOG_CATEGORY_LOGIN, "F",
|
||||
localeMessage
|
||||
@@ -812,13 +824,15 @@ public class MainController implements InterceptorSkipController {
|
||||
}
|
||||
|
||||
// 4. 비밀번호 변경
|
||||
userInfo.setPassword(Seed.encrypt(changePassword));
|
||||
userInfo.setPassword(DamoManager.getInstance().hash(DamoManager.SHA256,changePassword));
|
||||
userInfoService.save(userInfo);
|
||||
|
||||
UserAccessLogger.log(request, LOG_CATEGORY_LOGIN, "S", localeMessage
|
||||
.getString("login.pwdchanged"));
|
||||
session.setAttribute(RESULT_MSG, localeMessage
|
||||
.getString("login.pwdchanged"));
|
||||
logger.debug("\n\n\n\n===================" + localeMessage
|
||||
.getString("login.pwdchanged"));
|
||||
return REDIRECT_LOGIN_URL;
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -57,7 +57,7 @@ public class ClusteredSchedulerConfig {
|
||||
|
||||
quartzProperties.put("org.quartz.jobStore.misfireThreshold", "60000");
|
||||
quartzProperties.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");
|
||||
quartzProperties.put("org.quartz.jobStore.driverDelegateClass", "org.quartz.impl.jdbcjobstore.PostgreSQLDelegate");
|
||||
quartzProperties.put("org.quartz.jobStore.driverDelegateClass", "org.quartz.impl.jdbcjobstore.StdJDBCDelegate");
|
||||
quartzProperties.put("org.quartz.jobStore.useProperties", "false");
|
||||
quartzProperties.put("org.quartz.jobStore.dataSource", monitoringSchema);
|
||||
quartzProperties.put("org.quartz.jobStore.tablePrefix", monitoringSchema + ".QRTZ_");
|
||||
|
||||
@@ -15,4 +15,6 @@ public interface EMSScheduler {
|
||||
|
||||
void deleteAndAddJob(Scheduler scheduler, String jobName) throws SchedulerException, ClassNotFoundException;
|
||||
|
||||
void reloadJob(String jobName) throws SchedulerException, ClassNotFoundException;
|
||||
|
||||
}
|
||||
|
||||
@@ -120,12 +120,60 @@ public class EMSSchedulerImpl implements EMSScheduler {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reloadJob(String jobName) throws SchedulerException, ClassNotFoundException {
|
||||
JobInfo jobInfo = jobInfoService.getById(jobName);
|
||||
if (CLUSTERED.equals(jobInfo.getInstanceName())) {
|
||||
deleteJob(scheduler, jobName);
|
||||
reloadClusteredJob(jobInfo);
|
||||
} else {
|
||||
deleteAndAddJob(scheduler, jobInfo);
|
||||
deleteJob(clusteredScheduler, jobName);
|
||||
}
|
||||
}
|
||||
|
||||
private void reloadClusteredJob(JobInfo jobInfo) throws SchedulerException, ClassNotFoundException {
|
||||
String jobId = jobInfo.getId();
|
||||
TriggerKey triggerKey = new TriggerKey(getTriggerName(jobId), Scheduler.DEFAULT_GROUP);
|
||||
JobKey jobKey = new JobKey(jobId, Scheduler.DEFAULT_GROUP);
|
||||
|
||||
Trigger existing = clusteredScheduler.getTrigger(triggerKey);
|
||||
JobDetail existingJob = clusteredScheduler.getJobDetail(jobKey);
|
||||
|
||||
if (existing != null && existingJob != null) {
|
||||
// JobDetail(JobData 포함) 갱신
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<Job> jobClass = (Class<Job>) Class.forName(jobInfo.getJobClassName());
|
||||
JobBuilder jobBuilder = newJob(jobClass).withIdentity(jobKey);
|
||||
for (JobData jobData : jobInfo.getJobDatas()) {
|
||||
jobBuilder.usingJobData(jobData.getId().getDataKey(), jobData.getDataValue());
|
||||
}
|
||||
clusteredScheduler.addJob(jobBuilder.build(), true);
|
||||
|
||||
// 트리거(cron) 갱신
|
||||
CronTrigger newTrigger = newTrigger()
|
||||
.withIdentity(triggerKey)
|
||||
.withSchedule(cronSchedule(jobInfo.getCronExp()))
|
||||
.build();
|
||||
Date ft = clusteredScheduler.rescheduleJob(triggerKey, newTrigger);
|
||||
if (ft != null) {
|
||||
return;
|
||||
}
|
||||
log.warn("[reloadClusteredJob] {} rescheduleJob returned null, falling back to deleteAndAddJob", jobId);
|
||||
} else if (existing != null) {
|
||||
// orphaned trigger (job missing) — clean up before re-adding
|
||||
log.warn("[reloadClusteredJob] {} has orphaned trigger without job, unscheduling", jobId);
|
||||
clusteredScheduler.unscheduleJob(triggerKey);
|
||||
}
|
||||
deleteAndAddJob(clusteredScheduler, jobInfo);
|
||||
}
|
||||
|
||||
public void deleteJob(Scheduler inScheduler, String jobNm) throws SchedulerException {
|
||||
String schedulerType = (inScheduler == clusteredScheduler) ? "clusteredScheduler" : "scheduler";
|
||||
JobKey key = new JobKey(jobNm, Scheduler.DEFAULT_GROUP);
|
||||
JobDetail jobDetail = inScheduler.getJobDetail(key);
|
||||
if (jobDetail != null) {
|
||||
inScheduler.deleteJob(key);
|
||||
log.info(jobNm + " has been scheduled to deleted");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,10 +199,7 @@ public class EMSSchedulerImpl implements EMSScheduler {
|
||||
.withSchedule(cronSchedule(info.getCronExp()))
|
||||
.build();
|
||||
|
||||
Date ft = inScheduler.scheduleJob(job, trigger);
|
||||
log
|
||||
.info("{} has been scheduled to run at: {} and repeat based on expression: {}", job.getKey(), ft,
|
||||
trigger.getCronExpression());
|
||||
inScheduler.scheduleJob(job, trigger);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,9 @@ public class SchedulerManController extends BaseAnnotationController {
|
||||
@Qualifier("monitoringSyncAgent")
|
||||
private MonitoringSyncAgent monitoringSyncAgent;
|
||||
|
||||
@Autowired
|
||||
private EMSScheduler emsScheduler;
|
||||
|
||||
@GetMapping(value = "/common/scheduler/schedulerMan.view")
|
||||
public String viewList() {
|
||||
return "/common/scheduler/schedulerMan";
|
||||
@@ -96,6 +99,11 @@ public class SchedulerManController extends BaseAnnotationController {
|
||||
@PostMapping(value = "/common/scheduler/schedulerMan.json", params = "cmd=UPDATE")
|
||||
public ResponseEntity<Void> update(JobInfoUI jobInfoUI) {
|
||||
service.update(jobInfoUI);
|
||||
try {
|
||||
emsScheduler.reloadJob(jobInfoUI.getId());
|
||||
} catch (Exception e) {
|
||||
logger.error("reloadJob error on current server", e);
|
||||
}
|
||||
syncReloadScheduler(jobInfoUI.getId());
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
@@ -115,7 +123,7 @@ public class SchedulerManController extends BaseAnnotationController {
|
||||
String url = "/monitoring/schedulerReload.do";
|
||||
|
||||
try {
|
||||
ret = monitoringSyncAgent.sync(url, paramMap);
|
||||
ret = monitoringSyncAgent.targetSync(url, paramMap);
|
||||
} catch (Exception e) {
|
||||
logger.error("syncReloadScheduler error", e);
|
||||
} finally {
|
||||
|
||||
@@ -51,4 +51,17 @@ public class RoleMenuAuthService extends AbstractEMSDataSerivce<RoleMenuAuth, Ro
|
||||
.fetch();
|
||||
}
|
||||
|
||||
public List<String> findMenuIdsByServiceType(String serviceType) {
|
||||
|
||||
QRoleMenuAuth qRoleMenuAuth = QRoleMenuAuth.roleMenuAuth;
|
||||
|
||||
return getJPAQueryFactory()
|
||||
.select(qRoleMenuAuth.id.menuId)
|
||||
.distinct()
|
||||
.from(qRoleMenuAuth)
|
||||
.where(qRoleMenuAuth.id.serviceType.eq(serviceType))
|
||||
.setHint(QueryHints.CACHEABLE, true)
|
||||
.fetch();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package com.eactive.eai.rms.data.entity.onl.apim.messagerequest;
|
||||
|
||||
import com.eactive.apim.portal.template.entity.MessageRequest;
|
||||
import com.eactive.eai.data.jpa.BaseRepository;
|
||||
import com.eactive.eai.rms.data.EMSDataSource;
|
||||
|
||||
/**
|
||||
* 발송 내역(PTL_MESSAGE_REQUEST) 조회용 admin 전용 리포지토리.
|
||||
*
|
||||
* <p>elink-portal-common 의 {@code template.repository.MessageRequestRepository}(JpaRepository) 와
|
||||
* <b>단순 클래스명이 겹치면 Spring Data 빈 이름이 충돌</b>하므로(둘 다 admin 컨텍스트에 스캔됨)
|
||||
* 이름을 분리한다. 또한 QueryDSL {@code findAll(predicate, pageable)} 를 쓰기 위해
|
||||
* {@link BaseRepository} 를 상속한다.</p>
|
||||
*/
|
||||
@EMSDataSource
|
||||
public interface MessageRequestEmsRepository extends BaseRepository<MessageRequest, String> {
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
package com.eactive.eai.rms.data.entity.onl.apim.messagerequest;
|
||||
|
||||
import com.eactive.apim.portal.template.entity.MessageCode;
|
||||
import com.eactive.apim.portal.template.entity.MessageRequest;
|
||||
import com.eactive.apim.portal.template.entity.QMessageRequest;
|
||||
import com.eactive.eai.rms.common.util.StringUtils;
|
||||
import com.eactive.eai.rms.data.jpa.AbstractEMSDataSerivce;
|
||||
import com.eactive.eai.rms.onl.common.exception.BizException;
|
||||
import com.querydsl.core.BooleanBuilder;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Service
|
||||
@Transactional(transactionManager = "transactionManagerForEMS")
|
||||
public class MessageRequestService
|
||||
extends AbstractEMSDataSerivce<MessageRequest, String, MessageRequestEmsRepository> {
|
||||
|
||||
public Page<MessageRequest> findAll(Pageable pageable, MessageRequestUISearch search) {
|
||||
QMessageRequest q = QMessageRequest.messageRequest;
|
||||
BooleanBuilder predicate = new BooleanBuilder();
|
||||
|
||||
if (StringUtils.isNotBlank(search.getSearchRequestStatus())) {
|
||||
predicate.and(q.requestStatus.eq(search.getSearchRequestStatus()));
|
||||
}
|
||||
if (StringUtils.isNotBlank(search.getSearchMessageCode())) {
|
||||
MessageCode.findServiceId(search.getSearchMessageCode())
|
||||
.ifPresent(code -> predicate.and(q.messageCode.eq(code)));
|
||||
}
|
||||
if (StringUtils.isNotBlank(search.getSearchMessageType())) {
|
||||
predicate.and(q.messageType.eq(search.getSearchMessageType()));
|
||||
}
|
||||
if (StringUtils.isNotBlank(search.getSearchRecipient())) {
|
||||
predicate.and(q.username.containsIgnoreCase(search.getSearchRecipient()));
|
||||
}
|
||||
// requestDate 는 yyyyMMddHHmmss 14자리 문자열로 저장(LocalDateTimeToStringConverter14)되므로
|
||||
// 고정폭 비교로 기간 필터가 동작한다.
|
||||
if (search.getSearchFromDate() != null) {
|
||||
predicate.and(q.requestDate.goe(search.getSearchFromDate().atStartOfDay()));
|
||||
}
|
||||
if (search.getSearchToDate() != null) {
|
||||
predicate.and(q.requestDate.loe(search.getSearchToDate().atTime(23, 59, 59)));
|
||||
}
|
||||
|
||||
// 발송요청 역순 고정 (Pageable 에 정렬이 없을 때의 안전장치)
|
||||
Sort base = Sort.by(Sort.Direction.DESC, "requestDate");
|
||||
Sort sort = pageable.getSort().isSorted() ? pageable.getSort() : base;
|
||||
Pageable fixed = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), sort);
|
||||
|
||||
return repository.findAll(predicate, fixed);
|
||||
}
|
||||
|
||||
/** 특정 일자에 SMS/KAKAO 로 발송 요청된 건수 (UMS 일련번호 채번용). */
|
||||
public long countSmsKakaoRequestsOnDate(LocalDate date) {
|
||||
QMessageRequest q = QMessageRequest.messageRequest;
|
||||
BooleanBuilder predicate = new BooleanBuilder();
|
||||
predicate.and(q.messageType.in("SMS", "KAKAO"));
|
||||
predicate.and(q.requestDate.goe(date.atStartOfDay()));
|
||||
predicate.and(q.requestDate.loe(date.atTime(23, 59, 59)));
|
||||
return repository.count(predicate);
|
||||
}
|
||||
|
||||
/** PENDING 상태인 건만 FAILED 로 전환한다. */
|
||||
public void updateStatusToFailedIfPending(String id) {
|
||||
MessageRequest entity = getById(id);
|
||||
if (!"PENDING".equals(entity.getRequestStatus())) {
|
||||
throw new BizException("PENDING 상태인 건만 변경할 수 있습니다.");
|
||||
}
|
||||
entity.setRequestStatus("FAILED");
|
||||
save(entity);
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package com.eactive.eai.rms.data.entity.onl.apim.messagerequest;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Data
|
||||
public class MessageRequestUISearch {
|
||||
|
||||
/* 발송 상태 (PENDING/SENT/FAILED) */
|
||||
private String searchRequestStatus;
|
||||
|
||||
/* 메세지 코드 (MessageCode enum name) */
|
||||
private String searchMessageCode;
|
||||
|
||||
/* 메세지 유형 (EMAIL/SMS 등) */
|
||||
private String searchMessageType;
|
||||
|
||||
/* 수신자 이름 (MessageRequest.username 평문 컬럼 대상, LIKE 검색) */
|
||||
private String searchRecipient;
|
||||
|
||||
/* 요청일시 기간 검색 (email/phone 은 암호화 저장이라 검색 제외) */
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private LocalDate searchFromDate;
|
||||
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private LocalDate searchToDate;
|
||||
}
|
||||
+6
@@ -13,10 +13,16 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@Transactional(transactionManager = "transactionManagerForEMS")
|
||||
public class PortalNoticeService extends AbstractEMSDataSerivce<PortalNotice, String, PortalNoticeRepository> {
|
||||
|
||||
public void deleteByIds(List<String> ids) {
|
||||
repository.deleteAllById(ids);
|
||||
}
|
||||
|
||||
public Page<PortalNotice> findAll(Pageable pageable, PortalNoticeUISearch portalNoticeUISearch) {
|
||||
QPortalNotice qPortalNotice = QPortalNotice.portalNotice;
|
||||
|
||||
|
||||
+6
@@ -10,10 +10,16 @@ import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@Transactional(transactionManager = "transactionManagerForEMS")
|
||||
public class PortalPartnershipService extends AbstractEMSDataSerivce<PartnershipApplication, String, PortalPartnershipRepository> {
|
||||
|
||||
public void deleteByIds(List<String> ids) {
|
||||
repository.deleteAllById(ids);
|
||||
}
|
||||
|
||||
public Page<PartnershipApplication> findAll(Pageable pageable, String searchBizSubject, String searchBizDetail) {
|
||||
QPartnershipApplication qPartnershipApplication = QPartnershipApplication.partnershipApplication;
|
||||
|
||||
|
||||
+56
-2
@@ -8,12 +8,14 @@ import com.eactive.apim.portal.template.entity.QMessageRequest;
|
||||
import com.eactive.eai.rms.data.jpa.AbstractEMSDataSerivce;
|
||||
import com.querydsl.core.BooleanBuilder;
|
||||
import com.querydsl.core.types.dsl.BooleanExpression;
|
||||
import com.querydsl.jpa.JPAExpressions;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
@@ -36,7 +38,22 @@ public class PortalUserService extends AbstractEMSDataSerivce<PortalUser, String
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(portalUserUISearch.getSearchUserId())) {
|
||||
predicate.and(qPortalUser.loginId.containsIgnoreCase(portalUserUISearch.getSearchUserId()));
|
||||
// login_id / email_addr 는 PersonalDataEncryptConverter 로 암호화 저장되는 컬럼이다.
|
||||
// 암호화는 부분문자열을 보존하지 않으므로 LIKE/contains 검색은 절대 매칭되지 않는다.
|
||||
// 결정적(deterministic) 암호화라 정확일치(eq)만 가능하므로, login_id 와 email_addr 둘 다 정확일치로 OR 검색한다.
|
||||
String searchUserId = portalUserUISearch.getSearchUserId().trim();
|
||||
BooleanBuilder loginIdPredicate = new BooleanBuilder()
|
||||
.or(qPortalUser.loginId.eq(searchUserId))
|
||||
.or(qPortalUser.emailAddr.eq(searchUserId));
|
||||
// 이메일 형식 값(예: user+1@unit-test.com)은 form-urlencoded 디코딩 과정에서
|
||||
// '+' 가 공백으로 치환되어 전달될 수 있다(프록시/컨테이너 단계라 앱에서 인코딩만으로 막기 어려움).
|
||||
// login_id/email 에는 공백이 올 수 없으므로, 공백 포함 검색어는 '+' 치환 값도 함께 매칭한다.
|
||||
if (searchUserId.indexOf(' ') >= 0) {
|
||||
String plusRestored = searchUserId.replace(' ', '+');
|
||||
loginIdPredicate.or(qPortalUser.loginId.eq(plusRestored))
|
||||
.or(qPortalUser.emailAddr.eq(plusRestored));
|
||||
}
|
||||
predicate.and(loginIdPredicate);
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(portalUserUISearch.getSearchUserName())) {
|
||||
@@ -47,16 +64,49 @@ public class PortalUserService extends AbstractEMSDataSerivce<PortalUser, String
|
||||
predicate.and(qPortalUser.portalOrg.id.eq(portalUserUISearch.getSearchOrgId()));
|
||||
}
|
||||
if (StringUtils.isNotBlank(portalUserUISearch.getSearchMobileNumber())) {
|
||||
predicate.and(qPortalUser.mobileNumber.contains(portalUserUISearch.getSearchMobileNumber()));
|
||||
// mobile_number 도 암호화 저장 컬럼이라 부분문자열이 보존되지 않는다.
|
||||
// LIKE/contains 는 매칭되지 않으므로 정확일치(eq)로만 검색한다.
|
||||
predicate.and(qPortalUser.mobileNumber.eq(portalUserUISearch.getSearchMobileNumber().trim()));
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(portalUserUISearch.getSearchRoleCode())) {
|
||||
predicate.and(qPortalUser.roleCode.eq(PortalUserEnums.RoleCode.valueOf(portalUserUISearch.getSearchRoleCode())));
|
||||
}
|
||||
|
||||
// 휴대폰번호 중복(2명 이상 공유) 사용자만 조회
|
||||
if (portalUserUISearch.isOnlyDuplicateMobile()) {
|
||||
// 자바 리스트 .in() 은 컨버터가 IN 파라미터를 암호화하므로 레거시 평문 데이터와 매칭되지 않는다.
|
||||
// 대신 서브쿼리 .in() 으로 컬럼 대 컬럼(SQL 내부) 비교를 하여 평문/암호문 저장 방식과 무관하게 동작시킨다.
|
||||
QPortalUser dupUser = new QPortalUser("dupUser");
|
||||
predicate.and(qPortalUser.mobileNumber.in(
|
||||
JPAExpressions.select(dupUser.mobileNumber)
|
||||
.from(dupUser)
|
||||
.where(dupUser.mobileNumber.isNotNull()
|
||||
.and(dupUser.userStatus.ne(PortalUserEnums.UserStatus.REMOVED)))
|
||||
.groupBy(dupUser.mobileNumber)
|
||||
.having(dupUser.count().gt(1L))));
|
||||
}
|
||||
|
||||
return repository.findAll(predicate, pageable);
|
||||
}
|
||||
|
||||
/**
|
||||
* 휴대폰번호가 중복되는 사용자 총 인원수(중복 그룹에 속한 사용자 수의 합)를 반환한다.
|
||||
*/
|
||||
public long countDuplicateMobileUsers() {
|
||||
QPortalUser qPortalUser = QPortalUser.portalUser;
|
||||
// Oracle 빈 문자열=NULL 이슈로 isNotNull 만 사용 (findDuplicateMobileNumbers 와 동일 기준)
|
||||
List<Long> duplicateGroupCounts = getJPAQueryFactory()
|
||||
.select(qPortalUser.count())
|
||||
.from(qPortalUser)
|
||||
.where(qPortalUser.mobileNumber.isNotNull()
|
||||
.and(qPortalUser.userStatus.ne(PortalUserEnums.UserStatus.REMOVED)))
|
||||
.groupBy(qPortalUser.mobileNumber)
|
||||
.having(qPortalUser.count().gt(1L))
|
||||
.fetch();
|
||||
return duplicateGroupCounts.stream().mapToLong(Long::longValue).sum();
|
||||
}
|
||||
|
||||
public boolean existsByLoginId(String loginId) {
|
||||
BooleanExpression predicate = QPortalUser.portalUser.loginId.eq(loginId);
|
||||
return repository.exists(predicate);
|
||||
@@ -77,6 +127,10 @@ public class PortalUserService extends AbstractEMSDataSerivce<PortalUser, String
|
||||
}
|
||||
|
||||
public PortalUser findByUserId(String userId) {
|
||||
if (StringUtils.isBlank(userId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
QPortalUser qPortalUser = QPortalUser.portalUser;
|
||||
|
||||
return getJPAQueryFactory()
|
||||
|
||||
+3
@@ -23,4 +23,7 @@ public class PortalUserUISearch {
|
||||
|
||||
private boolean includeRemoved;
|
||||
|
||||
/** true 인 경우 휴대폰번호가 중복(2명 이상 공유)되는 사용자만 조회 */
|
||||
private boolean onlyDuplicateMobile;
|
||||
|
||||
}
|
||||
|
||||
@@ -26,9 +26,12 @@ public class WebhookSendLog {
|
||||
)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "TARGET_URL", nullable = false, length = 500)
|
||||
@Column(name = "TARGET_URL", length = 500)
|
||||
private String targetUrl;
|
||||
|
||||
@Column(name = "PROXY_URL", length = 500)
|
||||
private String proxyUrl;
|
||||
|
||||
@Column(name = "EVENT_TYPE", length = 100)
|
||||
private String eventType;
|
||||
|
||||
@@ -66,6 +69,9 @@ public class WebhookSendLog {
|
||||
@Column(name = "SENT_AT")
|
||||
private LocalDateTime sentAt;
|
||||
|
||||
@Column(name = "SEND_BY", length = 100)
|
||||
private String sendBy;
|
||||
|
||||
/* Boolean 편의 메서드 */
|
||||
public void setSuccess(Boolean success) {
|
||||
this.success = (success != null && success) ? "Y" : "N";
|
||||
|
||||
@@ -126,6 +126,15 @@ public class EAILogRepositoryImpl implements EAILogRepository {
|
||||
query.where(qeaiLog.eaisvcname.containsIgnoreCase(eaiLogSearch.getSearchEaiSvcName()));
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(eaiLogSearch.getSearchEaiSvcDesc())) {
|
||||
QEAIMessageEntity qeaiMessageEntity = QEAIMessageEntity.eAIMessageEntity;
|
||||
JPAQuery<String> subQuery = new JPAQueryFactory(em)
|
||||
.select(qeaiMessageEntity.eaisvcname)
|
||||
.from(qeaiMessageEntity)
|
||||
.where(qeaiMessageEntity.eaisvcdesc.containsIgnoreCase(eaiLogSearch.getSearchEaiSvcDesc()));
|
||||
query.where(qeaiLog.eaisvcname.in(subQuery));
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(eaiLogSearch.getSearchKeyMgtMsgCtnt())) {
|
||||
query.where(qeaiLog.keymgtmsgctnt.contains(eaiLogSearch.getSearchKeyMgtMsgCtnt()));
|
||||
}
|
||||
|
||||
@@ -39,11 +39,10 @@ public interface ApiStatusRepository extends BaseRepository<ApiStatus, String> {
|
||||
+ " FROM ("
|
||||
+ " SELECT A.EAISVCNAME"
|
||||
+ " , NVL(B.STATUS_CODE, 'N') AS STATUS_CODE"
|
||||
//TODO: 게시판 임시로 생성하여 사용. 유차장이 완료하면 변경할것
|
||||
+ " , NVL(( SELECT 'Y' FROM PTL_NOTICE_API X "
|
||||
+ " WHERE EXISTS (SELECT 1 FROM EMSAPP.PTL_NOTICE Y WHERE X.NOTICE_ID = Y.ID)"
|
||||
//+ " AND TO_CHAR(SYSDATE, 'YYYYMMDDHH24MI') BETWEEN START_TIME AND END_TIME) "
|
||||
+ " AND A.EAISVCNAME = X.API_NAME),'N') AS CTRL_YN "
|
||||
+ " , NVL(( SELECT MAX('Y') FROM EMSAPP.DJB_APISTATUS_INCIDENT_API X"
|
||||
+ " WHERE EXISTS (SELECT 1 FROM EMSAPP.DJB_APISTATUS_INCIDENT Y WHERE X.INCIDENT_ID = Y.INCIDENT_ID "
|
||||
+ " AND SYSTIMESTAMP BETWEEN STARTED_AT AND END_AT) "
|
||||
+ " AND A.EAISVCNAME = X.API_ID),'N') AS CTRL_YN "
|
||||
+ " , (SELECT CASE WHEN TOTAL = 0 THEN 'X'"
|
||||
+ " WHEN (TOTAL - SUCCESS) * 100 / TOTAL > :errorRate THEN 'Y'"
|
||||
+ " ELSE 'N' END"
|
||||
|
||||
@@ -26,10 +26,10 @@ public class ApiStatusService {
|
||||
|
||||
@Autowired
|
||||
private ApiStatusRepository apiStatusRepository;
|
||||
|
||||
|
||||
@Autowired
|
||||
private UmsManager ums;
|
||||
|
||||
private UmsManager ums;
|
||||
|
||||
@Autowired
|
||||
private ApiStatusDetectionService apiStatusDectionService;
|
||||
|
||||
@@ -55,7 +55,8 @@ public class ApiStatusService {
|
||||
apiStatus.setEaisvcname(event.getEaisvcname());
|
||||
apiStatus.setStatusCode(newStatusCode);
|
||||
|
||||
apiStatusRepository.save(apiStatus); // PK 있으면 UPDATE, 없으면 INSERT
|
||||
apiStatusRepository.saveAndFlush(apiStatus); // PK 있으면 UPDATE, 없으면 INSERT (즉시 flush)
|
||||
|
||||
log.debug("API 상태 변경: {}-{} {} → {}", event.getEaisvcname(), event.getEaisvcdesc(), event.getEvent(), newStatusCode);
|
||||
|
||||
|
||||
@@ -75,8 +76,8 @@ public class ApiStatusService {
|
||||
String event = entry.getKey();
|
||||
List<String> apiIds = entry.getValue();
|
||||
|
||||
String message = getSwingChatMessage(event, apiIds);
|
||||
ums.sendMessenger("api-monitor", MessageCode.API_STATUS_CHANGED, message);
|
||||
HashMap<String, Object> params = getSwingChatMessage(event, apiIds);
|
||||
ums.send("api-monitor", MessageCode.API_STATUS_CHANGED, params);
|
||||
|
||||
//제휴사 웹훅 발송
|
||||
ums.sendWebhook(event, apiIds);
|
||||
@@ -102,7 +103,7 @@ public class ApiStatusService {
|
||||
}
|
||||
|
||||
|
||||
private String getSwingChatMessage(String event, List<String> apiIds) {
|
||||
private HashMap<String, Object> getSwingChatMessage(String event, List<String> apiIds) {
|
||||
String message = "";
|
||||
switch (event) {
|
||||
case "CONTROL_START":
|
||||
@@ -125,9 +126,18 @@ public class ApiStatusService {
|
||||
break;
|
||||
default:
|
||||
message = "";
|
||||
}
|
||||
}
|
||||
|
||||
return message + "\n- " + String.join(",", apiIds);
|
||||
if (apiIds.size() > 1) {
|
||||
message += "\n" + apiIds.get(0) + "외 " + (apiIds.size()-1) + "종";
|
||||
} else {
|
||||
message += "\n- " + apiIds.get(0);
|
||||
}
|
||||
|
||||
HashMap<String, Object> params = new HashMap<>();
|
||||
params.put("message", message);
|
||||
|
||||
return params;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.eactive.eai.rms.ext.djb.inflow;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@@ -9,7 +10,6 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
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.onl.djb.inflow.InflowTokenInsufficient;
|
||||
import com.eactive.eai.rms.ext.djb.ums.UmsManager;
|
||||
|
||||
@@ -31,9 +31,10 @@ public class InflowTokenService {
|
||||
|
||||
for (InflowTokenInsufficient info : rows) {
|
||||
log.debug("유량제어 토큰 획득 실패: {}-{} 최근 {}분 동안 {}건", info.getEaisvcname(), info.getEaisvcdesc(), rangeMinute, info.getCnt());
|
||||
String message = "유량제어 토큰 획득 실패하였습니다 \n" + info.getEaisvcname();
|
||||
|
||||
ums.sendMessenger("api-monitor", MessageCode.INFLOW_TOKEN_FAILED, message);
|
||||
String message = "유량제어 토큰 획득 실패가 발생하였습니다 \n" + info.getEaisvcname();
|
||||
HashMap<String,Object> params = new HashMap<String,Object>();
|
||||
params.put("message", message);
|
||||
ums.send("api-monitor", MessageCode.INFLOW_TOKEN_FAILED, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package com.eactive.eai.rms.ext.djb.job;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.quartz.DisallowConcurrentExecution;
|
||||
@@ -68,10 +71,24 @@ public class ApiStatusMonitorJob implements Job {
|
||||
|
||||
@Override
|
||||
public void execute(JobExecutionContext context) throws JobExecutionException {
|
||||
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
|
||||
|
||||
// execute.instances 가 지정된 경우 해당 인스턴스에서만 실행
|
||||
String allowedInstances = jobDataMap.getString("execute.instances");
|
||||
if (StringUtils.isNotEmpty(allowedInstances)) {
|
||||
Set<String> instanceSet = Arrays.stream(allowedInstances.split(","))
|
||||
.map(s -> s.trim().toLowerCase())
|
||||
.collect(Collectors.toSet());
|
||||
String currentInstance = StringUtils.defaultString(System.getProperty("inst.Name")).trim().toLowerCase();
|
||||
if (!instanceSet.contains(currentInstance)) {
|
||||
log.debug("Job 실행 인스턴스 제한 - 현재: {}, 허용: {}", currentInstance, allowedInstances);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
log.info("*** START ApiStatusUpdateJob run({})", CommonUtil.getToday("yyyy-MM-dd HH:mm"));
|
||||
|
||||
// Job 파라미터 로깅
|
||||
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
|
||||
logJobParameters(jobDataMap);
|
||||
|
||||
HashMap<String, String> param = this.checkParameters(jobDataMap);
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package com.eactive.eai.rms.ext.djb.job;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.quartz.DisallowConcurrentExecution;
|
||||
import org.quartz.Job;
|
||||
import org.quartz.JobDataMap;
|
||||
@@ -48,6 +52,21 @@ public class InflowTokenMonitorJob implements Job {
|
||||
|
||||
@Override
|
||||
public void execute(JobExecutionContext context) throws JobExecutionException {
|
||||
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
|
||||
|
||||
// execute.instances 가 지정된 경우 해당 인스턴스에서만 실행
|
||||
String allowedInstances = jobDataMap.getString("execute.instances");
|
||||
if (StringUtils.isNotEmpty(allowedInstances)) {
|
||||
Set<String> instanceSet = Arrays.stream(allowedInstances.split(","))
|
||||
.map(s -> s.trim().toLowerCase())
|
||||
.collect(Collectors.toSet());
|
||||
String currentInstance = StringUtils.defaultString(System.getProperty("inst.Name")).trim().toLowerCase();
|
||||
if (!instanceSet.contains(currentInstance)) {
|
||||
log.debug("Job 실행 인스턴스 제한 - 현재: {}, 허용: {}", currentInstance, allowedInstances);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
log.info("*** START InflowTokenFailMonitorJob run({})", CommonUtil.getToday("yyyy-MM-dd HH:mm"));
|
||||
|
||||
long execMinute = 1; //default 배치 실행주기(분)
|
||||
|
||||
@@ -5,13 +5,20 @@ import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
|
||||
import com.eactive.eai.rms.common.util.CommonUtil;
|
||||
import com.eactive.eai.rms.onl.apim.portalInquiry.PortalInquiryClosingService;
|
||||
import com.eactive.eai.rms.onl.common.util.DateUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.quartz.DisallowConcurrentExecution;
|
||||
import org.quartz.Job;
|
||||
import org.quartz.JobDataMap;
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.quartz.JobExecutionException;
|
||||
import org.quartz.SchedulerException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 매일 00:05 실행 - RESPONDED 상태 문의글 자동 종료 배치
|
||||
@@ -39,13 +46,34 @@ public class PortalInquiryClosingJob implements Job {
|
||||
|
||||
private static final int DEFAULT_CLOSING_DAYS = 7;
|
||||
|
||||
@Autowired
|
||||
private PortalInquiryClosingService inquiryCommentClosingService;
|
||||
|
||||
@Override
|
||||
public void execute(JobExecutionContext context) throws JobExecutionException {
|
||||
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
|
||||
|
||||
// execute.instances 가 지정된 경우 해당 인스턴스에서만 실행
|
||||
String allowedInstances = jobDataMap.getString("execute.instances");
|
||||
if (StringUtils.isNotEmpty(allowedInstances)) {
|
||||
Set<String> instanceSet = Arrays.stream(allowedInstances.split(","))
|
||||
.map(s -> s.trim().toLowerCase())
|
||||
.collect(Collectors.toSet());
|
||||
String currentInstance = StringUtils.defaultString(System.getProperty("inst.Name")).trim().toLowerCase();
|
||||
if (!instanceSet.contains(currentInstance)) {
|
||||
log.debug("Job 실행 인스턴스 제한 - 현재: {}, 허용: {}", currentInstance, allowedInstances);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
log.info("*** START PortalInquiryCommentClosingService run({})", CommonUtil.getToday("yyyy-MM-dd HH:mm"));
|
||||
|
||||
PortalInquiryClosingService service;
|
||||
try {
|
||||
ApplicationContext appContext = (ApplicationContext) context.getScheduler().getContext().get("applicationContext");
|
||||
service = appContext.getBean(PortalInquiryClosingService.class);
|
||||
} catch (SchedulerException e) {
|
||||
log.error("applicationContext 조회 실패", e);
|
||||
throw new JobExecutionException(e);
|
||||
}
|
||||
|
||||
int closingDays = DEFAULT_CLOSING_DAYS;
|
||||
String closingDayParam = context.getMergedJobDataMap().getString(KEY_INQUIRY_COMMENT_CLOSING_DAY);
|
||||
if (closingDayParam != null) {
|
||||
@@ -59,7 +87,7 @@ public class PortalInquiryClosingJob implements Job {
|
||||
DataSourceContextHolder.setDataSourceType(
|
||||
DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.APIGW));
|
||||
try {
|
||||
int closedCount = inquiryCommentClosingService.closeExpiredInquiries(closingDays);
|
||||
int closedCount = service.closeExpiredInquiries(closingDays);
|
||||
log.info("PortalInquiryCommentClosingService 완료: {}건 CLOSED 처리", closedCount);
|
||||
} catch (Exception e) {
|
||||
log.error("PortalInquiryCommentClosingService execution failed", e);
|
||||
|
||||
@@ -85,7 +85,7 @@ public class ApiUseStatsService
|
||||
+ " ORDER BY ORGNAME";
|
||||
} else if ("API".equals(search.getSearchType())) {
|
||||
dataSql = "SELECT COUNT(*) OVER () AS RECORDS "
|
||||
+ ", C.EAISVCDESC"
|
||||
+ ", A.API_NAME"
|
||||
+ ", SUM(A.TOTAL_CNT)"
|
||||
+ ", SUM(A.SUCCESS_CNT)"
|
||||
+ ", CASE WHEN SUM(A.TOTAL_CNT) > 0 THEN SUM(A.SUCCESS_CNT) * 100 / SUM(A.TOTAL_CNT) ELSE NULL END"
|
||||
@@ -99,8 +99,8 @@ public class ApiUseStatsService
|
||||
+ " LEFT OUTER JOIN TSEAIAU01 B ON A.CLIENT_ID = B.CLIENTID"
|
||||
+ " LEFT OUTER JOIN TSEAIHE01 C ON A.API_NAME = C.EAISVCNAME "
|
||||
+ where
|
||||
+ " GROUP BY C.EAISVCDESC"
|
||||
+ " ORDER BY EAISVCDESC";
|
||||
+ " GROUP BY A.API_NAME"
|
||||
+ " ORDER BY API_NAME";
|
||||
} else if ("DATE".equals(search.getSearchType())) {
|
||||
dataSql = "SELECT COUNT(*) OVER () AS RECORDS "
|
||||
+ ", TO_CHAR(STAT_TIME,'YYYY-MM-DD') AS STAT_TIME "
|
||||
|
||||
@@ -29,14 +29,13 @@ public class UmsManager {
|
||||
private final WebhookService webhookService;
|
||||
|
||||
/**
|
||||
* role 역할을 가진 내부직원에게 메신저(Swing chat) 발송
|
||||
* roleId에 해당하는 역할을 가진 직원에게 UMS(sms,email,messenger) 발송
|
||||
* @param role
|
||||
* @param messageCode
|
||||
* @param message
|
||||
* @param params
|
||||
* @return
|
||||
*/
|
||||
public void sendMessenger(String roleId, MessageCode messageCode, String message) {
|
||||
|
||||
public void send(String roleId, MessageCode messageCode, HashMap<String, Object> params) {
|
||||
List<UserInfo> users = userInfoService.findAllByRoleId(roleId);
|
||||
if (users.isEmpty()) {
|
||||
log.info("sendMessenger: no users found for role '{}'", roleId);
|
||||
@@ -44,36 +43,36 @@ public class UmsManager {
|
||||
}
|
||||
|
||||
for (UserInfo user : users) {
|
||||
String cleanedPhone = user.getCphnno().replaceAll("[^0-9]", "").trim();
|
||||
MessageRecipient recipient = MessageRecipient.builder()
|
||||
.userId(user.getUserid())
|
||||
.username(user.getUsername())
|
||||
.email(user.getEmad())
|
||||
.phone(cleanedPhone)
|
||||
.messengerId(user.getUserid())
|
||||
.build();
|
||||
|
||||
HashMap<String, Object> params = new HashMap<>();
|
||||
params.put("message", message);
|
||||
|
||||
messageHandlerService.publishEvent(messageCode, recipient, params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 내부직원에게 메신저 발송
|
||||
* 직원 또는 회원에게 UMS(sms,email,messenger) 발송
|
||||
* @param user
|
||||
* @param messageCode
|
||||
* @param message
|
||||
* @param params
|
||||
*/
|
||||
public void sendMessenger(UserInfo user, MessageCode messageCode, String message) {
|
||||
public void send(UserInfo user, MessageCode messageCode, HashMap<String, Object> params) {
|
||||
String cleanedPhone = user.getCphnno().replaceAll("[^0-9]", "").trim();
|
||||
MessageRecipient recipient = MessageRecipient.builder()
|
||||
.userId(user.getUserid())
|
||||
.username(user.getUsername())
|
||||
.userId(user.getUserid())
|
||||
.username(user.getUsername())
|
||||
.email(user.getEmad())
|
||||
.phone(cleanedPhone)
|
||||
.messengerId(user.getUserid())
|
||||
.build();
|
||||
|
||||
HashMap<String, Object> params = new HashMap<>();
|
||||
params.put("message", message);
|
||||
|
||||
messageHandlerService.publishEvent(messageCode, recipient, params);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* event를 구독중인 제휴사에게 웹훅 발송 (비동기)
|
||||
* @param event
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
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 AdminVerificationMobilePhoneEventHandler implements MessageEventHandler {
|
||||
|
||||
@Override
|
||||
public MessageCode getKey() {
|
||||
return MessageCode.ADMIN_VERIFICATION_MOBILEPHONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowAdditionalRecipients() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "관리자포탈 휴대폰 인증번호 발송";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "<div>사용 가능 변수: </div><br/>"
|
||||
+ "<div> - userName 수신자 이름 </div><br/>"
|
||||
+ "<div> - authNumber 인증번호 </div><br/>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageSendEvent createEvent(Object source, MessageRecipient recipient, Map<String, Object> params) {
|
||||
Map<String, String> eventParams = new HashMap<>();
|
||||
String authNumber = (String) params.get("authNumber");
|
||||
eventParams.put("authNumber", authNumber);
|
||||
eventParams.put("AUTH_CODE", authNumber);
|
||||
eventParams.put("userName", (String) params.get("userName"));
|
||||
return new MessageSendEvent(source, MessageCode.ADMIN_VERIFICATION_MOBILEPHONE, recipient, eventParams);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import java.net.URL;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.SecureRandom;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@@ -15,10 +16,13 @@ import java.util.Map;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.eactive.apim.portal.common.util.PhoneNumberUtil;
|
||||
import com.eactive.apim.portal.portalproperty.service.PortalPropertyService;
|
||||
import com.eactive.apim.portal.template.entity.MessageRequest;
|
||||
import com.eactive.eai.common.util.StringUtil;
|
||||
import com.eactive.eai.common.util.SystemUtil;
|
||||
import com.eactive.eai.rms.data.entity.man.monitoringProperty.service.MonitoringPropertyService;
|
||||
import com.eactive.eai.rms.data.entity.onl.apim.messagerequest.MessageRequestService;
|
||||
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;
|
||||
@@ -35,11 +39,18 @@ public class UmsService {
|
||||
|
||||
private static final Gson gson = new Gson();
|
||||
|
||||
private static final String SYS_DVCD = "OPA";
|
||||
private static final String SYS_DVCD = "API"; //시스템코드
|
||||
private static final String BZWK_DVCD = "99"; //업무구분코드
|
||||
private static final String SUB_BZWK_CD = "9019"; //세부구분코드
|
||||
private static final String TEAM_DVCD = "00"; //팀구분코드 TODO: 변경해야함
|
||||
|
||||
|
||||
private final MonitoringPropertyService propertyService;
|
||||
private final MessageRequestService messageRequestService;
|
||||
|
||||
public UmsService(MonitoringPropertyService propertyService) {
|
||||
public UmsService(MonitoringPropertyService propertyService, MessageRequestService messageRequestService) {
|
||||
this.propertyService = propertyService;
|
||||
this.messageRequestService = messageRequestService;
|
||||
}
|
||||
|
||||
public boolean send(MessageRequest messageRequest) throws Exception {
|
||||
@@ -145,12 +156,14 @@ public class UmsService {
|
||||
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.setChnl_tycd("EAI"); // 채널유형코드
|
||||
stdHeadVO.setHmab_dvcd("1"); // 내외구분코드 1:내부 2:외부(타발)
|
||||
stdHeadVO.setIf_id(messageRequest.getEaiInterfaceId()); // 인터페이스ID
|
||||
stdHeadVO.setTx_id(messageRequest.getServiceId()); // 거래ID
|
||||
|
||||
if ("MESSENGER".equals(messageRequest.getMessageType()) ) {
|
||||
|
||||
stdHeadVO.setChnl_tycd("FEP"); // 채널유형코드
|
||||
|
||||
// 스윙챗 알림 공용
|
||||
SwingCommonVO swiComVO = new SwingCommonVO();
|
||||
@@ -161,14 +174,16 @@ public class UmsService {
|
||||
|
||||
// 스윙챗 알림 데이터
|
||||
SwingDataVO swiDataVO = new SwingDataVO();
|
||||
swiDataVO.setNotiCode("CNO_FEP_HMP_0002");
|
||||
swiDataVO.setNotiCode("CNO_FEP_APIM_0002"); //0001:긴급, 0002:중요
|
||||
swiDataVO.setNotiTitle("알림");
|
||||
swiDataVO.setNotiContent(messageRequest.getMessage());
|
||||
|
||||
// 스윙챗 알림 데이터
|
||||
List<SwingReceiverDataVO> swiReDataVOList = new ArrayList<SwingReceiverDataVO>();
|
||||
SwingReceiverDataVO receiver = new SwingReceiverDataVO();
|
||||
receiver.setCode(messageRequest.getMessengerId());
|
||||
receiver.setType("PSN");
|
||||
receiver.setCompanyCode("CB");
|
||||
receiver.setCode(messageRequest.getMessengerId());
|
||||
swiReDataVOList.add(receiver);
|
||||
|
||||
swiDataVO.setReceiverInfo(swiReDataVOList);
|
||||
@@ -190,27 +205,27 @@ public class UmsService {
|
||||
// Sms 데이터
|
||||
SmsDataVO dataVO = new SmsDataVO();
|
||||
dataVO.setSnd_dman_dt(jsonData[0]);
|
||||
dataVO.setSnd_dman_id("");
|
||||
dataVO.setSnd_dman_id(this.getSndDmanId(messageRequest.getMessageType()));
|
||||
dataVO.setSnd_dvcd("EAI");
|
||||
dataVO.setMsg_titl("");
|
||||
dataVO.setMsg_titl(StringUtil.chunkString(messageRequest.getSubject(), 40, false)); // MS949 40byte 초과시 절단
|
||||
dataVO.setMsg_ctnt(messageRequest.getMessage());
|
||||
dataVO.setRecvr_no(messageRequest.getPhone());
|
||||
dataVO.setRecvr_no(PhoneNumberUtil.digitsOnly(messageRequest.getPhone()));
|
||||
dataVO.setSndn_no("15880079"); //발신번호
|
||||
dataVO.setSnd_empno(""); //요청자
|
||||
dataVO.setSnd_emp_dprm_cd(""); //요청부서코드
|
||||
dataVO.setSnd_empno("APIM"); //요청자
|
||||
dataVO.setSnd_emp_dprm_cd("132"); //요청부서코드
|
||||
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_snd_prfl_key(propertyService.getPropertyValue("Monitoring", "djb.ums.sms.almtk_snd_prtl_key", "")); // 플러스친구아이디
|
||||
dataVO.setAlmtk_tmplt_cd(BZWK_DVCD + TEAM_DVCD + SUB_BZWK_CD + "001"); // 템플릿코드
|
||||
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_trnm_bzwk_dvcd(BZWK_DVCD); //업무구분코드
|
||||
dataVO.setSms_bzwk_dcls_dvcd(SUB_BZWK_CD); //업무세분코드
|
||||
dataVO.setSms_sys_dvcd("83"); //시스템코드
|
||||
dataVO.setTx_dt(jsonData[0]); //거래일자
|
||||
dataVO.setTx_tktm(jsonData[1].substring(0,6)); //거래시간
|
||||
@@ -246,10 +261,10 @@ public class UmsService {
|
||||
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.setSnd_empno("APIM"); // 발신자ID(필수)
|
||||
dataVO.setSnd_emp_dprm_cd("132"); // 발신자부서코드(필수)
|
||||
dataVO.setBzwk_cd(BZWK_DVCD); // 업무구분코드
|
||||
dataVO.setSub_bzwk_cd(SUB_BZWK_CD); // 업무세부코드
|
||||
dataVO.setSys_dvcd(SYS_DVCD);
|
||||
dataVO.setTx_dt(jsonData[0]); //거래일자
|
||||
dataVO.setTx_tktm(jsonData[1].substring(0,6)); //거래시간
|
||||
@@ -277,58 +292,41 @@ public class UmsService {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -341,4 +339,24 @@ public class UmsService {
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
private String getSndDmanId(String messageType) {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
|
||||
String yyyymmdd = sdf.format(new Date());
|
||||
|
||||
String uuid = SYS_DVCD + BZWK_DVCD + SUB_BZWK_CD + yyyymmdd + "O" ;
|
||||
|
||||
if ("SMS".equals(messageType)) {
|
||||
uuid += "S";
|
||||
} else if ("LMS".equals(messageType)) {
|
||||
uuid += "L";
|
||||
} else if ("KAKAO".equals(messageType)) {
|
||||
uuid += "K";
|
||||
}
|
||||
|
||||
long seq = messageRequestService.countSmsKakaoRequestsOnDate(LocalDate.now()) + 1;
|
||||
uuid += String.format("%014d", seq);
|
||||
|
||||
return uuid;
|
||||
}
|
||||
}
|
||||
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package com.eactive.eai.rms.ext.djb.webhook.controller;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.SortDefault;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
|
||||
import com.eactive.eai.rms.common.base.BaseAnnotationController;
|
||||
import com.eactive.eai.rms.common.combo.ComboService;
|
||||
import com.eactive.eai.rms.common.vo.GridResponse;
|
||||
import com.eactive.eai.rms.ext.djb.webhook.dto.WebhookSendLogUI;
|
||||
import com.eactive.eai.rms.ext.djb.webhook.dto.WebhookSendLogUISearch;
|
||||
import com.eactive.eai.rms.ext.djb.webhook.service.WebhookSendLogManService;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* 웹훅 발송 로그(PTL_WEBHOOK_SEND_LOG) 조회 화면 컨트롤러.
|
||||
*
|
||||
* <p>조회 전용 (등록/수정/삭제 없음)</p>
|
||||
*/
|
||||
@Controller
|
||||
@RequiredArgsConstructor
|
||||
public class WebhookSendLogManController extends BaseAnnotationController {
|
||||
|
||||
private final WebhookSendLogManService webhookSendLogManService;
|
||||
private final ComboService comboService;
|
||||
|
||||
@GetMapping(value = "/onl/apim/webhook/webhookSendLogMan.view")
|
||||
public void view() {
|
||||
// 목록 view
|
||||
}
|
||||
|
||||
@GetMapping(value = "/onl/apim/webhook/webhookSendLogMan.view", params = "cmd=DETAIL")
|
||||
public String detailView() {
|
||||
return "/onl/apim/webhook/webhookSendLogManDetail";
|
||||
}
|
||||
|
||||
@PostMapping(value = "/onl/apim/webhook/webhookSendLogMan.json", params = "cmd=LIST")
|
||||
public ResponseEntity<GridResponse<WebhookSendLogUI>> selectList(
|
||||
@SortDefault(sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable,
|
||||
WebhookSendLogUISearch search) {
|
||||
Page<WebhookSendLogUI> page = webhookSendLogManService.selectList(pageable, search);
|
||||
return ResponseEntity.ok(new GridResponse<>(page));
|
||||
}
|
||||
|
||||
@PostMapping(value = "/onl/apim/webhook/webhookSendLogMan.json", params = "cmd=LIST_INIT_COMBO")
|
||||
public ResponseEntity<Map<String, Object>> initCombo() {
|
||||
Map<String, Object> resultMap = new HashMap<>();
|
||||
resultMap.put("eventTypeList", comboService.getMonitoringCodeSortedBySeq(WebhookSendLogManService.CODE_GROUP_EVENT_TYPE));
|
||||
return ResponseEntity.ok(resultMap);
|
||||
}
|
||||
|
||||
@PostMapping(value = "/onl/apim/webhook/webhookSendLogMan.json", params = "cmd=DETAIL")
|
||||
public ResponseEntity<WebhookSendLogUI> selectDetail(Long id) {
|
||||
return ResponseEntity.ok(webhookSendLogManService.selectDetail(id));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.eactive.eai.rms.ext.djb.webhook.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 웹훅 발송 로그(PTL_WEBHOOK_SEND_LOG) 화면 응답 DTO.
|
||||
*
|
||||
* <p>목록에서는 일부 항목만 사용하고, 상세에서는 전체 항목을 사용한다.
|
||||
* 날짜는 yyyyMMdd HH:mm:ss 형식 문자열로 제공한다.</p>
|
||||
*/
|
||||
@Data
|
||||
public class WebhookSendLogUI {
|
||||
|
||||
private Long id;
|
||||
|
||||
private String orgId;
|
||||
private String orgName;
|
||||
|
||||
private String eventType;
|
||||
/** 모니터링 공통코드(EVENT_TYPE) 코드명. 상세 조회에서만 채워진다(목록은 화면에서 콤보로 변환). */
|
||||
private String eventTypeName;
|
||||
private String targetUrl;
|
||||
private String proxyUrl;
|
||||
|
||||
private String payload;
|
||||
private String signature;
|
||||
|
||||
private Integer statusCode;
|
||||
private String responseBody;
|
||||
|
||||
private Boolean success;
|
||||
private String sendBy;
|
||||
private String errorMessage;
|
||||
private Integer retryCount;
|
||||
|
||||
/* 등록/발송 시간 — yyyyMMdd HH:mm:ss */
|
||||
private String createdAt;
|
||||
private String sentAt;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.eactive.eai.rms.ext.djb.webhook.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class WebhookSendLogUISearch {
|
||||
|
||||
/* 기관명 (ptl_org.org_name, LIKE 검색) */
|
||||
private String searchOrgName;
|
||||
|
||||
/* 이벤트 유형 (모니터링 공통코드 EVENT_TYPE, 콤보 선택 값과 정확히 일치) */
|
||||
private String searchEventType;
|
||||
|
||||
/* 수신 URL (LIKE 검색) */
|
||||
private String searchTargetUrl;
|
||||
|
||||
/* 성공여부 (Y/N, 공백=전체) */
|
||||
private String searchSuccess;
|
||||
|
||||
/* 발송일시 기간 검색 (yyyyMMdd, 8자리 — userLoginHistoryMan.jsp 기간검색 스타일) */
|
||||
private String searchStartDate;
|
||||
private String searchEndDate;
|
||||
}
|
||||
@@ -8,7 +8,9 @@ import lombok.Setter;
|
||||
public class WebhookSendRequest {
|
||||
private String orgId;
|
||||
private String targetUrl;
|
||||
private String proxyUrl;
|
||||
private String eventType;
|
||||
private String secret;
|
||||
private String reverseProxyPath;
|
||||
private Object data;
|
||||
}
|
||||
+2
-2
@@ -4,17 +4,17 @@ package com.eactive.eai.rms.ext.djb.webhook.repository;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import com.eactive.eai.data.jpa.BaseRepository;
|
||||
import com.eactive.eai.rms.data.EMSDataSource;
|
||||
import com.eactive.eai.rms.data.entity.onl.djb.webhook.WebhookSendLog;
|
||||
|
||||
@Repository
|
||||
@EMSDataSource
|
||||
public interface WebhookSendLogRepository extends JpaRepository<WebhookSendLog, Long> {
|
||||
public interface WebhookSendLogRepository extends BaseRepository<WebhookSendLog, Long> {
|
||||
|
||||
// 오라클 CHAR(1) Y/N 기준 조회
|
||||
@Query("SELECT l FROM WebhookSendLog l " +
|
||||
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
package com.eactive.eai.rms.ext.djb.webhook.service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.eactive.apim.portal.monitoringcode.entity.MonitoringCode;
|
||||
import com.eactive.apim.portal.monitoringcode.entity.MonitoringCodeId;
|
||||
import com.eactive.apim.portal.portalorg.entity.QPortalOrg;
|
||||
import com.eactive.eai.rms.common.base.BaseService;
|
||||
import com.eactive.eai.rms.common.util.StringUtils;
|
||||
import com.eactive.eai.rms.data.entity.man.monitoringCode.repository.MonitoringCodeRepository;
|
||||
import com.eactive.eai.rms.data.entity.onl.djb.webhook.QWebhookSendLog;
|
||||
import com.eactive.eai.rms.data.entity.onl.djb.webhook.WebhookSendLog;
|
||||
import com.eactive.eai.rms.ext.djb.webhook.dto.WebhookSendLogUI;
|
||||
import com.eactive.eai.rms.ext.djb.webhook.dto.WebhookSendLogUISearch;
|
||||
import com.eactive.eai.rms.ext.djb.webhook.repository.WebhookSendLogRepository;
|
||||
import com.eactive.eai.rms.onl.common.exception.BizException;
|
||||
import com.querydsl.core.BooleanBuilder;
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* 웹훅 발송 로그(PTL_WEBHOOK_SEND_LOG) 조회 화면 서비스.
|
||||
*
|
||||
* <p>조회 전용. orgId 는 엔티티 연관관계가 아닌 단순 컬럼이므로,
|
||||
* 기관명은 QueryDSL 로 ptl_org 를 별도 조회하여 매핑한다.</p>
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(transactionManager = "transactionManagerForEMS")
|
||||
public class WebhookSendLogManService extends BaseService {
|
||||
|
||||
/** 모니터링 공통코드(tseairm28) 상의 웹훅 이벤트유형 코드 그룹. */
|
||||
public static final String CODE_GROUP_EVENT_TYPE = "EVENT_TYPE";
|
||||
|
||||
private static final DateTimeFormatter FMT_DATETIME = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
private static final DateTimeFormatter FMT_YYYYMMDD = DateTimeFormatter.ofPattern("yyyyMMdd");
|
||||
|
||||
private final WebhookSendLogRepository webhookSendLogRepository;
|
||||
private final MonitoringCodeRepository monitoringCodeRepository;
|
||||
|
||||
@PersistenceContext(unitName = "entityManagerFactoryForEMS")
|
||||
private EntityManager entityManager;
|
||||
|
||||
public Page<WebhookSendLogUI> selectList(Pageable pageable, WebhookSendLogUISearch search) {
|
||||
QWebhookSendLog log = QWebhookSendLog.webhookSendLog;
|
||||
BooleanBuilder predicate = new BooleanBuilder();
|
||||
|
||||
if (StringUtils.isNotBlank(search.getSearchOrgName())) {
|
||||
List<String> orgIds = findOrgIdsByName(search.getSearchOrgName());
|
||||
if (orgIds.isEmpty()) {
|
||||
// 일치하는 기관이 없으면 쿼리를 실행할 필요 없이 빈 결과를 바로 반환한다.
|
||||
// (QueryDSL의 where false 는 이 프로젝트의 Hibernate HQL 파서에서 지원하지 않음)
|
||||
return Page.empty(pageable);
|
||||
}
|
||||
predicate.and(log.orgId.in(orgIds));
|
||||
}
|
||||
if (StringUtils.isNotBlank(search.getSearchEventType())) {
|
||||
predicate.and(log.eventType.eq(search.getSearchEventType()));
|
||||
}
|
||||
if (StringUtils.isNotBlank(search.getSearchTargetUrl())) {
|
||||
predicate.and(log.targetUrl.containsIgnoreCase(search.getSearchTargetUrl()));
|
||||
}
|
||||
if (StringUtils.isNotBlank(search.getSearchSuccess())) {
|
||||
predicate.and(log.success.eq(search.getSearchSuccess()));
|
||||
}
|
||||
if (StringUtils.isNotBlank(search.getSearchStartDate())) {
|
||||
predicate.and(log.sentAt.goe(parseYYYYMMDD(search.getSearchStartDate()).atStartOfDay()));
|
||||
}
|
||||
if (StringUtils.isNotBlank(search.getSearchEndDate())) {
|
||||
predicate.and(log.sentAt.loe(parseYYYYMMDD(search.getSearchEndDate()).atTime(23, 59, 59)));
|
||||
}
|
||||
|
||||
Page<WebhookSendLog> page = webhookSendLogRepository.findAll(predicate, pageable);
|
||||
|
||||
Map<String, String> orgNameMap = findOrgNames(page.getContent());
|
||||
// 목록의 이벤트유형 코드명은 화면에서 콤보(LIST_INIT_COMBO) 기준으로 변환하므로 서버에서는 조회하지 않는다(N+1 방지).
|
||||
return page.map(e -> convertToUI(e, orgNameMap.get(e.getOrgId()), null));
|
||||
}
|
||||
|
||||
public WebhookSendLogUI selectDetail(Long id) {
|
||||
WebhookSendLog entity = webhookSendLogRepository.findById(id)
|
||||
.orElseThrow(() -> new BizException("존재하지 않는 웹훅 발송 로그입니다."));
|
||||
|
||||
Map<String, String> orgNameMap = findOrgNames(Collections.singletonList(entity));
|
||||
String eventTypeName = resolveEventTypeName(entity.getEventType());
|
||||
return convertToUI(entity, orgNameMap.get(entity.getOrgId()), eventTypeName);
|
||||
}
|
||||
|
||||
private static LocalDate parseYYYYMMDD(String yyyyMMdd) {
|
||||
return LocalDate.parse(yyyyMMdd, FMT_YYYYMMDD);
|
||||
}
|
||||
|
||||
/** 모니터링 공통코드(EVENT_TYPE)에서 코드명을 조회한다. 코드가 없으면 원본 코드를 그대로 반환한다. */
|
||||
private String resolveEventTypeName(String eventType) {
|
||||
if (StringUtils.isBlank(eventType)) {
|
||||
return eventType;
|
||||
}
|
||||
return monitoringCodeRepository.findById(new MonitoringCodeId(CODE_GROUP_EVENT_TYPE, eventType))
|
||||
.map(MonitoringCode::getCodeName)
|
||||
.orElse(eventType);
|
||||
}
|
||||
|
||||
/** 기관명 LIKE 검색으로 매칭되는 orgId 목록. */
|
||||
private List<String> findOrgIdsByName(String orgName) {
|
||||
QPortalOrg org = QPortalOrg.portalOrg;
|
||||
return new JPAQueryFactory(entityManager)
|
||||
.select(org.id)
|
||||
.from(org)
|
||||
.where(org.orgName.containsIgnoreCase(orgName))
|
||||
.fetch();
|
||||
}
|
||||
|
||||
/** 목록/상세에 필요한 orgId → orgName 매핑 (일괄 조회로 N+1 방지). */
|
||||
private Map<String, String> findOrgNames(List<WebhookSendLog> logs) {
|
||||
Set<String> orgIds = logs.stream()
|
||||
.map(WebhookSendLog::getOrgId)
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.collect(Collectors.toSet());
|
||||
if (orgIds.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
QPortalOrg org = QPortalOrg.portalOrg;
|
||||
return new JPAQueryFactory(entityManager)
|
||||
.select(org.id, org.orgName)
|
||||
.from(org)
|
||||
.where(org.id.in(orgIds))
|
||||
.fetch()
|
||||
.stream()
|
||||
.collect(Collectors.toMap(t -> t.get(org.id), t -> t.get(org.orgName)));
|
||||
}
|
||||
|
||||
private WebhookSendLogUI convertToUI(WebhookSendLog e, String orgName, String eventTypeName) {
|
||||
WebhookSendLogUI ui = new WebhookSendLogUI();
|
||||
ui.setId(e.getId());
|
||||
ui.setOrgId(e.getOrgId());
|
||||
ui.setOrgName(orgName);
|
||||
ui.setEventType(e.getEventType());
|
||||
ui.setEventTypeName(eventTypeName);
|
||||
ui.setTargetUrl(e.getTargetUrl());
|
||||
ui.setProxyUrl(e.getProxyUrl());
|
||||
ui.setPayload(e.getPayload());
|
||||
ui.setSignature(e.getSignature());
|
||||
ui.setStatusCode(e.getStatusCode());
|
||||
ui.setResponseBody(e.getResponseBody());
|
||||
ui.setSuccess(e.getSuccess());
|
||||
ui.setSendBy(e.getSendBy());
|
||||
ui.setErrorMessage(e.getErrorMessage());
|
||||
ui.setRetryCount(e.getRetryCount());
|
||||
ui.setCreatedAt(fmt(e.getCreatedAt(), FMT_DATETIME));
|
||||
ui.setSentAt(fmt(e.getSentAt(), FMT_DATETIME));
|
||||
return ui;
|
||||
}
|
||||
|
||||
private static String fmt(LocalDateTime t, DateTimeFormatter f) {
|
||||
return t == null ? "" : t.format(f);
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,9 @@ import org.springframework.web.client.HttpClientErrorException;
|
||||
import org.springframework.web.client.HttpServerErrorException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import com.eactive.apim.portal.portalorg.entity.QPortalOrg;
|
||||
import com.eactive.eai.rms.common.context.MonitoringContext;
|
||||
import com.eactive.eai.rms.common.util.ContainerUtil;
|
||||
import com.eactive.eai.rms.data.entity.onl.djb.webhook.QWebhookReq;
|
||||
import com.eactive.eai.rms.data.entity.onl.djb.webhook.QWebhookReqApi;
|
||||
import com.eactive.eai.rms.data.entity.onl.djb.webhook.QWebhookReqEvent;
|
||||
@@ -60,17 +62,21 @@ public class WebhookService {
|
||||
QWebhookReq req = QWebhookReq.webhookReq;
|
||||
QWebhookReqEvent evt = QWebhookReqEvent.webhookReqEvent;
|
||||
QWebhookReqApi api = QWebhookReqApi.webhookReqApi;
|
||||
QPortalOrg org = QPortalOrg.portalOrg;
|
||||
|
||||
String reverseProxyUrl = monitoringContext.getProperty(MonitoringContext.RMS_WEBHOOK_REVERSE_PROXY_URL);
|
||||
|
||||
List<Tuple> tuples = new JPAQueryFactory(entityManager)
|
||||
.select(req.orgId, req.targetUrl, req.secret, api.id.apiId)
|
||||
.select(req.orgId, req.targetUrl, req.secret, org.reverseProxyPath, api.id.apiId)
|
||||
.from(req)
|
||||
.join(evt).on(req.id.eq(evt.id.webhookReqId))
|
||||
.join(api).on(req.id.eq(api.id.webhookReqId))
|
||||
.join(org).on(req.orgId.eq(org.id))
|
||||
.where(evt.id.eventType.eq(event)
|
||||
.and(api.id.apiId.in(apiIds)))
|
||||
.fetch();
|
||||
|
||||
// orgId + targetUrl + secret 기준으로 그룹핑, apiId를 data(List)로 집계
|
||||
// orgId + targetUrl + secret 기준으로 그룹핑, apiIds를 data(List)로 집계
|
||||
Map<String, WebhookSendRequest> resultMap = new LinkedHashMap<>();
|
||||
for (Tuple t : tuples) {
|
||||
String key = t.get(req.orgId) + "|" + t.get(req.targetUrl);
|
||||
@@ -78,6 +84,7 @@ public class WebhookService {
|
||||
WebhookSendRequest wr = new WebhookSendRequest();
|
||||
wr.setOrgId(t.get(req.orgId));
|
||||
wr.setTargetUrl(t.get(req.targetUrl));
|
||||
wr.setProxyUrl(this.getProxyUrl(reverseProxyUrl, t.get(org.reverseProxyPath), t.get(req.targetUrl)));
|
||||
wr.setSecret(t.get(req.secret));
|
||||
wr.setEventType(event);
|
||||
wr.setData(new ArrayList<String>());
|
||||
@@ -88,6 +95,46 @@ public class WebhookService {
|
||||
|
||||
return new ArrayList<>(resultMap.values());
|
||||
}
|
||||
|
||||
//reverse proxy 서버로 발송하기 위한 url 만든다
|
||||
private String getProxyUrl(String proxyUrl, String path, String targetUrl) {
|
||||
String base = stripTrailingSlash(proxyUrl);
|
||||
String normalizedPath = stripSlashes(path);
|
||||
// scheme://host[:port] 부분만 제거하고 이후 path/query/fragment는 그대로 유지
|
||||
String targetPath = targetUrl.replaceFirst("^[a-zA-Z][a-zA-Z0-9+.-]*://[^/]+", "");
|
||||
|
||||
StringBuilder url = new StringBuilder(base);
|
||||
if (!normalizedPath.isEmpty()) {
|
||||
url.append('/').append(normalizedPath);
|
||||
}
|
||||
url.append(targetPath);
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
private String stripTrailingSlash(String value) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
int end = value.length();
|
||||
while (end > 0 && value.charAt(end - 1) == '/') {
|
||||
end--;
|
||||
}
|
||||
return value.substring(0, end);
|
||||
}
|
||||
|
||||
private String stripSlashes(String value) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
String result = value.trim();
|
||||
while (result.startsWith("/")) {
|
||||
result = result.substring(1);
|
||||
}
|
||||
while (result.endsWith("/")) {
|
||||
result = result.substring(0, result.length() - 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 웹훅 발송 메인 메서드
|
||||
@@ -96,6 +143,7 @@ public class WebhookService {
|
||||
public void send(WebhookSendRequest req) {
|
||||
|
||||
String targetUrl = req.getTargetUrl();
|
||||
String proxyUrl = req.getProxyUrl();
|
||||
String secret = req.getSecret();
|
||||
String eventType = req.getEventType();
|
||||
Object data = req.getData();
|
||||
@@ -103,7 +151,9 @@ public class WebhookService {
|
||||
WebhookSendLog sendLog = new WebhookSendLog();
|
||||
sendLog.setOrgId(req.getOrgId());
|
||||
sendLog.setTargetUrl(targetUrl);
|
||||
sendLog.setProxyUrl(proxyUrl);
|
||||
sendLog.setEventType(eventType);
|
||||
sendLog.setSendBy(ContainerUtil.getInstanceName());
|
||||
|
||||
try {
|
||||
// 1. Payload 생성
|
||||
@@ -123,11 +173,11 @@ public class WebhookService {
|
||||
headers.set("X-Webhook-Timestamp", String.valueOf(webhookPayload.getTimestamp()));
|
||||
|
||||
// 4. 발송 (재시도 포함)
|
||||
int retryCount = monitoringContext.getIntProperty(MonitoringContext.API_WEBHOOK_RETRY_COUNT, DEFAULT_RETRY_COUNT);
|
||||
int retryTime = monitoringContext.getIntProperty(MonitoringContext.API_WEBHOOK_RETRY_TIME, DEFAULT_RETRY_TIME);
|
||||
int retryCount = monitoringContext.getIntProperty(MonitoringContext.RMS_WEBHOOK_RETRY_COUNT, DEFAULT_RETRY_COUNT);
|
||||
int retryTime = monitoringContext.getIntProperty(MonitoringContext.RMS_WEBHOOK_RETRY_TIME, DEFAULT_RETRY_TIME);
|
||||
sendLog.setSentAt(LocalDateTime.now());
|
||||
HttpEntity<String> httpRequest = new HttpEntity<>(payloadJson, headers);
|
||||
ResponseEntity<String> response = sendWithRetry(targetUrl, httpRequest, sendLog, retryCount, retryTime);
|
||||
ResponseEntity<String> response = sendWithRetry(proxyUrl, httpRequest, sendLog, retryCount, retryTime);
|
||||
|
||||
// 5. 성공 로그
|
||||
sendLog.setStatusCode(response.getStatusCode().value());
|
||||
@@ -135,7 +185,7 @@ public class WebhookService {
|
||||
sendLog.setSuccess(response.getStatusCode().is2xxSuccessful());
|
||||
|
||||
log.info("[Webhook] 발송 성공 - eventType: {}, url: {}, status: {}, retryCount: {}",
|
||||
eventType, targetUrl, response.getStatusCode(), sendLog.getRetryCount());
|
||||
eventType, proxyUrl, response.getStatusCode(), sendLog.getRetryCount());
|
||||
|
||||
} catch (HttpClientErrorException | HttpServerErrorException e) {
|
||||
sendLog.setStatusCode(e.getStatusCode().value());
|
||||
@@ -143,13 +193,13 @@ public class WebhookService {
|
||||
sendLog.setSuccess(false);
|
||||
sendLog.setErrorMessage(e.getMessage());
|
||||
log.error("[Webhook] HTTP 오류 - eventType: {}, url: {}, status: {}",
|
||||
eventType, targetUrl, e.getStatusCode());
|
||||
eventType, proxyUrl, e.getStatusCode());
|
||||
|
||||
} catch (Exception e) {
|
||||
sendLog.setSuccess(false);
|
||||
sendLog.setErrorMessage(e.getMessage());
|
||||
log.error("[Webhook] 발송 실패 - eventType: {}, url: {}, error: {}",
|
||||
eventType, targetUrl, e.getMessage());
|
||||
eventType, proxyUrl, e.getMessage());
|
||||
}
|
||||
|
||||
sendLogRepository.save(sendLog);
|
||||
@@ -161,24 +211,25 @@ public class WebhookService {
|
||||
/**
|
||||
* 재시도 포함 HTTP 발송
|
||||
* - 4xx: 클라이언트 오류이므로 즉시 throw (재시도 불필요)
|
||||
* - 5xx / 네트워크 오류: 최대 RETRY_COUNT회까지 exponential backoff 재시도 (1s → 2s → 4s)
|
||||
* - 5xx / 네트워크 오류: 최대 RETRY_COUNT회까지 선형 증가 backoff 재시도 (1s → 2s → 3s → 4s → 5s)
|
||||
*/
|
||||
private ResponseEntity<String> sendWithRetry(String targetUrl, HttpEntity<String> request, WebhookSendLog sendLog, int retryCount, int retryTime) throws Exception {
|
||||
private ResponseEntity<String> sendWithRetry(String proxyUrl, HttpEntity<String> request, WebhookSendLog sendLog, int retryCount, int retryTime) throws Exception {
|
||||
Exception lastException = null;
|
||||
for (int attempt = 0; attempt <= retryCount; attempt++) {
|
||||
try {
|
||||
if (attempt > 0) {
|
||||
long delayMs = retryTime * (1L << (attempt - 1));
|
||||
log.info("[Webhook] 재시도 {}/{} - url: {}, delay: {}ms", attempt, retryCount, targetUrl, delayMs);
|
||||
Thread.sleep(delayMs);
|
||||
long delayMs = (long) retryTime * attempt;
|
||||
log.info("[Webhook] 재시도 {}/{} - url: {}, delay: {}ms", attempt, retryCount, proxyUrl, delayMs);
|
||||
sendLog.setRetryCount(attempt);
|
||||
Thread.sleep(delayMs);
|
||||
}
|
||||
return restTemplate.postForEntity(targetUrl, request, String.class);
|
||||
return restTemplate.postForEntity(proxyUrl, request, String.class);
|
||||
} catch (HttpClientErrorException e) {
|
||||
throw e; // 4xx는 재시도 불필요
|
||||
//throw e; // 4xx는 재시도 불필요
|
||||
lastException = e; //TODO: 재시도 테스트
|
||||
} catch (Exception e) {
|
||||
lastException = e;
|
||||
log.warn("[Webhook] 발송 실패 (attempt {}/{}) - url: {}, error: {}", attempt, retryCount, targetUrl, e.getMessage());
|
||||
log.warn("[Webhook] 발송 실패 (attempt {}/{}) - url: {}, error: {}", attempt, retryCount, proxyUrl, e.getMessage());
|
||||
}
|
||||
}
|
||||
throw lastException;
|
||||
|
||||
@@ -20,13 +20,15 @@ import javax.servlet.http.HttpSession;
|
||||
public class SmsAuthController implements InterceptorSkipController {
|
||||
|
||||
private final MainController mainController;
|
||||
private final SmsAuthService smsAuthService;
|
||||
|
||||
public SmsAuthController(MainController mainController) {
|
||||
public SmsAuthController(MainController mainController, SmsAuthService smsAuthService) {
|
||||
this.mainController = mainController;
|
||||
this.smsAuthService = smsAuthService;
|
||||
}
|
||||
|
||||
private SmsAuthService getSmsAuthService() {
|
||||
return SmsAuthService.getInstance();
|
||||
return smsAuthService;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,7 +58,7 @@ public class SmsAuthController implements InterceptorSkipController {
|
||||
|
||||
// 인증번호 생성 및 발송
|
||||
String authCode = getSmsAuthService().generateAuthCode();
|
||||
boolean sent = getSmsAuthService().sendAuthCode(userInfo.getCphnno(), authCode);
|
||||
boolean sent = getSmsAuthService().sendAuthCode(userInfo, authCode);
|
||||
|
||||
if (sent) {
|
||||
// 세션에 인증번호 저장 (기존 UserInfo, LoginVo 유지)
|
||||
@@ -106,7 +108,7 @@ public class SmsAuthController implements InterceptorSkipController {
|
||||
|
||||
// 새 인증번호 생성 및 발송
|
||||
String authCode = getSmsAuthService().generateAuthCode();
|
||||
boolean sent = getSmsAuthService().sendAuthCode(userInfo.getCphnno(), authCode);
|
||||
boolean sent = getSmsAuthService().sendAuthCode(userInfo, authCode);
|
||||
|
||||
if (sent) {
|
||||
LoginVo loginVo = getSmsAuthService().getLoginVoFromSession(session);
|
||||
|
||||
@@ -1,26 +1,31 @@
|
||||
package com.eactive.eai.rms.ext.kjb.smsauth;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.HashMap;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.eactive.apim.portal.template.entity.MessageCode;
|
||||
import com.eactive.apim.portal.user.entity.UserInfo;
|
||||
import com.eactive.eai.rms.common.login.LoginVo;
|
||||
import com.eactive.eai.rms.ext.djb.ums.UmsManager;
|
||||
import com.eactive.ext.kjb.common.KjbProperty;
|
||||
import com.eactive.ext.kjb.common.KjbPropertyHolder;
|
||||
import com.eactive.ext.kjb.common.KjbUmsException;
|
||||
import com.eactive.ext.kjb.ums.KjbUmsService;
|
||||
import com.eactive.ext.kjb.ums.UmsBizWorkCode;
|
||||
import com.eactive.ext.kjb.ums.gson.GsonUtil;
|
||||
import com.eactive.ext.kjb.ums.gson.VerifyPhoneUmsTemplate;
|
||||
import com.google.gson.JsonObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.security.SecureRandom;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SmsAuthService {
|
||||
private static volatile SmsAuthService instance;
|
||||
|
||||
// 세션 키
|
||||
public static final String SESSION_KEY_AUTH_CODE = "SMS_AUTH_CODE";
|
||||
@@ -39,20 +44,7 @@ public class SmsAuthService {
|
||||
|
||||
private final SecureRandom random = new SecureRandom();
|
||||
|
||||
private SmsAuthService() {
|
||||
// 싱글톤
|
||||
}
|
||||
|
||||
public static SmsAuthService getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (SmsAuthService.class) {
|
||||
if (instance == null) {
|
||||
instance = new SmsAuthService();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
private final UmsManager ums;
|
||||
|
||||
private static KjbProperty getProperty() {
|
||||
return KjbPropertyHolder.get();
|
||||
@@ -116,20 +108,15 @@ public class SmsAuthService {
|
||||
/**
|
||||
* SMS 인증번호 발송
|
||||
*/
|
||||
public boolean sendAuthCode(String phone, String authCode) {
|
||||
public boolean sendAuthCode(UserInfo userInfo, String authCode) {
|
||||
try {
|
||||
String cleanedPhone = phone.replaceAll("[^0-9]", "").trim();
|
||||
String guid = String.format("EAPIM_EMS-%s",
|
||||
DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS").format(LocalDateTime.now()));
|
||||
|
||||
JsonObject content = GsonUtil.toJsonObject(VerifyPhoneUmsTemplate.builder().authNumber(authCode).build());
|
||||
|
||||
getUmsService().sendKakaoAlimTalk(guid, UmsBizWorkCode.VERIFY_PHONE, cleanedPhone, content);
|
||||
log.info("SMS 인증번호 발송 완료 - guid: {}, phone: {}****{}",
|
||||
guid, cleanedPhone.substring(0, 3), cleanedPhone.substring(cleanedPhone.length() - 4));
|
||||
log.debug("SMS 인증번호 발송 - authCode: {}", authCode);
|
||||
HashMap<String,Object> params = new HashMap<String, Object>();
|
||||
params.put("authNumber", authCode);
|
||||
params.put("userName", userInfo.getUsername());
|
||||
log.debug("\n\n\nDDDDDDDDD" + userInfo.toString());
|
||||
ums.send(userInfo, MessageCode.ADMIN_VERIFICATION_MOBILEPHONE, params);
|
||||
return true;
|
||||
} catch (KjbUmsException e) {
|
||||
} catch (Exception e) {
|
||||
log.error("SMS 인증번호 발송 실패", e);
|
||||
return false;
|
||||
}
|
||||
@@ -267,17 +254,14 @@ public class SmsAuthService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 전화번호 마스킹
|
||||
* 전화번호 마스킹 (표준 규칙: 2·3번째 세그먼트 각각 뒤 2자리)
|
||||
* 예: 010-1234-5678 → 010-12**-56**
|
||||
*/
|
||||
public String maskPhoneNumber(String phone) {
|
||||
if (StringUtils.isEmpty(phone)) {
|
||||
return "****";
|
||||
}
|
||||
String cleaned = phone.replaceAll("[^0-9]", "");
|
||||
if (cleaned.length() < 7) {
|
||||
return "****";
|
||||
}
|
||||
return cleaned.substring(0, 3) + "****" + cleaned.substring(cleaned.length() - 4);
|
||||
return com.eactive.eai.common.util.MaskingUtils.maskPhoneNumber(phone);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -286,11 +270,4 @@ public class SmsAuthService {
|
||||
public boolean isEnabled() {
|
||||
return getProperty().isSmsAuthEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* 테스트용 인스턴스 리셋
|
||||
*/
|
||||
public static void resetForTest() {
|
||||
instance = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,8 +134,8 @@ public class SsoController implements InterceptorSkipController {
|
||||
log.debug("SSO 사용자 정보 - userId: {}, name: {}, phone: {}, email: {}",
|
||||
userId,
|
||||
MaskingUtils.maskName(name),
|
||||
maskPhoneNumber(cellPhoneNo),
|
||||
maskEmail(email));
|
||||
MaskingUtils.maskPhoneNumber(cellPhoneNo),
|
||||
MaskingUtils.maskEmailId(email));
|
||||
|
||||
// 사용자 생성 또는 업데이트
|
||||
UserInfo userInfo = findOrCreateUser(info, extendedInfo);
|
||||
@@ -341,33 +341,4 @@ public class SsoController implements InterceptorSkipController {
|
||||
}
|
||||
return phone.replaceAll("[^0-9]", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* 휴대폰번호 마스킹 (로그용)
|
||||
*/
|
||||
private String maskPhoneNumber(String phone) {
|
||||
if (StringUtils.isEmpty(phone) || phone.length() < 4) {
|
||||
return "****";
|
||||
}
|
||||
// 010-1234-5678 → 010-****-5678
|
||||
String cleaned = phone.replaceAll("[^0-9]", "");
|
||||
if (cleaned.length() < 7) {
|
||||
return "****";
|
||||
}
|
||||
return cleaned.substring(0, 3) + "****" + cleaned.substring(cleaned.length() - 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* 이메일 마스킹 (로그용)
|
||||
*/
|
||||
private String maskEmail(String email) {
|
||||
if (StringUtils.isEmpty(email) || !email.contains("@")) {
|
||||
return "****";
|
||||
}
|
||||
int atIndex = email.indexOf("@");
|
||||
if (atIndex <= 2) {
|
||||
return "**" + email.substring(atIndex);
|
||||
}
|
||||
return email.substring(0, 2) + "**" + email.substring(atIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import com.eactive.eai.rms.onl.apim.portalorg.PortalOrgManService;
|
||||
import com.eactive.eai.rms.onl.apim.portaluser.PortalUserManService;
|
||||
import com.eactive.eai.rms.onl.apim.portaluser.PortalUserUI;
|
||||
import com.eactive.eai.rms.common.acl.user.ui.UserUI;
|
||||
import com.eactive.eai.common.util.SystemUtil;
|
||||
import com.eactive.eai.rms.onl.common.exception.BizException;
|
||||
import com.eactive.eai.rms.onl.transaction.apim.ApiInterfaceService;
|
||||
import com.eactive.eai.rms.onl.transaction.apim.mapping.ApiSpecUIMapper;
|
||||
@@ -359,6 +360,10 @@ public class PortalApprovalManService extends BaseService {
|
||||
}
|
||||
|
||||
public boolean isHardDeleteEnabled() {
|
||||
// 승인 요청 완전삭제는 dev(개발) 환경에서만 허용
|
||||
if (!SystemUtil.isDevServer()) {
|
||||
return false;
|
||||
}
|
||||
Map<String, String> properties = portalPropertyService.getPortalPropertiesAsMap(PORTAL_PROPERTY_GROUP);
|
||||
return Boolean.parseBoolean(properties.getOrDefault(HARD_DELETE_FLAG_KEY, "false"));
|
||||
}
|
||||
|
||||
+35
-2
@@ -110,11 +110,13 @@ public class PortalAppApprovalListener implements ApprovalListener {
|
||||
return;
|
||||
}
|
||||
MessageRecipient recipient = new MessageRecipient();
|
||||
recipient.setUsername(requester.getUserName());
|
||||
recipient.setPhone(requester.getMobileNumber());
|
||||
recipient.setUserId(requester.getEmailAddr());
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("apiKey", appRequest.getClientName());
|
||||
messageSendService.sendMessage(MessageCode.APP_REGISTER_APPROVED, recipient, params);
|
||||
// DB 템플릿 APP_APPROVE SMS 변수(%APP_NAME%)에 매핑
|
||||
params.put("APP_NAME", appRequest.getClientName());
|
||||
messageSendService.sendMessage(MessageCode.APP_APPROVE, recipient, params);
|
||||
}
|
||||
|
||||
private void syncTargetServer(AppRequestType action, CredentialUI credentialUI) throws ApprovalDeployException {
|
||||
@@ -225,5 +227,36 @@ public class PortalAppApprovalListener implements ApprovalListener {
|
||||
@Transactional
|
||||
public void rollback(Approval approval) {
|
||||
logger.info("Approval {} rollback", approval.getId());
|
||||
|
||||
// 앱 사용 거절 통지
|
||||
Optional<AppRequest> optRequest = appRequestRepository.findAppRequestByApproval(approval);
|
||||
if (!optRequest.isPresent()) {
|
||||
return;
|
||||
}
|
||||
AppRequest appRequest = optRequest.get();
|
||||
com.eactive.apim.portal.portaluser.entity.PortalUser requester = approval.getRequester();
|
||||
if (requester == null) {
|
||||
logger.warn("앱 거절 통지 생략 - 요청자 정보 없음. appRequestId: {}", appRequest.getId());
|
||||
return;
|
||||
}
|
||||
MessageRecipient recipient = new MessageRecipient();
|
||||
recipient.setUsername(requester.getUserName());
|
||||
recipient.setPhone(requester.getMobileNumber());
|
||||
recipient.setUserId(requester.getEmailAddr());
|
||||
Map<String, String> params = new HashMap<>();
|
||||
// DB 템플릿 APP_REJECTED SMS 변수(%APP_NAME%, %REASON%)에 매핑
|
||||
params.put("APP_NAME", appRequest.getClientName());
|
||||
params.put("REASON", extractRejectReason(approval));
|
||||
messageSendService.sendMessage(MessageCode.APP_REJECTED, recipient, params);
|
||||
}
|
||||
|
||||
private String extractRejectReason(Approval approval) {
|
||||
if (approval.getApprovers() == null) {
|
||||
return "";
|
||||
}
|
||||
return approval.getApprovers().stream()
|
||||
.map(com.eactive.apim.portal.approval.entity.Approver::getApprovalMessage)
|
||||
.filter(m -> m != null && !m.isEmpty())
|
||||
.findFirst().orElse("");
|
||||
}
|
||||
}
|
||||
|
||||
+37
-12
@@ -94,10 +94,15 @@ public class PortalUserApprovalListener implements ApprovalListener {
|
||||
|
||||
private void sendApprovalResult(PortalUser portalUser) {
|
||||
MessageRecipient recipient = new MessageRecipient();
|
||||
recipient.setUsername(portalUser.getUserName());
|
||||
recipient.setPhone(portalUser.getMobileNumber());
|
||||
recipient.setUserId(portalUser.getEmailAddr());
|
||||
Map<String, String> params = new HashMap<>();
|
||||
// messageSendService.sendMessage(MessageCode.USER_REGISTRATION_APPROVED, recipient, params);
|
||||
// DB 템플릿 MANAGER_WITH_ORG_REGISTER_APPROVED SMS 변수(%ORG_NAME%)에 매핑
|
||||
PortalOrg org = portalUser.getPortalOrg();
|
||||
if (org != null) {
|
||||
params.put("ORG_NAME", org.getOrgName());
|
||||
}
|
||||
messageSendService.sendMessage(MessageCode.MANAGER_WITH_ORG_REGISTER_APPROVED, recipient, params);
|
||||
}
|
||||
|
||||
@@ -155,6 +160,13 @@ public class PortalUserApprovalListener implements ApprovalListener {
|
||||
PortalUser portalUser = portalUserService.findByUserId(userId);
|
||||
PortalOrg portalOrg = portalUser.getPortalOrg();
|
||||
|
||||
// 거절 통지에 필요한 정보를 반려 처리(정보 마스킹) 이전에 캡처
|
||||
String noticeName = portalUser.getUserName();
|
||||
String noticePhone = portalUser.getMobileNumber();
|
||||
String noticeEmail = portalUser.getEmailAddr();
|
||||
String noticeOrgName = portalOrg != null ? portalOrg.getOrgName() : null;
|
||||
String rejectReason = extractRejectReason(approval);
|
||||
|
||||
// 개인→법인 전환 요청 반려: approvalStatus가 PENDING이면 전환 신청이었으므로 개인 사용자로 복원
|
||||
if (PortalUserEnums.ApprovalStatus.PENDING.equals(portalUser.getApprovalStatus())) {
|
||||
logger.info("Approval {} is a personal-to-corporate conversion rejection. Reverting user {} to personal user.", approval.getId(), userId);
|
||||
@@ -185,19 +197,32 @@ public class PortalUserApprovalListener implements ApprovalListener {
|
||||
portalOrg.setOrgName("삭제된법인");
|
||||
portalOrg.setApprovalStatus(ApprovalStatus.REJECTED);
|
||||
portalOrg.setOrgStatus(OrgStatus.REMOVED);
|
||||
portalOrg.setOrgCode(null);
|
||||
portalOrg.setCompRegFile(null);
|
||||
portalOrg.setCorpRegNo(null);
|
||||
portalOrg.setCompRegNo(null);
|
||||
portalOrg.setCeoName(null);
|
||||
portalOrg.setOrgAddr(null);
|
||||
portalOrg.setOrgPhoneNumber(null);
|
||||
portalOrg.setScPhoneNumber(null);
|
||||
portalOrg.setOrgSectors(null);
|
||||
portalOrg.setOrgIndustryType(null);
|
||||
portalOrg.setServiceName(null);
|
||||
portalOrg.clearSensitiveDataOnRemoval();
|
||||
|
||||
portalOrgService.save(portalOrg);
|
||||
}
|
||||
|
||||
// 법인관리자/법인 등록 거절 통지 (정보 마스킹 이전 캡처값 사용)
|
||||
MessageRecipient recipient = new MessageRecipient();
|
||||
recipient.setUsername(noticeName);
|
||||
recipient.setPhone(noticePhone);
|
||||
recipient.setUserId(noticeEmail);
|
||||
Map<String, String> params = new HashMap<>();
|
||||
// DB 템플릿 MANAGER_WITH_ORG_REGISTER_REJECTED SMS 변수(%ORG_NAME%, %REASON%)에 매핑
|
||||
if (noticeOrgName != null) {
|
||||
params.put("ORG_NAME", noticeOrgName);
|
||||
}
|
||||
params.put("REASON", rejectReason);
|
||||
messageSendService.sendMessage(MessageCode.MANAGER_WITH_ORG_REGISTER_REJECTED, recipient, params);
|
||||
}
|
||||
|
||||
private String extractRejectReason(Approval approval) {
|
||||
if (approval.getApprovers() == null) {
|
||||
return "";
|
||||
}
|
||||
return approval.getApprovers().stream()
|
||||
.map(com.eactive.apim.portal.approval.entity.Approver::getApprovalMessage)
|
||||
.filter(m -> m != null && !m.isEmpty())
|
||||
.findFirst().orElse("");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.eactive.eai.rms.onl.apim.authserver;
|
||||
|
||||
import com.eactive.eai.agent.command.CommonCommand;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceContextHolder;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceType;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
|
||||
import com.eactive.eai.rms.onl.apim.common.BaseRestController;
|
||||
import com.eactive.eai.rms.onl.common.service.AgentUtilService;
|
||||
import com.eactive.eai.rms.onl.manage.authserver.client.ClientManService;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Controller;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 포털 등 내부 시스템이 호출하는 인증서버(GW) 클라이언트 제어용 내부 API.
|
||||
*
|
||||
* <p>{@link BaseRestController} 를 상속하여 ① 세션 인증을 skip(InterceptorSkipController)하고
|
||||
* ② {@code IpWhitelistInterceptor} 를 통해 {@code kjb.api.allow_ip_list} 기준 IP 화이트리스트
|
||||
* 인증을 적용받는다. (호출 측 포털 서버 IP를 반드시 허용목록에 등록해야 함)</p>
|
||||
*
|
||||
* @see com.eactive.eai.rms.common.interceptor.IpWhitelistInterceptor
|
||||
*/
|
||||
@Controller
|
||||
public class ClientBlockApiController extends BaseRestController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ClientBlockApiController.class);
|
||||
|
||||
private final ClientManService clientManService;
|
||||
|
||||
@Qualifier("onlAgentUtilService")
|
||||
@Autowired
|
||||
private AgentUtilService agentUtilService;
|
||||
|
||||
@Autowired
|
||||
public ClientBlockApiController(ClientManService clientManService) {
|
||||
this.clientManService = clientManService;
|
||||
}
|
||||
|
||||
/**
|
||||
* clientId 의 GW 인증 클라이언트를 차단(TSEAIAU01.appstatus = '0')한 뒤,
|
||||
* 승인 경로와 동일하게 GW 인증서버로 RELOAD_CLIENT 명령을 broadcast 하여 캐시를 갱신한다.
|
||||
*
|
||||
* @param clientId 차단할 클라이언트 ID
|
||||
* @return 처리 결과 JSON ({@code {"success":true,"clientId":...}} 또는 {@code {"success":false,"msg":...}})
|
||||
*/
|
||||
@RequestMapping(value = "/onl/admin/authserver/clientBlock.json", method = RequestMethod.POST, produces = JSON_CONTENT_TYPE)
|
||||
@ResponseBody
|
||||
public String blockClient(@RequestParam String clientId) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
try {
|
||||
// TSEAIAU01(ClientEntity)은 APIGW 데이터소스에 있으므로 라우팅을 먼저 설정한다.
|
||||
DataSourceType type = DataSourceTypeManager.getDataSourceType("APIGW");
|
||||
DataSourceContextHolder.setDataSourceType(type);
|
||||
|
||||
// 상태 차단 (appstatus = '0')
|
||||
clientManService.blockClient(clientId);
|
||||
|
||||
// GW 인증서버 캐시 리로드 (승인 경로 PortalAppApprovalListener 와 동일)
|
||||
CommonCommand.builder()
|
||||
.name(CommonCommand.RELOAD_CLIENT_COMMAND)
|
||||
.args(clientId)
|
||||
.build().broadcast(agentUtilService);
|
||||
|
||||
logger.warn("GW client 차단(appstatus=0)+리로드 완료 - clientId={}", clientId);
|
||||
result.put("success", true);
|
||||
result.put("clientId", clientId);
|
||||
} catch (Exception e) {
|
||||
logger.error("GW client 차단 실패 - clientId={}", clientId, e);
|
||||
result.put("success", false);
|
||||
result.put("msg", e.getMessage());
|
||||
}
|
||||
return toJson(result);
|
||||
}
|
||||
}
|
||||
@@ -1,83 +1,52 @@
|
||||
package com.eactive.eai.rms.onl.apim.masking;
|
||||
|
||||
import com.eactive.eai.rms.common.util.StringUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 개인정보 마스킹 유틸리티 (eapim-admin)
|
||||
*
|
||||
* <p>실제 마스킹 규칙은 보안아키텍처 표준 구현인
|
||||
* {@link com.eactive.eai.common.util.MaskingUtils}(elink-online-core)에 위임한다.
|
||||
* 기존 호출부 호환을 위해 메서드명/시그니처는 그대로 유지한다.</p>
|
||||
*
|
||||
* <pre>
|
||||
* 이메일 eact***@gmail.com (구) → **ct1**@gmail.com (앞2·뒤2, 4자 이하 전체)
|
||||
* 이름 홍길* (구) → 홍*동 (2자 뒤1, 3자↑ 가운데 전체)
|
||||
* 전화 010-****-5678 (구) → 010-12**-56** (세그먼트별 뒤 2자리)
|
||||
* IPv4 192.168.***.1 (3번째 옥텟) / IPv6 마지막 그룹, 콤마 다중 IP 지원
|
||||
* </pre>
|
||||
*/
|
||||
public class MaskingUtils {
|
||||
|
||||
private MaskingUtils() {}
|
||||
|
||||
/**
|
||||
* 이메일 주소의 ID 부분을 마스킹 처리
|
||||
* 예: eactive@gmail.com => eact***@gmail.com
|
||||
* 예: aajjbacc@gmail.com => **jjba**@gmail.com
|
||||
*/
|
||||
public static String maskEmailId(String email) {
|
||||
if (email == null || email.isEmpty() || !email.contains("@")) {
|
||||
return email;
|
||||
}
|
||||
|
||||
String[] parts = email.split("@");
|
||||
String id = parts[0];
|
||||
String domain = parts[1];
|
||||
|
||||
if (id.length() <= 3) {
|
||||
return "***" + "@" + domain;
|
||||
}
|
||||
|
||||
String maskedId = id.substring(0, id.length() - 3) + "***";
|
||||
return maskedId + "@" + domain;
|
||||
return com.eactive.eai.common.util.MaskingUtils.maskEmail(email);
|
||||
}
|
||||
|
||||
/**
|
||||
* 이름의 마지막 글자를 마스킹 처리
|
||||
* 이름 마스킹 처리 (2자: 뒤 1자리, 3자 이상: 앞뒤 제외 가운데 전체)
|
||||
* 예: 홍길동 => 홍*동
|
||||
*/
|
||||
public static String maskName(String name) {
|
||||
if (name == null || name.isEmpty() || name.length() < 2) {
|
||||
return name;
|
||||
}
|
||||
|
||||
return name.substring(0, name.length() - 1) + "*";
|
||||
return com.eactive.eai.common.util.MaskingUtils.maskKoreanName(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 전화번호 마스킹 처리
|
||||
* 예: 010-1234-5678 => 010-****-5678
|
||||
* 전화번호/휴대폰/FAX 마스킹 처리 (2·3번째 세그먼트 각각 뒤 2자리)
|
||||
* 예: 010-1234-5678 => 010-12**-56**
|
||||
*/
|
||||
public static String maskPhoneNumber(String phoneNumber) {
|
||||
if (phoneNumber == null || phoneNumber.isEmpty()) {
|
||||
return phoneNumber;
|
||||
}
|
||||
|
||||
String[] parts = phoneNumber.split("-");
|
||||
if (parts.length != 3) {
|
||||
return phoneNumber;
|
||||
}
|
||||
|
||||
return parts[0] + "-****-" + parts[2];
|
||||
return com.eactive.eai.common.util.MaskingUtils.maskPhoneNumber(phoneNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* IP 주소 마스킹 처리
|
||||
* 예: 192.168.1.1 => 192.168.*.*
|
||||
* IP 주소 마스킹 처리 (IPv4 3번째 옥텟 / IPv6 마지막 그룹, 콤마 다중 IP 지원)
|
||||
* 예: 192.168.1.1 => 192.168.***.1
|
||||
*/
|
||||
public static String maskIpAddress(String ipAddresses) {
|
||||
if (StringUtils.isBlank(ipAddresses)) {
|
||||
return ipAddresses;
|
||||
}
|
||||
|
||||
String[] ips = ipAddresses.split(",");
|
||||
List<String> maskedIps = Arrays.stream(ips)
|
||||
.map(ip -> {
|
||||
String[] parts = ip.trim().split("\\.");
|
||||
if (parts.length == 4) {
|
||||
// 세 번째 octet을 ***로 마스킹
|
||||
parts[2] = "***";
|
||||
return String.join(".", parts);
|
||||
}
|
||||
return ip; // 유효하지 않은 IP 형식은 그대로 반환
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return String.join(",", maskedIps);
|
||||
return com.eactive.eai.common.util.MaskingUtils.maskIpAddress(ipAddresses);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
package com.eactive.eai.rms.onl.apim.masking;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 메세지 본문/메신저ID 패턴 기반 마스킹 유틸 (발송 내역 화면 전용).
|
||||
*
|
||||
* <p>본문(message) 안에 섞여 있는 개인정보/인증정보를 패턴으로 찾아 마스킹하고,
|
||||
* 패턴 종류별로 색상 강조 {@code <span>} 으로 감싼 <b>안전한 HTML</b> 을 만든다.
|
||||
* 매칭되지 않은 영역과 마스킹 결과 모두 HTML escape 하므로 본문에 스크립트가 있어도 무력화된다.</p>
|
||||
*
|
||||
* <pre>
|
||||
* 이메일 test@gmail.com → <span class="mask-email">**st@gmail.com</span>
|
||||
* 휴대폰(하이픈) 010-1234-5678 → <span class="mask-phone">010-12**-56**</span>
|
||||
* 6자리+ 숫자 123456 → <span class="mask-digits">123***</span> (앞 3자리 제외)
|
||||
* 메신저ID kakao_abcd → kakao_ab** (뒤 2자리)
|
||||
* </pre>
|
||||
*
|
||||
* <p>실제 이메일/휴대폰 마스킹 규칙은 표준 구현
|
||||
* {@link com.eactive.eai.common.util.MaskingUtils}(elink-online-core)에 위임한다.</p>
|
||||
*/
|
||||
public final class MessagePatternMaskingUtils {
|
||||
|
||||
private MessagePatternMaskingUtils() {}
|
||||
|
||||
private static final int DIGITS_KEEP_HEAD = 3;
|
||||
|
||||
/**
|
||||
* 매칭 우선순위(이메일 > 하이픈 휴대폰 > 6자리+ 숫자)를 단일 패스로 보장하는 결합 정규식.
|
||||
* 휴대폰 하이픈 형식이 "6자리 이상 숫자" 규칙에 이중 적용되지 않도록 한 Matcher 로 순회한다.
|
||||
*/
|
||||
private static final Pattern COMBINED = Pattern.compile(
|
||||
"(?<email>[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,})"
|
||||
+ "|(?<phone>01[016789]-\\d{3,4}-\\d{4})"
|
||||
+ "|(?<digits>\\d{6,})");
|
||||
|
||||
/** 본문 → escape + 패턴별 마스킹/색상강조 span 이 적용된 안전 HTML 반환. */
|
||||
public static String maskAndHighlight(String raw) {
|
||||
if (raw == null || raw.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
Matcher m = COMBINED.matcher(raw);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int last = 0;
|
||||
while (m.find()) {
|
||||
sb.append(escapeHtml(raw.substring(last, m.start())));
|
||||
String token = m.group();
|
||||
if (m.group("email") != null) {
|
||||
sb.append(span("mask-email", com.eactive.eai.common.util.MaskingUtils.maskEmail(token)));
|
||||
} else if (m.group("phone") != null) {
|
||||
sb.append(span("mask-phone", com.eactive.eai.common.util.MaskingUtils.maskPhoneNumber(token)));
|
||||
} else {
|
||||
sb.append(span("mask-digits", maskKeepHead(token, DIGITS_KEEP_HEAD)));
|
||||
}
|
||||
last = m.end();
|
||||
}
|
||||
sb.append(escapeHtml(raw.substring(last)));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/** 메신저 ID 마스킹: 뒤 2자리(2자 이하는 전체) 마스킹. */
|
||||
public static String maskMessengerId(String id) {
|
||||
if (id == null || id.isEmpty()) {
|
||||
return id;
|
||||
}
|
||||
if (id.length() <= 2) {
|
||||
return repeat('*', id.length());
|
||||
}
|
||||
return id.substring(0, id.length() - 2) + "**";
|
||||
}
|
||||
|
||||
/** 앞 head 자리만 남기고 나머지를 '*' 로 마스킹. */
|
||||
private static String maskKeepHead(String digits, int head) {
|
||||
if (digits.length() <= head) {
|
||||
return digits;
|
||||
}
|
||||
return digits.substring(0, head) + repeat('*', digits.length() - head);
|
||||
}
|
||||
|
||||
private static String span(String cls, String inner) {
|
||||
return "<span class=\"" + cls + "\">" + escapeHtml(inner) + "</span>";
|
||||
}
|
||||
|
||||
/**
|
||||
* 최소 HTML escape. {@code < > & " '} 만 변환하고 한글 등 비-ASCII 문자는 보존한다.
|
||||
* (commons-lang {@code escapeHtml} 은 0x7F 초과 문자를 {@code &#NNN;} 로 변환해 한글이 깨지므로 직접 구현)
|
||||
*/
|
||||
private static String escapeHtml(String s) {
|
||||
if (s == null || s.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
StringBuilder sb = new StringBuilder(s.length());
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
switch (c) {
|
||||
case '&': sb.append("&"); break;
|
||||
case '<': sb.append("<"); break;
|
||||
case '>': sb.append(">"); break;
|
||||
case '"': sb.append("""); break;
|
||||
case '\'': sb.append("'"); break;
|
||||
default: sb.append(c);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String repeat(char c, int n) {
|
||||
if (n <= 0) {
|
||||
return "";
|
||||
}
|
||||
char[] a = new char[n];
|
||||
Arrays.fill(a, c);
|
||||
return new String(a);
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
package com.eactive.eai.rms.onl.apim.messagerequest;
|
||||
|
||||
import com.eactive.eai.rms.common.base.BaseAnnotationController;
|
||||
import com.eactive.eai.rms.common.vo.GridResponse;
|
||||
import com.eactive.eai.rms.data.entity.onl.apim.messagerequest.MessageRequestUISearch;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.SortDefault;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 발송 내역(PTL_MESSAGE_REQUEST) 조회 화면 컨트롤러.
|
||||
*
|
||||
* <p>조회 전용 + PENDING→FAILED 상태 전환만 제공한다. (등록/수정/삭제 없음)</p>
|
||||
*/
|
||||
@Controller
|
||||
@RequiredArgsConstructor
|
||||
public class MessageRequestManController extends BaseAnnotationController {
|
||||
|
||||
private final MessageRequestManService messageRequestManService;
|
||||
|
||||
@GetMapping(value = "/onl/apim/messagerequest/messageRequestMan.view")
|
||||
public void view() {
|
||||
// 목록 view
|
||||
}
|
||||
|
||||
@GetMapping(value = "/onl/apim/messagerequest/messageRequestMan.view", params = "cmd=DETAIL")
|
||||
public String detailView() {
|
||||
return "/onl/apim/messagerequest/messageRequestManDetail";
|
||||
}
|
||||
|
||||
@PostMapping(value = "/onl/apim/messagerequest/messageRequestMan.json", params = "cmd=LIST")
|
||||
public ResponseEntity<GridResponse<MessageRequestUI>> selectList(
|
||||
@SortDefault(sort = "requestDate", direction = Sort.Direction.DESC) Pageable pageable,
|
||||
MessageRequestUISearch search) {
|
||||
Page<MessageRequestUI> page = messageRequestManService.selectList(pageable, search);
|
||||
return ResponseEntity.ok(new GridResponse<>(page));
|
||||
}
|
||||
|
||||
@PostMapping(value = "/onl/apim/messagerequest/messageRequestMan.json", params = "cmd=LIST_INIT_COMBO")
|
||||
public ResponseEntity<Map<String, Object>> initCombo() {
|
||||
Map<String, Object> resultMap = new HashMap<>();
|
||||
resultMap.put("statusList", messageRequestManService.statusCombo());
|
||||
resultMap.put("messageCodeList", messageRequestManService.messageCodeCombo());
|
||||
return ResponseEntity.ok(resultMap);
|
||||
}
|
||||
|
||||
@PostMapping(value = "/onl/apim/messagerequest/messageRequestMan.json", params = "cmd=DETAIL")
|
||||
public ResponseEntity<MessageRequestUI> selectDetail(String id) {
|
||||
return ResponseEntity.ok(messageRequestManService.selectDetail(id));
|
||||
}
|
||||
|
||||
/** PENDING 건을 FAILED 로 전환 (감사 포인트: UPDATE_STATUS). */
|
||||
@PostMapping(value = "/onl/apim/messagerequest/messageRequestMan.json", params = "cmd=UPDATE_STATUS")
|
||||
public ResponseEntity<Void> updateStatus(String id) {
|
||||
messageRequestManService.updateStatusToFailed(id);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
package com.eactive.eai.rms.onl.apim.messagerequest;
|
||||
|
||||
import com.eactive.apim.portal.template.entity.MessageCode;
|
||||
import com.eactive.apim.portal.template.entity.MessageRequest;
|
||||
import com.eactive.eai.rms.common.base.BaseService;
|
||||
import com.eactive.eai.rms.common.combo.ComboVo;
|
||||
import com.eactive.eai.rms.data.entity.onl.apim.messagerequest.MessageRequestUISearch;
|
||||
import com.eactive.eai.rms.onl.apim.masking.MaskingUtils;
|
||||
import com.eactive.eai.rms.onl.apim.masking.MessagePatternMaskingUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@Transactional(transactionManager = "transactionManagerForEMS")
|
||||
public class MessageRequestManService extends BaseService {
|
||||
|
||||
// KST 한글 요일 포맷 (표시용 / 툴팁용)
|
||||
private static final DateTimeFormatter FMT_SHORT = DateTimeFormatter.ofPattern("MM-dd(E) HH:mm", Locale.KOREA);
|
||||
private static final DateTimeFormatter FMT_FULL = DateTimeFormatter.ofPattern("yyyy-MM-dd (EEE) HH:mm:ss", Locale.KOREA);
|
||||
|
||||
private final com.eactive.eai.rms.data.entity.onl.apim.messagerequest.MessageRequestService messageRequestService;
|
||||
|
||||
@Autowired
|
||||
public MessageRequestManService(com.eactive.eai.rms.data.entity.onl.apim.messagerequest.MessageRequestService messageRequestService) {
|
||||
this.messageRequestService = messageRequestService;
|
||||
}
|
||||
|
||||
public Page<MessageRequestUI> selectList(Pageable pageable, MessageRequestUISearch search) {
|
||||
return messageRequestService.findAll(pageable, search).map(this::convertToUI);
|
||||
}
|
||||
|
||||
public MessageRequestUI selectDetail(String id) {
|
||||
return convertToUI(messageRequestService.getById(id));
|
||||
}
|
||||
|
||||
/** PENDING → FAILED 전환. */
|
||||
public void updateStatusToFailed(String id) {
|
||||
messageRequestService.updateStatusToFailedIfPending(id);
|
||||
}
|
||||
|
||||
/** 엔티티 → 마스킹/포맷 적용된 UI 변환 (목록·상세 공통). */
|
||||
private MessageRequestUI convertToUI(MessageRequest e) {
|
||||
MessageRequestUI ui = new MessageRequestUI();
|
||||
ui.setId(e.getId());
|
||||
ui.setMessageCode(e.getMessageCode() != null ? e.getMessageCode().name() : null);
|
||||
ui.setMessageCodeName(e.getMessageCode() != null ? e.getMessageCode().getDescription() : null);
|
||||
ui.setSubject(e.getSubject());
|
||||
ui.setMessageType(e.getMessageType());
|
||||
|
||||
// 본문: 패턴 마스킹 + 색상강조 안전 HTML
|
||||
ui.setMessageHtml(MessagePatternMaskingUtils.maskAndHighlight(e.getMessage()));
|
||||
|
||||
// 수신자 마스킹 (email/phone 은 @Convert 로 이미 복호화된 평문)
|
||||
ui.setUsername(MaskingUtils.maskName(e.getUsername()));
|
||||
ui.setEmail(MaskingUtils.maskEmailId(e.getEmail()));
|
||||
ui.setPhone(MaskingUtils.maskPhoneNumber(e.getPhone()));
|
||||
ui.setMessengerId(MessagePatternMaskingUtils.maskMessengerId(e.getMessengerId()));
|
||||
|
||||
ui.setUmsUid(e.getUmsUid());
|
||||
ui.setRequestStatus(e.getRequestStatus());
|
||||
ui.setEaiInterfaceId(e.getEaiInterfaceId());
|
||||
ui.setServiceId(e.getServiceId());
|
||||
|
||||
ui.setRequestDateShort(fmt(e.getRequestDate(), FMT_SHORT));
|
||||
ui.setRequestDateFull(fmt(e.getRequestDate(), FMT_FULL));
|
||||
ui.setSentDateShort(fmt(e.getSentDate(), FMT_SHORT));
|
||||
ui.setSentDateFull(fmt(e.getSentDate(), FMT_FULL));
|
||||
return ui;
|
||||
}
|
||||
|
||||
private static String fmt(LocalDateTime t, DateTimeFormatter f) {
|
||||
return t == null ? "" : t.format(f);
|
||||
}
|
||||
|
||||
/** 상태 검색 콤보 (PENDING/SENT/FAILED). */
|
||||
public List<ComboVo> statusCombo() {
|
||||
return Arrays.stream(new String[]{"PENDING", "SENT", "FAILED"})
|
||||
.map(s -> {
|
||||
ComboVo v = new ComboVo();
|
||||
v.setCode(s);
|
||||
v.setCodename(s);
|
||||
return v;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/** 메세지 코드 검색 콤보 (enum name → 한글 설명). */
|
||||
public List<ComboVo> messageCodeCombo() {
|
||||
return Arrays.stream(MessageCode.values())
|
||||
.map(c -> {
|
||||
ComboVo v = new ComboVo();
|
||||
v.setCode(c.name());
|
||||
v.setCodename(c.getDescription());
|
||||
return v;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user