Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
Vendored
+179
@@ -0,0 +1,179 @@
|
|||||||
|
pipeline {
|
||||||
|
agent { label 'djb-vm' }
|
||||||
|
|
||||||
|
options {
|
||||||
|
timestamps()
|
||||||
|
disableConcurrentBuilds()
|
||||||
|
buildDiscarder(logRotator(numToKeepStr: '20', artifactNumToKeepStr: '20'))
|
||||||
|
}
|
||||||
|
|
||||||
|
environment {
|
||||||
|
JAVA_HOME = '/apps/opts/jdk8'
|
||||||
|
JAVA_HOME_TOMCAT = '/apps/opts/jdk17'
|
||||||
|
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}"
|
||||||
|
GIT_SSH_COMMAND = 'ssh -o StrictHostKeyChecking=accept-new'
|
||||||
|
CATALINA_BASE = '/prod/eapim/adminportal'
|
||||||
|
CATALINA_HOME = '/prod/eapim/apache-tomcat-9.0.116'
|
||||||
|
CATALINA_PID = '/prod/eapim/adminportal/temp/catalina.pid'
|
||||||
|
DEPLOY_HTTP_PORT = '39120'
|
||||||
|
}
|
||||||
|
|
||||||
|
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'
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
success {
|
||||||
|
sh '''
|
||||||
|
set -eu
|
||||||
|
cd build/libs
|
||||||
|
sha1sum eapim-admin.war > SHA1SUMS
|
||||||
|
sha256sum eapim-admin.war > SHA256SUMS
|
||||||
|
'''
|
||||||
|
archiveArtifacts artifacts: 'build/libs/eapim-admin.war,build/libs/SHA1SUMS,build/libs/SHA256SUMS', fingerprint: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Stop Tomcat') {
|
||||||
|
steps {
|
||||||
|
sh '''
|
||||||
|
set +e
|
||||||
|
systemctl --user stop eapim-admin 2>/dev/null
|
||||||
|
|
||||||
|
if [ -f "$CATALINA_PID" ] && kill -0 "$(cat "$CATALINA_PID")" 2>/dev/null; then
|
||||||
|
JAVA_HOME="$JAVA_HOME_TOMCAT" "$CATALINA_HOME/bin/shutdown.sh" 30 -force || true
|
||||||
|
for i in $(seq 1 30); do
|
||||||
|
[ -f "$CATALINA_PID" ] && kill -0 "$(cat "$CATALINA_PID")" 2>/dev/null || break
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
rm -f "$CATALINA_PID"
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Deploy monitoring.war') {
|
||||||
|
steps {
|
||||||
|
sh '''
|
||||||
|
set -eu
|
||||||
|
DEPLOY_DIR="$CATALINA_BASE/webapps"
|
||||||
|
rm -rf "$DEPLOY_DIR/monitoring" "$DEPLOY_DIR/monitoring.war"
|
||||||
|
cp build/libs/eapim-admin.war "$DEPLOY_DIR/monitoring.war"
|
||||||
|
ls -la "$DEPLOY_DIR"
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Start Tomcat and readiness') {
|
||||||
|
steps {
|
||||||
|
sh '''
|
||||||
|
set -eu
|
||||||
|
LOG="$CATALINA_BASE/logs/catalina.$(date +%Y-%m-%d).log"
|
||||||
|
BEFORE=0
|
||||||
|
[ -f "$LOG" ] && BEFORE=$(stat -c %s "$LOG")
|
||||||
|
|
||||||
|
systemctl --user start eapim-admin
|
||||||
|
|
||||||
|
PROBE_URL="http://localhost:$DEPLOY_HTTP_PORT/monitoring/loginForm.do"
|
||||||
|
DEADLINE=$(($(date +%s) + 300))
|
||||||
|
STATUS=000
|
||||||
|
|
||||||
|
while [ "$(date +%s)" -lt "$DEADLINE" ]; do
|
||||||
|
STATUS=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 3 \
|
||||||
|
"$PROBE_URL" 2>/dev/null || echo "000")
|
||||||
|
case "$STATUS" in
|
||||||
|
200|302|401)
|
||||||
|
echo "Readiness OK ($PROBE_URL HTTP $STATUS)"
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -f "$LOG" ] && tail -c +$((BEFORE+1)) "$LOG" | grep -qE "Application listener .* Exception|SEVERE.*startup.Catalina"; then
|
||||||
|
echo "Startup error detected in log"
|
||||||
|
tail -c +$((BEFORE+1)) "$LOG" | tail -80
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
case "$STATUS" in
|
||||||
|
200|302|401) ;;
|
||||||
|
*)
|
||||||
|
echo "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 not listening)"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "--- key boot log lines ---"
|
||||||
|
tail -c +$((BEFORE+1)) "$LOG" | grep -E "Server startup|Deployment of web" | head -10 || true
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ PortalTermsManController_약관관리_APIGW_INSERT,UPDATE,DELETE
|
|||||||
ProdClientManController_운영Client (키정보)관리_APIGW_INSERT,UPDATE,DELETE
|
ProdClientManController_운영Client (키정보)관리_APIGW_INSERT,UPDATE,DELETE
|
||||||
ApiSpecController_API스펙관리_APIGW_INSERT,DELETE
|
ApiSpecController_API스펙관리_APIGW_INSERT,DELETE
|
||||||
MessageTemplateManController_메세지템플릿관리_APIGW_INSERT,UPDATE,DELETE
|
MessageTemplateManController_메세지템플릿관리_APIGW_INSERT,UPDATE,DELETE
|
||||||
PortalPartnershipManController_사업제휴신청관리_APIGW_UNMASK
|
PortalPartnershipManController_피드백/개선요청관리_APIGW_UNMASK,DELETE
|
||||||
PortalPropertyController_포탈 프로퍼티 관리_APIGW_UPDATE
|
PortalPropertyController_포탈 프로퍼티 관리_APIGW_UPDATE
|
||||||
PortalUserTermsManController_약관동의 이력_APIGW_UNMASK
|
PortalUserTermsManController_약관동의 이력_APIGW_UNMASK
|
||||||
|
MessageRequestManController_메세지발송내역_APIGW_UPDATE_STATUS
|
||||||
|
|||||||
@@ -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 language="java" contentType="text/html; charset=utf-8"%>
|
||||||
<%@page import="org.apache.commons.lang3.StringUtils"%>
|
<%@page import="org.apache.commons.lang3.StringUtils"%>
|
||||||
<%@page import="com.eactive.eai.rms.common.login.SessionManager"%>
|
<%@page import="com.eactive.eai.rms.common.login.SessionManager"%>
|
||||||
|
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
|||||||
@@ -52,6 +52,13 @@
|
|||||||
var urlParam = window.document.location.search;
|
var urlParam = window.document.location.search;
|
||||||
var mod = "";
|
var mod = "";
|
||||||
var strServiceType = "<%=request.getParameter("serviceType")%>";
|
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 != ""){
|
||||||
if(urlParam.indexOf("mod=") > -1){
|
if(urlParam.indexOf("mod=") > -1){
|
||||||
mod = urlParam.substring((urlParam.indexOf("mod=") + 4), urlParam.lastIndexOf("&"));
|
mod = urlParam.substring((urlParam.indexOf("mod=") + 4), urlParam.lastIndexOf("&"));
|
||||||
@@ -69,10 +76,13 @@
|
|||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
var url = '<%=topPage%>';
|
var url = '<%=topPage%>';
|
||||||
|
var serviceType = getServiceType();
|
||||||
|
if (serviceType) {
|
||||||
if (url.indexOf("?")>=0){
|
if (url.indexOf("?")>=0){
|
||||||
url = url + "&serviceType="+ sessionStorage["serviceType"];
|
url = url + "&serviceType="+ serviceType;
|
||||||
}else{
|
}else{
|
||||||
url = url + "?serviceType="+ sessionStorage["serviceType"];
|
url = url + "?serviceType="+ serviceType;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$("#topFrame").attr('src',url);
|
$("#topFrame").attr('src',url);
|
||||||
|
|||||||
@@ -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 = {
|
const StringMaskingUtil = {
|
||||||
|
|
||||||
@@ -8,41 +13,51 @@ const StringMaskingUtil = {
|
|||||||
return str != null && str !== '';
|
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) {
|
maskName: function(name) {
|
||||||
if (!this.isValidString(name)) {
|
if (!this.isValidString(name)) {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
return name.length > 1 ?
|
const len = name.length;
|
||||||
name.substring(0, name.length - 1) + "*" :
|
if (len === 1) {
|
||||||
name;
|
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) {
|
maskEmail: function(email) {
|
||||||
if (!this.isValidString(email) || !email.includes('@')) {
|
if (!this.isValidString(email) || !email.includes('@')) {
|
||||||
return email;
|
return email;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parts = email.split('@');
|
const atIdx = email.indexOf('@');
|
||||||
if (parts.length !== 2) {
|
const local = email.substring(0, atIdx);
|
||||||
return email;
|
const domain = email.substring(atIdx);
|
||||||
|
|
||||||
|
if (local.length <= 4) {
|
||||||
|
return this._stars(local.length) + domain;
|
||||||
}
|
}
|
||||||
|
return this._stars(2) + local.substring(2, local.length - 2) + this._stars(2) + 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];
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// 휴대폰 번호 마스킹 처리
|
// 휴대폰/전화 번호 마스킹 처리 (2·3번째 세그먼트 각각 뒤 2자리)
|
||||||
maskMobileNumber: function(number) {
|
maskMobileNumber: function(number) {
|
||||||
if (!this.isValidString(number)) {
|
if (!this.isValidString(number)) {
|
||||||
return number;
|
return number;
|
||||||
@@ -50,7 +65,9 @@ const StringMaskingUtil = {
|
|||||||
|
|
||||||
const parts = number.split('-');
|
const parts = number.split('-');
|
||||||
if (parts.length === 3) {
|
if (parts.length === 3) {
|
||||||
return parts[0] + '-****-' + parts[2];
|
return parts[0] + '-' +
|
||||||
|
this._maskSegmentTail(parts[1], 2) + '-' +
|
||||||
|
this._maskSegmentTail(parts[2], 2);
|
||||||
}
|
}
|
||||||
return number;
|
return number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -251,6 +251,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);
|
buttonControl(key);
|
||||||
titleControl(key);
|
titleControl(key);
|
||||||
});
|
});
|
||||||
@@ -289,12 +312,17 @@ var url_view = '<c:url value="/onl/admin/common/propertyMan.view" />';
|
|||||||
<table class="table_row" cellspacing="0">
|
<table class="table_row" cellspacing="0">
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width:20%;"><%= localeMessage.getString("propertyDetail.propertyKey") %></th>
|
<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> -->
|
<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>
|
<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>
|
||||||
<tr>
|
<tr>
|
||||||
<th><%= localeMessage.getString("propertyDetail.propertyValue") %></th>
|
<th style="width:20%;"><%= localeMessage.getString("propertyDetail.propertyValue") %></th>
|
||||||
<td><input type="text" name="prpty2Val"/></td>
|
<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>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<!-- grid -->
|
<!-- grid -->
|
||||||
|
|||||||
@@ -0,0 +1,211 @@
|
|||||||
|
<%@ 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>
|
||||||
|
</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>
|
||||||
@@ -20,7 +20,9 @@
|
|||||||
var combo;
|
var combo;
|
||||||
|
|
||||||
function formatNoticeType(cellvalue, options, rowObject) {
|
function formatNoticeType(cellvalue, options, rowObject) {
|
||||||
var name = "";
|
if (!combo || !combo.noticeTypeList) {
|
||||||
|
return cellvalue;
|
||||||
|
}
|
||||||
for (var i = 0; i < combo.noticeTypeList.length; i++) {
|
for (var i = 0; i < combo.noticeTypeList.length; i++) {
|
||||||
if (combo.noticeTypeList[i].CODE == cellvalue) {
|
if (combo.noticeTypeList[i].CODE == cellvalue) {
|
||||||
return combo.noticeTypeList[i].NAME;
|
return combo.noticeTypeList[i].NAME;
|
||||||
@@ -47,6 +49,37 @@
|
|||||||
return cellvalue + icon;
|
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(){
|
function init(){
|
||||||
$.ajax({
|
$.ajax({
|
||||||
@@ -60,6 +93,9 @@
|
|||||||
new makeOptions("CODE","NAME").setObj($("select[name=searchNoticeType]")).setNoValueInclude(true).setNoValue('','<%=localeMessage.getString("combo.all")%>').setData(json.noticeTypeList).rendering();
|
new makeOptions("CODE","NAME").setObj($("select[name=searchNoticeType]")).setNoValueInclude(true).setNoValue('','<%=localeMessage.getString("combo.all")%>').setData(json.noticeTypeList).rendering();
|
||||||
|
|
||||||
putSelectFromParam();
|
putSelectFromParam();
|
||||||
|
|
||||||
|
// 콤보(combo)가 채워진 뒤에 그리드를 생성해야 formatNoticeType 경합이 발생하지 않음
|
||||||
|
buildGrid();
|
||||||
},
|
},
|
||||||
error:function(e){
|
error:function(e){
|
||||||
alert(e.responseText);
|
alert(e.responseText);
|
||||||
@@ -67,7 +103,7 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(function() {
|
function buildGrid(){
|
||||||
|
|
||||||
$('#grid').jqGrid({
|
$('#grid').jqGrid({
|
||||||
datatype:"json",
|
datatype:"json",
|
||||||
@@ -103,6 +139,8 @@
|
|||||||
autowidth: true,
|
autowidth: true,
|
||||||
viewrecords: true,
|
viewrecords: true,
|
||||||
rowList : eval('[${rmsDefaultRowList}]'),
|
rowList : eval('[${rmsDefaultRowList}]'),
|
||||||
|
multiselect: true,
|
||||||
|
multiboxonly: true,
|
||||||
ondblClickRow: function(rowId) {
|
ondblClickRow: function(rowId) {
|
||||||
var rowData = $(this).getRowData(rowId);
|
var rowData = $(this).getRowData(rowId);
|
||||||
var id = rowData['id'];
|
var id = rowData['id'];
|
||||||
@@ -139,9 +177,13 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
init();
|
|
||||||
|
|
||||||
resizeJqGridWidth('grid','content_middle','1000');
|
resizeJqGridWidth('grid','content_middle','1000');
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
|
||||||
|
// 콤보 로드 → 콤보 성공 콜백에서 buildGrid() 호출 (경합 방지)
|
||||||
|
init();
|
||||||
|
|
||||||
$("#btn_search").click(function(){
|
$("#btn_search").click(function(){
|
||||||
var postData = getSearchForJqgrid("cmd","LIST");
|
var postData = getSearchForJqgrid("cmd","LIST");
|
||||||
@@ -157,6 +199,10 @@
|
|||||||
goNav(url2);
|
goNav(url2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("#btn_delete_selected").click(function(){
|
||||||
|
deleteSelectedRows();
|
||||||
|
});
|
||||||
|
|
||||||
$("select[name=searchUseYn], input[name^=search]").keydown(function(key){
|
$("select[name=searchUseYn], input[name^=search]").keydown(function(key){
|
||||||
if (key.keyCode == 13){
|
if (key.keyCode == 13){
|
||||||
$("#btn_search").click();
|
$("#btn_search").click();
|
||||||
@@ -179,6 +225,7 @@
|
|||||||
<div class="search_wrap">
|
<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_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_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>
|
||||||
<div class="title">공지사항 목록<span class="tooltip">공지사항을 관리합니다.</span></div>
|
<div class="title">공지사항 목록<span class="tooltip">공지사항을 관리합니다.</span></div>
|
||||||
<form id="ajaxForm" onsubmit="return false;">
|
<form id="ajaxForm" onsubmit="return false;">
|
||||||
|
|||||||
@@ -26,6 +26,37 @@ function formatFile(cellvalue, options, rowObject) {
|
|||||||
return cellvalue + icon;
|
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('삭제 중 오류가 발생했습니다.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
$('#grid').jqGrid({
|
$('#grid').jqGrid({
|
||||||
datatype:"json",
|
datatype:"json",
|
||||||
@@ -54,6 +85,8 @@ $(document).ready(function() {
|
|||||||
autowidth: true,
|
autowidth: true,
|
||||||
viewrecords: true,
|
viewrecords: true,
|
||||||
rowList : eval('[${rmsDefaultRowList}]'),
|
rowList : eval('[${rmsDefaultRowList}]'),
|
||||||
|
multiselect: true,
|
||||||
|
multiboxonly: true,
|
||||||
ondblClickRow: function(rowId) {
|
ondblClickRow: function(rowId) {
|
||||||
var rowData = $(this).getRowData(rowId);
|
var rowData = $(this).getRowData(rowId);
|
||||||
var id = rowData['id'];
|
var id = rowData['id'];
|
||||||
@@ -98,6 +131,10 @@ $(document).ready(function() {
|
|||||||
$("#grid").setGridParam({ url:url,postData: postData ,page:1 }).trigger("reloadGrid");
|
$("#grid").setGridParam({ url:url,postData: postData ,page:1 }).trigger("reloadGrid");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("#btn_delete_selected").click(function(){
|
||||||
|
deleteSelectedRows();
|
||||||
|
});
|
||||||
|
|
||||||
$("input[name^=search]").keydown(function(key){
|
$("input[name^=search]").keydown(function(key){
|
||||||
if (key.keyCode == 13){
|
if (key.keyCode == 13){
|
||||||
$("#btn_search").click();
|
$("#btn_search").click();
|
||||||
@@ -120,8 +157,9 @@ $(document).ready(function() {
|
|||||||
<div class="content_middle" id="content_middle">
|
<div class="content_middle" id="content_middle">
|
||||||
<div class="search_wrap">
|
<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_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>
|
||||||
<div class="title">사업제휴 신청 목록</div>
|
<div class="title">피드백/개선요청 신청 목록</div>
|
||||||
<form id="ajaxForm" onsubmit="return false;">
|
<form id="ajaxForm" onsubmit="return false;">
|
||||||
<table class="search_condition" cellspacing=0;>
|
<table class="search_condition" cellspacing=0;>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|||||||
@@ -92,6 +92,27 @@ $(document).ready(function() {
|
|||||||
goNav(returnUrl);//LIST로 이동
|
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 () {
|
$("#btn_unmask").click(function () {
|
||||||
const reason = prompt("마스킹 해제 사유를 입력하세요:");
|
const reason = prompt("마스킹 해제 사유를 입력하세요:");
|
||||||
|
|
||||||
@@ -134,9 +155,10 @@ $(document).ready(function() {
|
|||||||
<div class="content_middle">
|
<div class="content_middle">
|
||||||
<div class="search_wrap">
|
<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_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>
|
<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>
|
||||||
<div class="title">사업제휴 신청 상세</div>
|
<div class="title">피드백/개선요청 신청 상세</div>
|
||||||
|
|
||||||
<table id="grid" ></table>
|
<table id="grid" ></table>
|
||||||
<div id="pager"></div>
|
<div id="pager"></div>
|
||||||
|
|||||||
@@ -29,6 +29,16 @@
|
|||||||
// 원본 행 데이터를 ID로 조회하기 위한 맵
|
// 원본 행 데이터를 ID로 조회하기 위한 맵
|
||||||
var gridRowDataMap = {};
|
var gridRowDataMap = {};
|
||||||
|
|
||||||
|
// 휴대폰번호 중복 조회 모드 여부
|
||||||
|
var isDupView = false;
|
||||||
|
|
||||||
|
// 현재 검색조건 + 중복 조회 모드 플래그를 합쳐 LIST 요청 파라미터를 생성
|
||||||
|
function buildListPostData() {
|
||||||
|
var postData = getSearchForJqgrid("cmd", "LIST");
|
||||||
|
postData.onlyDuplicateMobile = isDupView;
|
||||||
|
return postData;
|
||||||
|
}
|
||||||
|
|
||||||
/* 선택된 사용자 삭제 - 상태에 따라 Soft/Hard Delete 자동 분기 */
|
/* 선택된 사용자 삭제 - 상태에 따라 Soft/Hard Delete 자동 분기 */
|
||||||
function deleteSelectedUsers() {
|
function deleteSelectedUsers() {
|
||||||
var selectedIds = $("#grid").jqGrid('getGridParam', 'selarrrow');
|
var selectedIds = $("#grid").jqGrid('getGridParam', 'selarrrow');
|
||||||
@@ -166,6 +176,13 @@
|
|||||||
|
|
||||||
$('#grid').jqGrid('setColProp', 'approvalStatus', {editoptions: {value: select_approvalStatus}});
|
$('#grid').jqGrid('setColProp', 'approvalStatus', {editoptions: {value: select_approvalStatus}});
|
||||||
$('#grid').jqGrid('setColProp', 'userStatus', {editoptions: {value: select_userStatus}});
|
$('#grid').jqGrid('setColProp', 'userStatus', {editoptions: {value: select_userStatus}});
|
||||||
|
|
||||||
|
// 휴대폰번호 중복 허용안함(기능 ON) + 중복 사용자 존재 시 배너 노출
|
||||||
|
if (json.mobileDuplicateCheckEnabled && json.mobileDuplicateUserCount > 0) {
|
||||||
|
$("#dupMobileCount").text(json.mobileDuplicateUserCount);
|
||||||
|
$("#dupMobileBanner").show();
|
||||||
|
}
|
||||||
|
|
||||||
$('#grid').trigger('reloadGrid');
|
$('#grid').trigger('reloadGrid');
|
||||||
|
|
||||||
},
|
},
|
||||||
@@ -176,7 +193,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
var gridPostData = getSearchForJqgrid("cmd", "LIST");
|
var gridPostData = buildListPostData();
|
||||||
|
|
||||||
$('#grid').jqGrid({
|
$('#grid').jqGrid({
|
||||||
datatype: "json",
|
datatype: "json",
|
||||||
@@ -188,7 +205,7 @@
|
|||||||
'<%= localeMessage.getString("portalUser.orgName") %>',
|
'<%= localeMessage.getString("portalUser.orgName") %>',
|
||||||
'<%= localeMessage.getString("portalUser.name") %>',
|
'<%= localeMessage.getString("portalUser.name") %>',
|
||||||
'<%= localeMessage.getString("portalUser.userId") %>',
|
'<%= localeMessage.getString("portalUser.userId") %>',
|
||||||
<%--'<%= localeMessage.getString("portalUser.mobile") %>',--%>
|
'<%= localeMessage.getString("portalUser.mobileNumber") %>',
|
||||||
'<%= localeMessage.getString("portalUser.roleName") %>',
|
'<%= localeMessage.getString("portalUser.roleName") %>',
|
||||||
'<%= localeMessage.getString("portalUser.userStatus") %>',
|
'<%= localeMessage.getString("portalUser.userStatus") %>',
|
||||||
'<%= localeMessage.getString("portalUser.createOn") %>',
|
'<%= localeMessage.getString("portalUser.createOn") %>',
|
||||||
@@ -201,7 +218,7 @@
|
|||||||
{name: 'portalOrgUIs.orgName', align: 'center', width: "120"},
|
{name: 'portalOrgUIs.orgName', align: 'center', width: "120"},
|
||||||
{name: 'userName', align: 'center', width: "80" },
|
{name: 'userName', align: 'center', width: "80" },
|
||||||
{name: 'loginId', align: 'center', width: "150" },
|
{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: 'roleCodeDescription', align: 'center', width: "80"},
|
||||||
{name: 'userStatusDescription', align: 'center', width: "50"},
|
{name: 'userStatusDescription', align: 'center', width: "50"},
|
||||||
{name: 'createdDate', align: 'center', width: "100", formatter: timeStampFormat},
|
{name: 'createdDate', align: 'center', width: "100", formatter: timeStampFormat},
|
||||||
@@ -274,9 +291,20 @@
|
|||||||
init();
|
init();
|
||||||
|
|
||||||
$("#btn_search").click(function () {
|
$("#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 () {
|
$("#btn_new").click(function () {
|
||||||
@@ -334,6 +362,11 @@
|
|||||||
</div>
|
</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;>
|
<table class="search_condition" cellspacing=0;>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -222,7 +222,7 @@
|
|||||||
$("input[name=searchStartDate], input[name=searchEndDate]").datepicker();
|
$("input[name=searchStartDate], input[name=searchEndDate]").datepicker();
|
||||||
|
|
||||||
var today = getToday();
|
var today = getToday();
|
||||||
var startDate = today;
|
var startDate = today.substring(0,6)+"01";
|
||||||
var endDate = today;
|
var endDate = today;
|
||||||
|
|
||||||
|
|
||||||
@@ -234,6 +234,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
list();
|
list();
|
||||||
|
search();
|
||||||
|
|
||||||
resizeJqGridWidth('grid', 'content_middle', '1200');
|
resizeJqGridWidth('grid', 'content_middle', '1200');
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -202,7 +202,7 @@ public class MainController implements InterceptorSkipController {
|
|||||||
|
|
||||||
// SMS 인증번호 생성 및 발송
|
// SMS 인증번호 생성 및 발송
|
||||||
String authCode = smsAuthService.generateAuthCode();
|
String authCode = smsAuthService.generateAuthCode();
|
||||||
boolean sent = smsAuthService.sendAuthCode(userInfo.getCphnno(), authCode);
|
boolean sent = smsAuthService.sendAuthCode(userInfo, authCode);
|
||||||
|
|
||||||
if (!sent) {
|
if (!sent) {
|
||||||
return LoginResponseDto.builder()
|
return LoginResponseDto.builder()
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ public class ClusteredSchedulerConfig {
|
|||||||
|
|
||||||
quartzProperties.put("org.quartz.jobStore.misfireThreshold", "60000");
|
quartzProperties.put("org.quartz.jobStore.misfireThreshold", "60000");
|
||||||
quartzProperties.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");
|
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.useProperties", "false");
|
||||||
quartzProperties.put("org.quartz.jobStore.dataSource", monitoringSchema);
|
quartzProperties.put("org.quartz.jobStore.dataSource", monitoringSchema);
|
||||||
quartzProperties.put("org.quartz.jobStore.tablePrefix", monitoringSchema + ".QRTZ_");
|
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 deleteAndAddJob(Scheduler scheduler, String jobName) throws SchedulerException, ClassNotFoundException;
|
||||||
|
|
||||||
|
void reloadJob(String jobName) throws SchedulerException, ClassNotFoundException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,12 +120,50 @@ 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) {
|
||||||
|
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 {
|
public void deleteJob(Scheduler inScheduler, String jobNm) throws SchedulerException {
|
||||||
|
String schedulerType = (inScheduler == clusteredScheduler) ? "clusteredScheduler" : "scheduler";
|
||||||
JobKey key = new JobKey(jobNm, Scheduler.DEFAULT_GROUP);
|
JobKey key = new JobKey(jobNm, Scheduler.DEFAULT_GROUP);
|
||||||
JobDetail jobDetail = inScheduler.getJobDetail(key);
|
JobDetail jobDetail = inScheduler.getJobDetail(key);
|
||||||
if (jobDetail != null) {
|
if (jobDetail != null) {
|
||||||
inScheduler.deleteJob(key);
|
inScheduler.deleteJob(key);
|
||||||
log.info(jobNm + " has been scheduled to deleted");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,10 +189,7 @@ public class EMSSchedulerImpl implements EMSScheduler {
|
|||||||
.withSchedule(cronSchedule(info.getCronExp()))
|
.withSchedule(cronSchedule(info.getCronExp()))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
Date ft = inScheduler.scheduleJob(job, trigger);
|
inScheduler.scheduleJob(job, trigger);
|
||||||
log
|
|
||||||
.info("{} has been scheduled to run at: {} and repeat based on expression: {}", job.getKey(), ft,
|
|
||||||
trigger.getCronExpression());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ public class SchedulerManController extends BaseAnnotationController {
|
|||||||
@Qualifier("monitoringSyncAgent")
|
@Qualifier("monitoringSyncAgent")
|
||||||
private MonitoringSyncAgent monitoringSyncAgent;
|
private MonitoringSyncAgent monitoringSyncAgent;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EMSScheduler emsScheduler;
|
||||||
|
|
||||||
@GetMapping(value = "/common/scheduler/schedulerMan.view")
|
@GetMapping(value = "/common/scheduler/schedulerMan.view")
|
||||||
public String viewList() {
|
public String viewList() {
|
||||||
return "/common/scheduler/schedulerMan";
|
return "/common/scheduler/schedulerMan";
|
||||||
@@ -96,6 +99,11 @@ public class SchedulerManController extends BaseAnnotationController {
|
|||||||
@PostMapping(value = "/common/scheduler/schedulerMan.json", params = "cmd=UPDATE")
|
@PostMapping(value = "/common/scheduler/schedulerMan.json", params = "cmd=UPDATE")
|
||||||
public ResponseEntity<Void> update(JobInfoUI jobInfoUI) {
|
public ResponseEntity<Void> update(JobInfoUI jobInfoUI) {
|
||||||
service.update(jobInfoUI);
|
service.update(jobInfoUI);
|
||||||
|
try {
|
||||||
|
emsScheduler.reloadJob(jobInfoUI.getId());
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("reloadJob error on current server", e);
|
||||||
|
}
|
||||||
syncReloadScheduler(jobInfoUI.getId());
|
syncReloadScheduler(jobInfoUI.getId());
|
||||||
return ResponseEntity.ok().build();
|
return ResponseEntity.ok().build();
|
||||||
}
|
}
|
||||||
@@ -115,7 +123,7 @@ public class SchedulerManController extends BaseAnnotationController {
|
|||||||
String url = "/monitoring/schedulerReload.do";
|
String url = "/monitoring/schedulerReload.do";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ret = monitoringSyncAgent.sync(url, paramMap);
|
ret = monitoringSyncAgent.targetSync(url, paramMap);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("syncReloadScheduler error", e);
|
logger.error("syncReloadScheduler error", e);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
+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> {
|
||||||
|
}
|
||||||
+65
@@ -0,0 +1,65 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 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.PageRequest;
|
||||||
import org.springframework.data.domain.Sort;
|
import org.springframework.data.domain.Sort;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Transactional(transactionManager = "transactionManagerForEMS")
|
@Transactional(transactionManager = "transactionManagerForEMS")
|
||||||
public class PortalNoticeService extends AbstractEMSDataSerivce<PortalNotice, String, PortalNoticeRepository> {
|
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) {
|
public Page<PortalNotice> findAll(Pageable pageable, PortalNoticeUISearch portalNoticeUISearch) {
|
||||||
QPortalNotice qPortalNotice = QPortalNotice.portalNotice;
|
QPortalNotice qPortalNotice = QPortalNotice.portalNotice;
|
||||||
|
|
||||||
|
|||||||
+6
@@ -10,10 +10,16 @@ import org.springframework.data.domain.Pageable;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Transactional(transactionManager = "transactionManagerForEMS")
|
@Transactional(transactionManager = "transactionManagerForEMS")
|
||||||
public class PortalPartnershipService extends AbstractEMSDataSerivce<PartnershipApplication, String, PortalPartnershipRepository> {
|
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) {
|
public Page<PartnershipApplication> findAll(Pageable pageable, String searchBizSubject, String searchBizDetail) {
|
||||||
QPartnershipApplication qPartnershipApplication = QPartnershipApplication.partnershipApplication;
|
QPartnershipApplication qPartnershipApplication = QPartnershipApplication.partnershipApplication;
|
||||||
|
|
||||||
|
|||||||
+37
@@ -8,12 +8,14 @@ import com.eactive.apim.portal.template.entity.QMessageRequest;
|
|||||||
import com.eactive.eai.rms.data.jpa.AbstractEMSDataSerivce;
|
import com.eactive.eai.rms.data.jpa.AbstractEMSDataSerivce;
|
||||||
import com.querydsl.core.BooleanBuilder;
|
import com.querydsl.core.BooleanBuilder;
|
||||||
import com.querydsl.core.types.dsl.BooleanExpression;
|
import com.querydsl.core.types.dsl.BooleanExpression;
|
||||||
|
import com.querydsl.jpa.JPAExpressions;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@@ -54,9 +56,40 @@ public class PortalUserService extends AbstractEMSDataSerivce<PortalUser, String
|
|||||||
predicate.and(qPortalUser.roleCode.eq(PortalUserEnums.RoleCode.valueOf(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);
|
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) {
|
public boolean existsByLoginId(String loginId) {
|
||||||
BooleanExpression predicate = QPortalUser.portalUser.loginId.eq(loginId);
|
BooleanExpression predicate = QPortalUser.portalUser.loginId.eq(loginId);
|
||||||
return repository.exists(predicate);
|
return repository.exists(predicate);
|
||||||
@@ -77,6 +110,10 @@ public class PortalUserService extends AbstractEMSDataSerivce<PortalUser, String
|
|||||||
}
|
}
|
||||||
|
|
||||||
public PortalUser findByUserId(String userId) {
|
public PortalUser findByUserId(String userId) {
|
||||||
|
if (StringUtils.isBlank(userId)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
QPortalUser qPortalUser = QPortalUser.portalUser;
|
QPortalUser qPortalUser = QPortalUser.portalUser;
|
||||||
|
|
||||||
return getJPAQueryFactory()
|
return getJPAQueryFactory()
|
||||||
|
|||||||
+3
@@ -23,4 +23,7 @@ public class PortalUserUISearch {
|
|||||||
|
|
||||||
private boolean includeRemoved;
|
private boolean includeRemoved;
|
||||||
|
|
||||||
|
/** true 인 경우 휴대폰번호가 중복(2명 이상 공유)되는 사용자만 조회 */
|
||||||
|
private boolean onlyDuplicateMobile;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,11 +39,10 @@ public interface ApiStatusRepository extends BaseRepository<ApiStatus, String> {
|
|||||||
+ " FROM ("
|
+ " FROM ("
|
||||||
+ " SELECT A.EAISVCNAME"
|
+ " SELECT A.EAISVCNAME"
|
||||||
+ " , NVL(B.STATUS_CODE, 'N') AS STATUS_CODE"
|
+ " , NVL(B.STATUS_CODE, 'N') AS STATUS_CODE"
|
||||||
//TODO: 게시판 임시로 생성하여 사용. 유차장이 완료하면 변경할것
|
+ " , NVL(( SELECT MAX('Y') FROM EMSAPP.DJB_APISTATUS_INCIDENT_API X"
|
||||||
+ " , NVL(( SELECT 'Y' FROM PTL_NOTICE_API X "
|
+ " WHERE EXISTS (SELECT 1 FROM EMSAPP.DJB_APISTATUS_INCIDENT Y WHERE X.INCIDENT_ID = Y.INCIDENT_ID "
|
||||||
+ " WHERE EXISTS (SELECT 1 FROM EMSAPP.PTL_NOTICE Y WHERE X.NOTICE_ID = Y.ID)"
|
+ " AND SYSTIMESTAMP BETWEEN STARTED_AT AND END_AT) "
|
||||||
//+ " AND TO_CHAR(SYSDATE, 'YYYYMMDDHH24MI') BETWEEN START_TIME AND END_TIME) "
|
+ " AND A.EAISVCNAME = X.API_ID),'N') AS CTRL_YN "
|
||||||
+ " AND A.EAISVCNAME = X.API_NAME),'N') AS CTRL_YN "
|
|
||||||
+ " , (SELECT CASE WHEN TOTAL = 0 THEN 'X'"
|
+ " , (SELECT CASE WHEN TOTAL = 0 THEN 'X'"
|
||||||
+ " WHEN (TOTAL - SUCCESS) * 100 / TOTAL > :errorRate THEN 'Y'"
|
+ " WHEN (TOTAL - SUCCESS) * 100 / TOTAL > :errorRate THEN 'Y'"
|
||||||
+ " ELSE 'N' END"
|
+ " ELSE 'N' END"
|
||||||
|
|||||||
@@ -55,7 +55,8 @@ public class ApiStatusService {
|
|||||||
apiStatus.setEaisvcname(event.getEaisvcname());
|
apiStatus.setEaisvcname(event.getEaisvcname());
|
||||||
apiStatus.setStatusCode(newStatusCode);
|
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);
|
log.debug("API 상태 변경: {}-{} {} → {}", event.getEaisvcname(), event.getEaisvcdesc(), event.getEvent(), newStatusCode);
|
||||||
|
|
||||||
|
|
||||||
@@ -75,8 +76,8 @@ public class ApiStatusService {
|
|||||||
String event = entry.getKey();
|
String event = entry.getKey();
|
||||||
List<String> apiIds = entry.getValue();
|
List<String> apiIds = entry.getValue();
|
||||||
|
|
||||||
String message = getSwingChatMessage(event, apiIds);
|
HashMap<String, Object> params = getSwingChatMessage(event, apiIds);
|
||||||
ums.sendMessenger("api-monitor", MessageCode.API_STATUS_CHANGED, message);
|
ums.send("api-monitor", MessageCode.API_STATUS_CHANGED, params);
|
||||||
|
|
||||||
//제휴사 웹훅 발송
|
//제휴사 웹훅 발송
|
||||||
ums.sendWebhook(event, apiIds);
|
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 = "";
|
String message = "";
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case "CONTROL_START":
|
case "CONTROL_START":
|
||||||
@@ -127,7 +128,16 @@ public class ApiStatusService {
|
|||||||
message = "";
|
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;
|
package com.eactive.eai.rms.ext.djb.inflow;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -9,7 +10,6 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import com.eactive.apim.portal.template.entity.MessageCode;
|
import com.eactive.apim.portal.template.entity.MessageCode;
|
||||||
import com.eactive.eai.rms.data.entity.man.role.Role;
|
|
||||||
import com.eactive.eai.rms.data.entity.onl.djb.inflow.InflowTokenInsufficient;
|
import com.eactive.eai.rms.data.entity.onl.djb.inflow.InflowTokenInsufficient;
|
||||||
import com.eactive.eai.rms.ext.djb.ums.UmsManager;
|
import com.eactive.eai.rms.ext.djb.ums.UmsManager;
|
||||||
|
|
||||||
@@ -32,8 +32,9 @@ public class InflowTokenService {
|
|||||||
for (InflowTokenInsufficient info : rows) {
|
for (InflowTokenInsufficient info : rows) {
|
||||||
log.debug("유량제어 토큰 획득 실패: {}-{} 최근 {}분 동안 {}건", info.getEaisvcname(), info.getEaisvcdesc(), rangeMinute, info.getCnt());
|
log.debug("유량제어 토큰 획득 실패: {}-{} 최근 {}분 동안 {}건", info.getEaisvcname(), info.getEaisvcdesc(), rangeMinute, info.getCnt());
|
||||||
String message = "유량제어 토큰 획득 실패하였습니다 \n" + info.getEaisvcname();
|
String message = "유량제어 토큰 획득 실패하였습니다 \n" + info.getEaisvcname();
|
||||||
|
HashMap<String,Object> params = new HashMap<String,Object>();
|
||||||
ums.sendMessenger("api-monitor", MessageCode.INFLOW_TOKEN_FAILED, message);
|
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;
|
package com.eactive.eai.rms.ext.djb.job;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.quartz.DisallowConcurrentExecution;
|
import org.quartz.DisallowConcurrentExecution;
|
||||||
@@ -68,10 +71,24 @@ public class ApiStatusMonitorJob implements Job {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(JobExecutionContext context) throws JobExecutionException {
|
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"));
|
log.info("*** START ApiStatusUpdateJob run({})", CommonUtil.getToday("yyyy-MM-dd HH:mm"));
|
||||||
|
|
||||||
// Job 파라미터 로깅
|
// Job 파라미터 로깅
|
||||||
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
|
|
||||||
logJobParameters(jobDataMap);
|
logJobParameters(jobDataMap);
|
||||||
|
|
||||||
HashMap<String, String> param = this.checkParameters(jobDataMap);
|
HashMap<String, String> param = this.checkParameters(jobDataMap);
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
package com.eactive.eai.rms.ext.djb.job;
|
package com.eactive.eai.rms.ext.djb.job;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.quartz.DisallowConcurrentExecution;
|
import org.quartz.DisallowConcurrentExecution;
|
||||||
import org.quartz.Job;
|
import org.quartz.Job;
|
||||||
import org.quartz.JobDataMap;
|
import org.quartz.JobDataMap;
|
||||||
@@ -48,6 +52,21 @@ public class InflowTokenMonitorJob implements Job {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(JobExecutionContext context) throws JobExecutionException {
|
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"));
|
log.info("*** START InflowTokenFailMonitorJob run({})", CommonUtil.getToday("yyyy-MM-dd HH:mm"));
|
||||||
|
|
||||||
long execMinute = 1; //default 배치 실행주기(분)
|
long execMinute = 1; //default 배치 실행주기(분)
|
||||||
|
|||||||
@@ -5,14 +5,20 @@ import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
|
|||||||
import com.eactive.eai.rms.common.util.CommonUtil;
|
import com.eactive.eai.rms.common.util.CommonUtil;
|
||||||
import com.eactive.eai.rms.onl.apim.portalInquiry.PortalInquiryClosingService;
|
import com.eactive.eai.rms.onl.apim.portalInquiry.PortalInquiryClosingService;
|
||||||
import com.eactive.eai.rms.onl.common.util.DateUtil;
|
import com.eactive.eai.rms.onl.common.util.DateUtil;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.quartz.DisallowConcurrentExecution;
|
import org.quartz.DisallowConcurrentExecution;
|
||||||
import org.quartz.Job;
|
import org.quartz.Job;
|
||||||
|
import org.quartz.JobDataMap;
|
||||||
import org.quartz.JobExecutionContext;
|
import org.quartz.JobExecutionContext;
|
||||||
import org.quartz.JobExecutionException;
|
import org.quartz.JobExecutionException;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 매일 00:05 실행 - RESPONDED 상태 문의글 자동 종료 배치
|
* 매일 00:05 실행 - RESPONDED 상태 문의글 자동 종료 배치
|
||||||
*
|
*
|
||||||
@@ -44,6 +50,21 @@ public class PortalInquiryClosingJob implements Job {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(JobExecutionContext context) throws JobExecutionException {
|
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"));
|
log.info("*** START PortalInquiryCommentClosingService run({})", CommonUtil.getToday("yyyy-MM-dd HH:mm"));
|
||||||
|
|
||||||
int closingDays = DEFAULT_CLOSING_DAYS;
|
int closingDays = DEFAULT_CLOSING_DAYS;
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ public class ApiUseStatsService
|
|||||||
+ " ORDER BY ORGNAME";
|
+ " ORDER BY ORGNAME";
|
||||||
} else if ("API".equals(search.getSearchType())) {
|
} else if ("API".equals(search.getSearchType())) {
|
||||||
dataSql = "SELECT COUNT(*) OVER () AS RECORDS "
|
dataSql = "SELECT COUNT(*) OVER () AS RECORDS "
|
||||||
+ ", C.EAISVCDESC"
|
+ ", A.API_NAME"
|
||||||
+ ", SUM(A.TOTAL_CNT)"
|
+ ", SUM(A.TOTAL_CNT)"
|
||||||
+ ", SUM(A.SUCCESS_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"
|
+ ", 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 TSEAIAU01 B ON A.CLIENT_ID = B.CLIENTID"
|
||||||
+ " LEFT OUTER JOIN TSEAIHE01 C ON A.API_NAME = C.EAISVCNAME "
|
+ " LEFT OUTER JOIN TSEAIHE01 C ON A.API_NAME = C.EAISVCNAME "
|
||||||
+ where
|
+ where
|
||||||
+ " GROUP BY C.EAISVCDESC"
|
+ " GROUP BY A.API_NAME"
|
||||||
+ " ORDER BY EAISVCDESC";
|
+ " ORDER BY API_NAME";
|
||||||
} else if ("DATE".equals(search.getSearchType())) {
|
} else if ("DATE".equals(search.getSearchType())) {
|
||||||
dataSql = "SELECT COUNT(*) OVER () AS RECORDS "
|
dataSql = "SELECT COUNT(*) OVER () AS RECORDS "
|
||||||
+ ", TO_CHAR(STAT_TIME,'YYYY-MM-DD') AS STAT_TIME "
|
+ ", TO_CHAR(STAT_TIME,'YYYY-MM-DD') AS STAT_TIME "
|
||||||
|
|||||||
@@ -29,14 +29,13 @@ public class UmsManager {
|
|||||||
private final WebhookService webhookService;
|
private final WebhookService webhookService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* role 역할을 가진 내부직원에게 메신저(Swing chat) 발송
|
* roleId에 해당하는 역할을 가진 직원에게 UMS(sms,email,messenger) 발송
|
||||||
* @param role
|
* @param role
|
||||||
* @param messageCode
|
* @param messageCode
|
||||||
* @param message
|
* @param params
|
||||||
* @return
|
* @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);
|
List<UserInfo> users = userInfoService.findAllByRoleId(roleId);
|
||||||
if (users.isEmpty()) {
|
if (users.isEmpty()) {
|
||||||
log.info("sendMessenger: no users found for role '{}'", roleId);
|
log.info("sendMessenger: no users found for role '{}'", roleId);
|
||||||
@@ -44,33 +43,33 @@ public class UmsManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (UserInfo user : users) {
|
for (UserInfo user : users) {
|
||||||
|
String cleanedPhone = user.getCphnno().replaceAll("[^0-9]", "").trim();
|
||||||
MessageRecipient recipient = MessageRecipient.builder()
|
MessageRecipient recipient = MessageRecipient.builder()
|
||||||
.userId(user.getUserid())
|
.userId(user.getUserid())
|
||||||
.username(user.getUsername())
|
.username(user.getUsername())
|
||||||
|
.email(user.getEmad())
|
||||||
|
.phone(cleanedPhone)
|
||||||
|
.messengerId(user.getUserid())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
HashMap<String, Object> params = new HashMap<>();
|
|
||||||
params.put("message", message);
|
|
||||||
|
|
||||||
messageHandlerService.publishEvent(messageCode, recipient, params);
|
messageHandlerService.publishEvent(messageCode, recipient, params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 내부직원에게 메신저 발송
|
* 직원 또는 회원에게 UMS(sms,email,messenger) 발송
|
||||||
* @param user
|
* @param user
|
||||||
* @param messageCode
|
* @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()
|
MessageRecipient recipient = MessageRecipient.builder()
|
||||||
.userId(user.getUserid())
|
.userId(user.getUserid())
|
||||||
.username(user.getUsername())
|
.username(user.getUsername())
|
||||||
|
.email(user.getEmad())
|
||||||
|
.phone(cleanedPhone)
|
||||||
|
.messengerId(user.getUserid())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
HashMap<String, Object> params = new HashMap<>();
|
|
||||||
params.put("message", message);
|
|
||||||
|
|
||||||
messageHandlerService.publishEvent(messageCode, recipient, params);
|
messageHandlerService.publishEvent(messageCode, recipient, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ public class SmsAuthController implements InterceptorSkipController {
|
|||||||
|
|
||||||
// 인증번호 생성 및 발송
|
// 인증번호 생성 및 발송
|
||||||
String authCode = getSmsAuthService().generateAuthCode();
|
String authCode = getSmsAuthService().generateAuthCode();
|
||||||
boolean sent = getSmsAuthService().sendAuthCode(userInfo.getCphnno(), authCode);
|
boolean sent = getSmsAuthService().sendAuthCode(userInfo, authCode);
|
||||||
|
|
||||||
if (sent) {
|
if (sent) {
|
||||||
// 세션에 인증번호 저장 (기존 UserInfo, LoginVo 유지)
|
// 세션에 인증번호 저장 (기존 UserInfo, LoginVo 유지)
|
||||||
@@ -106,7 +106,7 @@ public class SmsAuthController implements InterceptorSkipController {
|
|||||||
|
|
||||||
// 새 인증번호 생성 및 발송
|
// 새 인증번호 생성 및 발송
|
||||||
String authCode = getSmsAuthService().generateAuthCode();
|
String authCode = getSmsAuthService().generateAuthCode();
|
||||||
boolean sent = getSmsAuthService().sendAuthCode(userInfo.getCphnno(), authCode);
|
boolean sent = getSmsAuthService().sendAuthCode(userInfo, authCode);
|
||||||
|
|
||||||
if (sent) {
|
if (sent) {
|
||||||
LoginVo loginVo = getSmsAuthService().getLoginVoFromSession(session);
|
LoginVo loginVo = getSmsAuthService().getLoginVoFromSession(session);
|
||||||
|
|||||||
@@ -1,22 +1,25 @@
|
|||||||
package com.eactive.eai.rms.ext.kjb.smsauth;
|
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.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
import com.eactive.apim.portal.template.entity.MessageCode;
|
||||||
import com.eactive.apim.portal.user.entity.UserInfo;
|
import com.eactive.apim.portal.user.entity.UserInfo;
|
||||||
import com.eactive.eai.rms.common.login.LoginVo;
|
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.KjbProperty;
|
||||||
import com.eactive.ext.kjb.common.KjbPropertyHolder;
|
import com.eactive.ext.kjb.common.KjbPropertyHolder;
|
||||||
import com.eactive.ext.kjb.common.KjbUmsException;
|
import com.eactive.ext.kjb.common.KjbUmsException;
|
||||||
import com.eactive.ext.kjb.ums.KjbUmsService;
|
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 lombok.extern.slf4j.Slf4j;
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class SmsAuthService {
|
public class SmsAuthService {
|
||||||
@@ -39,6 +42,9 @@ public class SmsAuthService {
|
|||||||
|
|
||||||
private final SecureRandom random = new SecureRandom();
|
private final SecureRandom random = new SecureRandom();
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UmsManager ums;
|
||||||
|
|
||||||
private SmsAuthService() {
|
private SmsAuthService() {
|
||||||
// 싱글톤
|
// 싱글톤
|
||||||
}
|
}
|
||||||
@@ -116,20 +122,25 @@ public class SmsAuthService {
|
|||||||
/**
|
/**
|
||||||
* SMS 인증번호 발송
|
* SMS 인증번호 발송
|
||||||
*/
|
*/
|
||||||
public boolean sendAuthCode(String phone, String authCode) {
|
public boolean sendAuthCode(UserInfo userInfo, String authCode) {
|
||||||
try {
|
try {
|
||||||
String cleanedPhone = phone.replaceAll("[^0-9]", "").trim();
|
// String cleanedPhone = phone.replaceAll("[^0-9]", "").trim();
|
||||||
String guid = String.format("EAPIM_EMS-%s",
|
// String guid = String.format("EAPIM_EMS-%s",
|
||||||
DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS").format(LocalDateTime.now()));
|
// 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);
|
||||||
|
|
||||||
JsonObject content = GsonUtil.toJsonObject(VerifyPhoneUmsTemplate.builder().authNumber(authCode).build());
|
HashMap<String,Object> params = new HashMap<String, Object>();
|
||||||
|
params.put("authNumber", authCode);
|
||||||
|
|
||||||
getUmsService().sendKakaoAlimTalk(guid, UmsBizWorkCode.VERIFY_PHONE, cleanedPhone, content);
|
ums.send(userInfo, MessageCode.USER_VERIFICATION_MOBILEPHONE, params);
|
||||||
log.info("SMS 인증번호 발송 완료 - guid: {}, phone: {}****{}",
|
|
||||||
guid, cleanedPhone.substring(0, 3), cleanedPhone.substring(cleanedPhone.length() - 4));
|
|
||||||
log.debug("SMS 인증번호 발송 - authCode: {}", authCode);
|
|
||||||
return true;
|
return true;
|
||||||
} catch (KjbUmsException e) {
|
} catch (Exception e) {
|
||||||
log.error("SMS 인증번호 발송 실패", e);
|
log.error("SMS 인증번호 발송 실패", e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -267,17 +278,14 @@ public class SmsAuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 전화번호 마스킹
|
* 전화번호 마스킹 (표준 규칙: 2·3번째 세그먼트 각각 뒤 2자리)
|
||||||
|
* 예: 010-1234-5678 → 010-12**-56**
|
||||||
*/
|
*/
|
||||||
public String maskPhoneNumber(String phone) {
|
public String maskPhoneNumber(String phone) {
|
||||||
if (StringUtils.isEmpty(phone)) {
|
if (StringUtils.isEmpty(phone)) {
|
||||||
return "****";
|
return "****";
|
||||||
}
|
}
|
||||||
String cleaned = phone.replaceAll("[^0-9]", "");
|
return com.eactive.eai.common.util.MaskingUtils.maskPhoneNumber(phone);
|
||||||
if (cleaned.length() < 7) {
|
|
||||||
return "****";
|
|
||||||
}
|
|
||||||
return cleaned.substring(0, 3) + "****" + cleaned.substring(cleaned.length() - 4);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -134,8 +134,8 @@ public class SsoController implements InterceptorSkipController {
|
|||||||
log.debug("SSO 사용자 정보 - userId: {}, name: {}, phone: {}, email: {}",
|
log.debug("SSO 사용자 정보 - userId: {}, name: {}, phone: {}, email: {}",
|
||||||
userId,
|
userId,
|
||||||
MaskingUtils.maskName(name),
|
MaskingUtils.maskName(name),
|
||||||
maskPhoneNumber(cellPhoneNo),
|
MaskingUtils.maskPhoneNumber(cellPhoneNo),
|
||||||
maskEmail(email));
|
MaskingUtils.maskEmailId(email));
|
||||||
|
|
||||||
// 사용자 생성 또는 업데이트
|
// 사용자 생성 또는 업데이트
|
||||||
UserInfo userInfo = findOrCreateUser(info, extendedInfo);
|
UserInfo userInfo = findOrCreateUser(info, extendedInfo);
|
||||||
@@ -341,33 +341,4 @@ public class SsoController implements InterceptorSkipController {
|
|||||||
}
|
}
|
||||||
return phone.replaceAll("[^0-9]", "");
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,83 +1,52 @@
|
|||||||
package com.eactive.eai.rms.onl.apim.masking;
|
package com.eactive.eai.rms.onl.apim.masking;
|
||||||
|
|
||||||
import com.eactive.eai.rms.common.util.StringUtils;
|
/**
|
||||||
|
* 개인정보 마스킹 유틸리티 (eapim-admin)
|
||||||
import java.util.Arrays;
|
*
|
||||||
import java.util.List;
|
* <p>실제 마스킹 규칙은 보안아키텍처 표준 구현인
|
||||||
import java.util.stream.Collectors;
|
* {@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 {
|
public class MaskingUtils {
|
||||||
|
|
||||||
|
private MaskingUtils() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 이메일 주소의 ID 부분을 마스킹 처리
|
* 이메일 주소의 ID 부분을 마스킹 처리
|
||||||
* 예: eactive@gmail.com => eact***@gmail.com
|
* 예: aajjbacc@gmail.com => **jjba**@gmail.com
|
||||||
*/
|
*/
|
||||||
public static String maskEmailId(String email) {
|
public static String maskEmailId(String email) {
|
||||||
if (email == null || email.isEmpty() || !email.contains("@")) {
|
return com.eactive.eai.common.util.MaskingUtils.maskEmail(email);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 이름의 마지막 글자를 마스킹 처리
|
* 이름 마스킹 처리 (2자: 뒤 1자리, 3자 이상: 앞뒤 제외 가운데 전체)
|
||||||
|
* 예: 홍길동 => 홍*동
|
||||||
*/
|
*/
|
||||||
public static String maskName(String name) {
|
public static String maskName(String name) {
|
||||||
if (name == null || name.isEmpty() || name.length() < 2) {
|
return com.eactive.eai.common.util.MaskingUtils.maskKoreanName(name);
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
return name.substring(0, name.length() - 1) + "*";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 전화번호 마스킹 처리
|
* 전화번호/휴대폰/FAX 마스킹 처리 (2·3번째 세그먼트 각각 뒤 2자리)
|
||||||
* 예: 010-1234-5678 => 010-****-5678
|
* 예: 010-1234-5678 => 010-12**-56**
|
||||||
*/
|
*/
|
||||||
public static String maskPhoneNumber(String phoneNumber) {
|
public static String maskPhoneNumber(String phoneNumber) {
|
||||||
if (phoneNumber == null || phoneNumber.isEmpty()) {
|
return com.eactive.eai.common.util.MaskingUtils.maskPhoneNumber(phoneNumber);
|
||||||
return phoneNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
String[] parts = phoneNumber.split("-");
|
|
||||||
if (parts.length != 3) {
|
|
||||||
return phoneNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
return parts[0] + "-****-" + parts[2];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IP 주소 마스킹 처리
|
* IP 주소 마스킹 처리 (IPv4 3번째 옥텟 / IPv6 마지막 그룹, 콤마 다중 IP 지원)
|
||||||
* 예: 192.168.1.1 => 192.168.*.*
|
* 예: 192.168.1.1 => 192.168.***.1
|
||||||
*/
|
*/
|
||||||
public static String maskIpAddress(String ipAddresses) {
|
public static String maskIpAddress(String ipAddresses) {
|
||||||
if (StringUtils.isBlank(ipAddresses)) {
|
return com.eactive.eai.common.util.MaskingUtils.maskIpAddress(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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.eactive.eai.rms.onl.apim.messagerequest;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 발송 내역(PTL_MESSAGE_REQUEST) 화면 응답 DTO.
|
||||||
|
*
|
||||||
|
* <p>개인정보는 모두 마스킹된 값으로 채워지며, 본문(messageHtml)은 패턴 마스킹 + 색상강조가 적용된
|
||||||
|
* 안전한 HTML 이다. 날짜는 표시용(short)/툴팁용(full) 두 가지 포맷 문자열로 제공한다.</p>
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class MessageRequestUI {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/* 제목/코드 — subject 가 없으면 화면에서 messageCode 로 대체 표기 */
|
||||||
|
private String messageCode; // MessageCode enum name (제목 마우스오버/대체용)
|
||||||
|
private String messageCodeName; // MessageCode.getDescription() 한글명
|
||||||
|
private String subject;
|
||||||
|
private String messageType; // EMAIL/SMS 등
|
||||||
|
|
||||||
|
/* 본문 — 패턴 마스킹 + span 색상강조된 안전 HTML */
|
||||||
|
private String messageHtml;
|
||||||
|
|
||||||
|
/* 수신자 (마스킹) */
|
||||||
|
private String username;
|
||||||
|
private String email;
|
||||||
|
private String phone;
|
||||||
|
private String messengerId;
|
||||||
|
|
||||||
|
/* 발송 메타 */
|
||||||
|
private String umsUid;
|
||||||
|
private String requestStatus; // PENDING/SENT/FAILED
|
||||||
|
private String eaiInterfaceId;
|
||||||
|
private String serviceId;
|
||||||
|
|
||||||
|
/* 요청/발송 시간 — 표시용(short) / 툴팁용(full) */
|
||||||
|
private String requestDateShort; // MM-dd(E) HH:mm
|
||||||
|
private String requestDateFull; // yyyy-MM-dd (EEE) HH:mm:ss
|
||||||
|
private String sentDateShort;
|
||||||
|
private String sentDateFull;
|
||||||
|
}
|
||||||
@@ -80,4 +80,10 @@ public class PortalNoticeManController extends BaseAnnotationController {
|
|||||||
return ResponseEntity.ok().build();
|
return ResponseEntity.ok().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping(value = "/onl/apim/portalnotice/portalNoticeMan.json", params = "cmd=DELETE_BULK")
|
||||||
|
public ResponseEntity<String> deleteBulk(String ids) {
|
||||||
|
portalNoticeManService.deleteBulk(ids);
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import java.nio.charset.Charset;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -282,4 +283,20 @@ public class PortalNoticeManService extends BaseService {
|
|||||||
portalNoticeService.deleteById(id);
|
portalNoticeService.deleteById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void deleteBulk(String ids) {
|
||||||
|
if (StringUtils.isBlank(ids)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> idList = Arrays.asList(ids.split(","));
|
||||||
|
|
||||||
|
// 첨부파일 정리 (단건 삭제와 동일한 처리)
|
||||||
|
idList.forEach(id -> {
|
||||||
|
PortalNotice portalNotice = portalNoticeService.getById(id);
|
||||||
|
Optional.ofNullable(portalNotice.getFileId()).ifPresent(fileService::deleteFile);
|
||||||
|
});
|
||||||
|
|
||||||
|
portalNoticeService.deleteByIds(idList);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+12
@@ -55,4 +55,16 @@ public class PortalPartnershipManController extends BaseAnnotationController {
|
|||||||
return ResponseEntity.ok(portalPartnershipUI);
|
return ResponseEntity.ok(portalPartnershipUI);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping(value = "/onl/apim/portalpartnership/portalPartnershipMan.json", params = "cmd=DELETE")
|
||||||
|
public ResponseEntity<Void> delete(String id) {
|
||||||
|
portalPartnershipManService.delete(id);
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(value = "/onl/apim/portalpartnership/portalPartnershipMan.json", params = "cmd=DELETE_BULK")
|
||||||
|
public ResponseEntity<Void> deleteBulk(String ids) {
|
||||||
|
portalPartnershipManService.deleteBulk(ids);
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+38
-4
@@ -15,6 +15,10 @@ import org.springframework.data.domain.Pageable;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Transactional(transactionManager = "transactionManagerForEMS")
|
@Transactional(transactionManager = "transactionManagerForEMS")
|
||||||
public class PortalPartnershipManService extends BaseService {
|
public class PortalPartnershipManService extends BaseService {
|
||||||
@@ -51,18 +55,48 @@ public class PortalPartnershipManService extends BaseService {
|
|||||||
return convertToUIWithMaskOption(partnershipApplication, false);
|
return convertToUIWithMaskOption(partnershipApplication, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void delete(String id) {
|
||||||
|
PartnershipApplication partnership = partnershipService.getById(id);
|
||||||
|
Optional.ofNullable(partnership.getFileId())
|
||||||
|
.filter(StringUtils::isNotBlank)
|
||||||
|
.ifPresent(fileService::deleteFile);
|
||||||
|
partnershipService.deleteById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteBulk(String ids) {
|
||||||
|
if (StringUtils.isBlank(ids)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> idList = Arrays.asList(ids.split(","));
|
||||||
|
|
||||||
|
// 첨부파일 정리 (단건 삭제와 동일한 처리)
|
||||||
|
idList.forEach(id -> {
|
||||||
|
PartnershipApplication partnership = partnershipService.getById(id);
|
||||||
|
Optional.ofNullable(partnership.getFileId())
|
||||||
|
.filter(StringUtils::isNotBlank)
|
||||||
|
.ifPresent(fileService::deleteFile);
|
||||||
|
});
|
||||||
|
|
||||||
|
partnershipService.deleteByIds(idList);
|
||||||
|
}
|
||||||
|
|
||||||
private PortalPartnershipUI convertToUIWithMaskOption(PartnershipApplication partnership, boolean isMasked) {
|
private PortalPartnershipUI convertToUIWithMaskOption(PartnershipApplication partnership, boolean isMasked) {
|
||||||
PortalPartnershipUI partnershipUI = portalPartnershipUIMapper.toVo(partnership);
|
PortalPartnershipUI partnershipUI = portalPartnershipUIMapper.toVo(partnership);
|
||||||
PortalUser user = findUser(partnership.getCreatedBy());
|
PortalUser user = findUser(partnership.getCreatedBy());
|
||||||
|
|
||||||
|
// 작성자(createdBy)에 해당하는 포털 사용자가 없을 수 있음(탈퇴/삭제, 시스템 작성 등)
|
||||||
|
String userName = user != null ? user.getUserName() : null;
|
||||||
|
String loginId = user != null ? user.getLoginId() : null;
|
||||||
|
|
||||||
if (isMasked) {
|
if (isMasked) {
|
||||||
// 마스킹 처리
|
// 마스킹 처리
|
||||||
partnershipUI.setCreatedByName(MaskingUtils.maskName(user.getUserName()));
|
partnershipUI.setCreatedByName(MaskingUtils.maskName(userName));
|
||||||
partnershipUI.setCreatedById(MaskingUtils.maskEmailId(user.getLoginId()));
|
partnershipUI.setCreatedById(MaskingUtils.maskEmailId(loginId));
|
||||||
} else {
|
} else {
|
||||||
// 마스킹 해제 상태
|
// 마스킹 해제 상태
|
||||||
partnershipUI.setCreatedByName(user.getUserName());
|
partnershipUI.setCreatedByName(userName);
|
||||||
partnershipUI.setCreatedById(user.getLoginId());
|
partnershipUI.setCreatedById(loginId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 첨부파일 처리
|
// 첨부파일 처리
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.eactive.apim.portal.agreements.service.AgreementsService;
|
|||||||
import com.eactive.apim.portal.file.entity.FileDetail;
|
import com.eactive.apim.portal.file.entity.FileDetail;
|
||||||
import com.eactive.apim.portal.file.entity.FileInfo;
|
import com.eactive.apim.portal.file.entity.FileInfo;
|
||||||
import com.eactive.apim.portal.file.service.FileService;
|
import com.eactive.apim.portal.file.service.FileService;
|
||||||
|
import com.eactive.apim.portal.user.entity.UserInfo;
|
||||||
import com.eactive.eai.rms.common.base.BaseService;
|
import com.eactive.eai.rms.common.base.BaseService;
|
||||||
import com.eactive.eai.rms.data.entity.man.user.UserInfoService;
|
import com.eactive.eai.rms.data.entity.man.user.UserInfoService;
|
||||||
import com.eactive.eai.rms.data.entity.onl.apim.portalterms.PortalTermsService;
|
import com.eactive.eai.rms.data.entity.onl.apim.portalterms.PortalTermsService;
|
||||||
@@ -49,7 +50,14 @@ public class PortalTermsManService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String findName(String userId) {
|
private String findName(String userId) {
|
||||||
return userInfoService.getById(userId).getUsername();
|
if (StringUtils.isBlank(userId)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
// 등록자/수정자 ID로 사용자를 찾지 못해도(삭제된 사용자, 레거시 시드 데이터 등)
|
||||||
|
// 화면 전체가 깨지지 않도록 방어적으로 조회한다. (PortalFaq/PortalInquiry 컨벤션과 동일)
|
||||||
|
return userInfoService.findById(userId)
|
||||||
|
.map(UserInfo::getUsername)
|
||||||
|
.orElse("Unknown");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Page<PortalTermsUI> selectList(Pageable pageable, String searchAgreementsType) {
|
public Page<PortalTermsUI> selectList(Pageable pageable, String searchAgreementsType) {
|
||||||
|
|||||||
@@ -102,6 +102,12 @@ public class PortalUserManController extends BaseAnnotationController {
|
|||||||
resultMap.put("userStatusRows", userStatusList);
|
resultMap.put("userStatusRows", userStatusList);
|
||||||
resultMap.put("roleCodeRows", roleCodeList);
|
resultMap.put("roleCodeRows", roleCodeList);
|
||||||
resultMap.put("isProdServer", SystemUtil.isProdServer());
|
resultMap.put("isProdServer", SystemUtil.isProdServer());
|
||||||
|
|
||||||
|
// 휴대폰번호 중복 허용안함 여부 및 중복 사용자 수 (배너 노출용)
|
||||||
|
Map<String, Object> mobileDuplicateInfo = portalUserManService.getMobileDuplicateInfo();
|
||||||
|
resultMap.put("mobileDuplicateCheckEnabled", mobileDuplicateInfo.get("enabled"));
|
||||||
|
resultMap.put("mobileDuplicateUserCount", mobileDuplicateInfo.get("count"));
|
||||||
|
|
||||||
return ResponseEntity.ok(resultMap);
|
return ResponseEntity.ok(resultMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.eactive.apim.portal.file.entity.FileDetail;
|
|||||||
import com.eactive.apim.portal.file.entity.FileInfo;
|
import com.eactive.apim.portal.file.entity.FileInfo;
|
||||||
import com.eactive.apim.portal.file.service.FileService;
|
import com.eactive.apim.portal.file.service.FileService;
|
||||||
import com.eactive.apim.portal.portalorg.entity.PortalOrg;
|
import com.eactive.apim.portal.portalorg.entity.PortalOrg;
|
||||||
|
import com.eactive.apim.portal.portalproperty.service.PortalPropertyService;
|
||||||
import com.eactive.apim.portal.portaluser.entity.PortalUser;
|
import com.eactive.apim.portal.portaluser.entity.PortalUser;
|
||||||
import com.eactive.apim.portal.portaluser.entity.PortalUserEnums;
|
import com.eactive.apim.portal.portaluser.entity.PortalUserEnums;
|
||||||
import com.eactive.apim.portal.portaluser.entity.PortalUserPrivacyAgreement;
|
import com.eactive.apim.portal.portaluser.entity.PortalUserPrivacyAgreement;
|
||||||
@@ -31,7 +32,9 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Transactional(transactionManager = "transactionManagerForEMS")
|
@Transactional(transactionManager = "transactionManagerForEMS")
|
||||||
@@ -39,6 +42,11 @@ import java.util.List;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class PortalUserManService extends BaseService {
|
public class PortalUserManService extends BaseService {
|
||||||
|
|
||||||
|
/** PortalProperty 그룹명 */
|
||||||
|
private static final String PORTAL_PROPERTY_GROUP = "Portal";
|
||||||
|
/** 사용자 별 휴대폰 번호 중복 허용 프로퍼티 키 (Y:허용, N:허용안함) */
|
||||||
|
private static final String PROP_MOBILE_DUPLICATE_ALLOW = "user.mobile.duplicate.allow";
|
||||||
|
|
||||||
private final PortalUserService portalUserService;
|
private final PortalUserService portalUserService;
|
||||||
private final PortalUserUIMapper portalUserUIMapper;
|
private final PortalUserUIMapper portalUserUIMapper;
|
||||||
private final LocaleMessage localeMessage;
|
private final LocaleMessage localeMessage;
|
||||||
@@ -50,6 +58,7 @@ public class PortalUserManService extends BaseService {
|
|||||||
private final PortalUserTermsService portalUserTermsService;
|
private final PortalUserTermsService portalUserTermsService;
|
||||||
private final ApprovalService approvalService;
|
private final ApprovalService approvalService;
|
||||||
private final PortalInquiryService portalInquiryService;
|
private final PortalInquiryService portalInquiryService;
|
||||||
|
private final PortalPropertyService portalPropertyService;
|
||||||
|
|
||||||
|
|
||||||
public Page<PortalUserUI> selectList(Pageable pageable, PortalUserUISearch portalUserUISearch) {
|
public Page<PortalUserUI> selectList(Pageable pageable, PortalUserUISearch portalUserUISearch) {
|
||||||
@@ -57,6 +66,11 @@ public class PortalUserManService extends BaseService {
|
|||||||
return portalUser.map(entity -> {
|
return portalUser.map(entity -> {
|
||||||
PortalUserUI ui = convertToUIWithMaskOption(entity, false);
|
PortalUserUI ui = convertToUIWithMaskOption(entity, false);
|
||||||
|
|
||||||
|
// 목록의 휴대폰번호는 항상 마스킹 (중복 조회 모드에서만 컬럼이 노출되며 개인정보 보호)
|
||||||
|
if (ui != null && StringUtils.isNotBlank(ui.getMobileNumber())) {
|
||||||
|
ui.setMobileNumber(MaskingUtils.maskPhoneNumber(ui.getMobileNumber()));
|
||||||
|
}
|
||||||
|
|
||||||
if (entity.getPortalOrg() != null) {
|
if (entity.getPortalOrg() != null) {
|
||||||
setPortalOrgUI(entity, ui);
|
setPortalOrgUI(entity, ui);
|
||||||
}
|
}
|
||||||
@@ -64,6 +78,28 @@ public class PortalUserManService extends BaseService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 사용자 별 휴대폰 번호 중복 허용 프로퍼티(Portal/user.mobile.duplicate.allow)를 확인한다.
|
||||||
|
* 프로퍼티가 없으면 기본값 'N'(허용안함)으로 생성하며, '허용안함'일 때 중복 휴대폰번호 사용자 수를 함께 반환한다.
|
||||||
|
*
|
||||||
|
* @return enabled(boolean): 중복 조회 기능 활성 여부, count(long): 중복 휴대폰번호 사용자 수
|
||||||
|
*/
|
||||||
|
public Map<String, Object> getMobileDuplicateInfo() {
|
||||||
|
String allowValue = portalPropertyService.getOrCreateProperty(
|
||||||
|
PORTAL_PROPERTY_GROUP,
|
||||||
|
PROP_MOBILE_DUPLICATE_ALLOW,
|
||||||
|
"N",
|
||||||
|
"사용자 별 휴대폰 번호 중복 허용 여부 (Y:허용, N:허용안함). 허용안함일 때 사용자 관리 목록에 중복 휴대폰번호 사용자 조회 기능을 노출");
|
||||||
|
|
||||||
|
boolean checkEnabled = "N".equalsIgnoreCase(allowValue);
|
||||||
|
long duplicateUserCount = checkEnabled ? portalUserService.countDuplicateMobileUsers() : 0L;
|
||||||
|
|
||||||
|
Map<String, Object> info = new HashMap<>();
|
||||||
|
info.put("enabled", checkEnabled);
|
||||||
|
info.put("count", duplicateUserCount);
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
public PortalUserUI selectDetail(String id, boolean isMasked) {
|
public PortalUserUI selectDetail(String id, boolean isMasked) {
|
||||||
PortalUser portalUser = portalUserService.getById(id);
|
PortalUser portalUser = portalUserService.getById(id);
|
||||||
PortalUserUI portalUserUI = convertToUIWithMaskOption(portalUser, isMasked);
|
PortalUserUI portalUserUI = convertToUIWithMaskOption(portalUser, isMasked);
|
||||||
|
|||||||
+11
-14
@@ -1,10 +1,10 @@
|
|||||||
package com.eactive.eai.rms.onl.common.controller;
|
package com.eactive.eai.rms.onl.common.controller;
|
||||||
|
|
||||||
import com.eactive.eai.rms.common.interceptor.InterceptorSkipController;
|
import java.util.HashMap;
|
||||||
import com.eactive.eai.rms.common.scheduler.EMSScheduler;
|
import java.util.Map;
|
||||||
import com.eactive.eai.rms.onl.manage.adapter.spcftmadapter.AdapterJobInfo;
|
|
||||||
import com.eactive.eai.rms.onl.manage.adapter.spcftmadapter.AdapterSchedulerUtil;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import com.eactive.eai.rms.onl.manage.adapter.spcftmadapter.SpcfTmAdapterDao;
|
|
||||||
import org.quartz.Scheduler;
|
import org.quartz.Scheduler;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -15,9 +15,11 @@ import org.springframework.stereotype.Controller;
|
|||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.servlet.ModelAndView;
|
import org.springframework.web.servlet.ModelAndView;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import com.eactive.eai.rms.common.interceptor.InterceptorSkipController;
|
||||||
import java.util.HashMap;
|
import com.eactive.eai.rms.common.scheduler.EMSScheduler;
|
||||||
import java.util.Map;
|
import com.eactive.eai.rms.onl.manage.adapter.spcftmadapter.AdapterJobInfo;
|
||||||
|
import com.eactive.eai.rms.onl.manage.adapter.spcftmadapter.AdapterSchedulerUtil;
|
||||||
|
import com.eactive.eai.rms.onl.manage.adapter.spcftmadapter.SpcfTmAdapterDao;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
public class SchedulerReloadController implements InterceptorSkipController {
|
public class SchedulerReloadController implements InterceptorSkipController {
|
||||||
@@ -37,16 +39,11 @@ public class SchedulerReloadController implements InterceptorSkipController {
|
|||||||
@Qualifier("scheduler")
|
@Qualifier("scheduler")
|
||||||
private Scheduler scheduler;
|
private Scheduler scheduler;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
@Qualifier("clusteredScheduler")
|
|
||||||
private Scheduler clusteredScheduler;
|
|
||||||
|
|
||||||
@RequestMapping("/schedulerReload.do")
|
@RequestMapping("/schedulerReload.do")
|
||||||
public ResponseEntity<Map<String, Object>> rmsSchedulerReload(String jobName) {
|
public ResponseEntity<Map<String, Object>> rmsSchedulerReload(String jobName) {
|
||||||
Map<String, Object> result = new HashMap<>();
|
Map<String, Object> result = new HashMap<>();
|
||||||
try {
|
try {
|
||||||
emsScheduler.deleteAndAddJob(scheduler, jobName);
|
emsScheduler.reloadJob(jobName);
|
||||||
emsScheduler.deleteAndAddJob(clusteredScheduler, jobName);
|
|
||||||
result.put("status", "success");
|
result.put("status", "success");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("", e);
|
logger.error("", e);
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import com.eactive.eai.agent.encryption.ReloadEncryptionYNCommand;
|
|||||||
import com.eactive.eai.agent.property.ReloadPropertyCommand;
|
import com.eactive.eai.agent.property.ReloadPropertyCommand;
|
||||||
import com.eactive.eai.rms.common.base.OnlBaseAnnotationController;
|
import com.eactive.eai.rms.common.base.OnlBaseAnnotationController;
|
||||||
import com.eactive.eai.rms.common.vo.GridResponse;
|
import com.eactive.eai.rms.common.vo.GridResponse;
|
||||||
|
import com.eactive.ext.djb.DamoManager;
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
@@ -152,4 +153,11 @@ public class PropertyController extends OnlBaseAnnotationController {
|
|||||||
return new ModelAndView("excelView", resultMap);
|
return new ModelAndView("excelView", resultMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@PostMapping(value = "/onl/admin/common/propertyMan.json", params = "cmd=ENCRYPT")
|
||||||
|
public ResponseEntity<String> encrypt(PropertyUI propertyForm) throws Exception {
|
||||||
|
String encrypted = DamoManager.getInstance().encrypt(propertyForm.getPrpty2Val());
|
||||||
|
return ResponseEntity.ok(encrypted);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+1
-5
@@ -279,11 +279,7 @@ public class InflowGroupControlManService extends BaseService {
|
|||||||
for (Map<String, String> server : serverList) {
|
for (Map<String, String> server : serverList) {
|
||||||
String serverName = server.get("EAISVRINSTNM");
|
String serverName = server.get("EAISVRINSTNM");
|
||||||
String serverIp = server.get("EAISVRIP");
|
String serverIp = server.get("EAISVRIP");
|
||||||
String serverPort = server.get("WEBSERVERPORT");
|
String serverPort = server.get("EAISVRLSNPORT");
|
||||||
|
|
||||||
if (StringUtils.isEmpty(serverPort)) {
|
|
||||||
serverPort = server.get("EAISVRLSNPORT");
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, Object> serverStatus = new HashMap<>();
|
Map<String, Object> serverStatus = new HashMap<>();
|
||||||
serverStatus.put("serverName", serverName);
|
serverStatus.put("serverName", serverName);
|
||||||
|
|||||||
+1
-5
@@ -164,11 +164,7 @@ public class InflowControlManService extends BaseService {
|
|||||||
for (Map<String, String> server : serverList) {
|
for (Map<String, String> server : serverList) {
|
||||||
String serverName = server.get("EAISVRINSTNM");
|
String serverName = server.get("EAISVRINSTNM");
|
||||||
String serverIp = server.get("EAISVRIP");
|
String serverIp = server.get("EAISVRIP");
|
||||||
String serverPort = server.get("WEBSERVERPORT");
|
String serverPort = server.get("EAISVRLSNPORT");
|
||||||
|
|
||||||
if (StringUtils.isEmpty(serverPort)) {
|
|
||||||
serverPort = server.get("EAISVRLSNPORT");
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, Object> serverStatus = new HashMap<>();
|
Map<String, Object> serverStatus = new HashMap<>();
|
||||||
serverStatus.put("serverName", serverName);
|
serverStatus.put("serverName", serverName);
|
||||||
|
|||||||
@@ -0,0 +1,128 @@
|
|||||||
|
package com.eactive.eai.rms.onl.apim.masking;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* eapim-admin {@link MaskingUtils}가 보안아키텍처 표준 규칙(elink-online-core)으로
|
||||||
|
* 위임되어 동작하는지 검증한다.
|
||||||
|
*/
|
||||||
|
class MaskingUtilsTest {
|
||||||
|
|
||||||
|
// --- 이메일 (maskEmailId) ---
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("이메일 — 아이디 앞2·뒤2 마스킹")
|
||||||
|
void maskEmailId_normal() {
|
||||||
|
assertEquals("**st1**@test.com", MaskingUtils.maskEmailId("test123@test.com"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("이메일 — 아이디 4자: 전체 마스킹")
|
||||||
|
void maskEmailId_fourChars() {
|
||||||
|
assertEquals("****@test.com", MaskingUtils.maskEmailId("test@test.com"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("이메일 — 아이디 1자: 전체 마스킹")
|
||||||
|
void maskEmailId_oneChar() {
|
||||||
|
assertEquals("*@123.com", MaskingUtils.maskEmailId("1@123.com"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("이메일 — @ 없으면 원본 / null·빈문자 방어")
|
||||||
|
void maskEmailId_invalidAndNull() {
|
||||||
|
assertEquals("notanemail", MaskingUtils.maskEmailId("notanemail"));
|
||||||
|
assertNull(MaskingUtils.maskEmailId(null));
|
||||||
|
assertEquals("", MaskingUtils.maskEmailId(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 이름 (maskName) ---
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("이름 — 2자: 뒤 1자리")
|
||||||
|
void maskName_2chars() {
|
||||||
|
assertEquals("가*", MaskingUtils.maskName("가나"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("이름 — 3자: 가운데 1자리")
|
||||||
|
void maskName_3chars() {
|
||||||
|
assertEquals("가*다", MaskingUtils.maskName("가나다"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("이름 — 4자: 앞뒤 제외 가운데 전체")
|
||||||
|
void maskName_4chars() {
|
||||||
|
assertEquals("가**라", MaskingUtils.maskName("가나다라"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("이름 — 5자: 앞뒤 제외 가운데 전체")
|
||||||
|
void maskName_5chars() {
|
||||||
|
assertEquals("가***마", MaskingUtils.maskName("가나다라마"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("이름 — 1자/null/빈문자 방어")
|
||||||
|
void maskName_oneAndNull() {
|
||||||
|
assertEquals("가", MaskingUtils.maskName("가"));
|
||||||
|
assertNull(MaskingUtils.maskName(null));
|
||||||
|
assertEquals("", MaskingUtils.maskName(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 전화/휴대폰/Fax (maskPhoneNumber) ---
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("전화 — 2·3번째 세그먼트 각 뒤 2자리")
|
||||||
|
void maskPhoneNumber_normal() {
|
||||||
|
assertEquals("010-12**-12**", MaskingUtils.maskPhoneNumber("010-1234-1234"));
|
||||||
|
assertEquals("064-12**-56**", MaskingUtils.maskPhoneNumber("064-1234-5678"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("전화 — 하이픈 없는 11자리")
|
||||||
|
void maskPhoneNumber_noHyphen() {
|
||||||
|
assertEquals("01012**56**", MaskingUtils.maskPhoneNumber("01012345678"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("전화 — null/빈문자 방어")
|
||||||
|
void maskPhoneNumber_null() {
|
||||||
|
assertNull(MaskingUtils.maskPhoneNumber(null));
|
||||||
|
assertEquals("", MaskingUtils.maskPhoneNumber(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- IP (maskIpAddress) ---
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("IPv4 — 3번째 옥텟 마스킹")
|
||||||
|
void maskIpAddress_ipv4() {
|
||||||
|
assertEquals("172.0.***.1", MaskingUtils.maskIpAddress("172.0.0.1"));
|
||||||
|
assertEquals("192.168.***.1", MaskingUtils.maskIpAddress("192.168.100.1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("IPv6 — 마지막 그룹 마스킹")
|
||||||
|
void maskIpAddress_ipv6() {
|
||||||
|
assertEquals(
|
||||||
|
"AAAA:BBBB:CCCC:DDDD:EEEE:FFFF:0000:****",
|
||||||
|
MaskingUtils.maskIpAddress("AAAA:BBBB:CCCC:DDDD:EEEE:FFFF:0000:1111"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("IP — 쉼표 구분 다중 IPv4")
|
||||||
|
void maskIpAddress_multiple() {
|
||||||
|
assertEquals("192.168.***.1,10.0.***.2", MaskingUtils.maskIpAddress("192.168.100.1,10.0.0.2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("IP — null/빈문자 방어")
|
||||||
|
void maskIpAddress_null() {
|
||||||
|
assertNull(MaskingUtils.maskIpAddress(null));
|
||||||
|
assertEquals("", MaskingUtils.maskIpAddress(""));
|
||||||
|
}
|
||||||
|
}
|
||||||
+135
@@ -0,0 +1,135 @@
|
|||||||
|
package com.eactive.eai.rms.onl.apim.masking;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 메세지 본문/메신저ID 패턴 마스킹 + 색상강조 + XSS escape 검증.
|
||||||
|
*/
|
||||||
|
class MessagePatternMaskingUtilsTest {
|
||||||
|
|
||||||
|
// --- 이메일 ---
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("본문 이메일 — mask-email span + 표준 이메일 마스킹")
|
||||||
|
void maskAndHighlight_email() {
|
||||||
|
assertEquals("<span class=\"mask-email\">**st1**@test.com</span>",
|
||||||
|
MessagePatternMaskingUtils.maskAndHighlight("test123@test.com"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 휴대폰(하이픈) ---
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("본문 휴대폰(하이픈) — mask-phone span + 세그먼트별 뒤2자리 마스킹")
|
||||||
|
void maskAndHighlight_phoneHyphen() {
|
||||||
|
assertEquals("<span class=\"mask-phone\">010-12**-56**</span>",
|
||||||
|
MessagePatternMaskingUtils.maskAndHighlight("010-1234-5678"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 6자리 이상 숫자 (앞 3자리 제외 전체 마스킹) ---
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("본문 6자리 숫자 — 앞 3자리 제외 전체 마스킹(mask-digits)")
|
||||||
|
void maskAndHighlight_digits6() {
|
||||||
|
assertEquals("<span class=\"mask-digits\">123***</span>",
|
||||||
|
MessagePatternMaskingUtils.maskAndHighlight("123456"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("본문 하이픈 없는 휴대폰(11자리) — 6자리+ 숫자 규칙 적용")
|
||||||
|
void maskAndHighlight_phoneNoHyphen() {
|
||||||
|
assertEquals("<span class=\"mask-digits\">010********</span>",
|
||||||
|
MessagePatternMaskingUtils.maskAndHighlight("01012345678"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("5자리 이하 숫자는 마스킹하지 않음")
|
||||||
|
void maskAndHighlight_digitsUnder6() {
|
||||||
|
assertEquals("12345", MessagePatternMaskingUtils.maskAndHighlight("12345"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 우선순위 / 혼합 ---
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("하이픈 휴대폰이 숫자 규칙에 이중 적용되지 않음 (phone 우선)")
|
||||||
|
void maskAndHighlight_priority() {
|
||||||
|
String result = MessagePatternMaskingUtils.maskAndHighlight("010-1234-5678");
|
||||||
|
assertTrue(result.contains("mask-phone"));
|
||||||
|
assertFalse(result.contains("mask-digits"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("텍스트 + 이메일/휴대폰/숫자 혼합 — 각 패턴별 span 생성, 일반 텍스트는 보존")
|
||||||
|
void maskAndHighlight_mixed() {
|
||||||
|
String result = MessagePatternMaskingUtils.maskAndHighlight(
|
||||||
|
"인증요청 test123@test.com 전화 010-1234-5678 코드 123456 끝");
|
||||||
|
assertTrue(result.contains("<span class=\"mask-email\">**st1**@test.com</span>"));
|
||||||
|
assertTrue(result.contains("<span class=\"mask-phone\">010-12**-56**</span>"));
|
||||||
|
assertTrue(result.contains("<span class=\"mask-digits\">123***</span>"));
|
||||||
|
assertTrue(result.contains("인증요청"));
|
||||||
|
assertTrue(result.contains("끝"));
|
||||||
|
// 원문 일부가 그대로 노출되지 않아야 함
|
||||||
|
assertFalse(result.contains("test123@"));
|
||||||
|
assertFalse(result.contains("010-1234-5678"));
|
||||||
|
assertFalse(result.contains("123456"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- XSS / HTML escape ---
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("매칭 외 영역의 HTML 태그는 escape 되어 무력화")
|
||||||
|
void maskAndHighlight_escapesHtml() {
|
||||||
|
assertEquals("<b>hi</b>",
|
||||||
|
MessagePatternMaskingUtils.maskAndHighlight("<b>hi</b>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("script 태그 escape")
|
||||||
|
void maskAndHighlight_escapesScript() {
|
||||||
|
String result = MessagePatternMaskingUtils.maskAndHighlight("<script>alert(1)</script>");
|
||||||
|
assertFalse(result.contains("<script>"));
|
||||||
|
assertTrue(result.contains("<script>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("앰퍼샌드 escape")
|
||||||
|
void maskAndHighlight_escapesAmp() {
|
||||||
|
assertEquals("a&b", MessagePatternMaskingUtils.maskAndHighlight("a&b"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- null / empty ---
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("null·빈문자 본문은 빈문자 반환")
|
||||||
|
void maskAndHighlight_nullEmpty() {
|
||||||
|
assertEquals("", MessagePatternMaskingUtils.maskAndHighlight(null));
|
||||||
|
assertEquals("", MessagePatternMaskingUtils.maskAndHighlight(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 메신저 ID ---
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("메신저ID — 뒤 2자리 마스킹")
|
||||||
|
void maskMessengerId_normal() {
|
||||||
|
assertEquals("kakao_ab**", MessagePatternMaskingUtils.maskMessengerId("kakao_abcd"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("메신저ID — 2자 이하 전체 마스킹")
|
||||||
|
void maskMessengerId_short() {
|
||||||
|
assertEquals("**", MessagePatternMaskingUtils.maskMessengerId("ab"));
|
||||||
|
assertEquals("*", MessagePatternMaskingUtils.maskMessengerId("a"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("메신저ID — null·빈문자 방어")
|
||||||
|
void maskMessengerId_nullEmpty() {
|
||||||
|
assertNull(MessagePatternMaskingUtils.maskMessengerId(null));
|
||||||
|
assertEquals("", MessagePatternMaskingUtils.maskMessengerId(""));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user