Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8a884e53e3 | |||
| 7de00ecad2 |
Vendored
-179
@@ -1,179 +0,0 @@
|
||||
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,7 +12,6 @@ PortalTermsManController_약관관리_APIGW_INSERT,UPDATE,DELETE
|
||||
ProdClientManController_운영Client (키정보)관리_APIGW_INSERT,UPDATE,DELETE
|
||||
ApiSpecController_API스펙관리_APIGW_INSERT,DELETE
|
||||
MessageTemplateManController_메세지템플릿관리_APIGW_INSERT,UPDATE,DELETE
|
||||
PortalPartnershipManController_피드백/개선요청관리_APIGW_UNMASK,DELETE
|
||||
PortalPartnershipManController_사업제휴신청관리_APIGW_UNMASK
|
||||
PortalPropertyController_포탈 프로퍼티 관리_APIGW_UPDATE
|
||||
PortalUserTermsManController_약관동의 이력_APIGW_UNMASK
|
||||
MessageRequestManController_메세지발송내역_APIGW_UPDATE_STATUS
|
||||
|
||||
@@ -1,170 +0,0 @@
|
||||
<%@ 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,7 +1,6 @@
|
||||
<%@ page language="java" contentType="text/html; charset=utf-8"%>
|
||||
<%@page import="org.apache.commons.lang3.StringUtils"%>
|
||||
<%@page import="com.eactive.eai.rms.common.login.SessionManager"%>
|
||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
|
||||
@@ -52,13 +52,6 @@
|
||||
var urlParam = window.document.location.search;
|
||||
var mod = "";
|
||||
var strServiceType = "<%=request.getParameter("serviceType")%>";
|
||||
function getServiceType() {
|
||||
var serviceType = sessionStorage.getItem("serviceType");
|
||||
if (!serviceType || serviceType == "undefined" || serviceType == "null") {
|
||||
serviceType = localStorage.getItem("serviceType") || "";
|
||||
}
|
||||
return serviceType;
|
||||
}
|
||||
if(urlParam != ""){
|
||||
if(urlParam.indexOf("mod=") > -1){
|
||||
mod = urlParam.substring((urlParam.indexOf("mod=") + 4), urlParam.lastIndexOf("&"));
|
||||
@@ -76,13 +69,10 @@
|
||||
|
||||
$(document).ready(function() {
|
||||
var url = '<%=topPage%>';
|
||||
var serviceType = getServiceType();
|
||||
if (serviceType) {
|
||||
if (url.indexOf("?")>=0){
|
||||
url = url + "&serviceType="+ serviceType;
|
||||
url = url + "&serviceType="+ sessionStorage["serviceType"];
|
||||
}else{
|
||||
url = url + "?serviceType="+ serviceType;
|
||||
}
|
||||
url = url + "?serviceType="+ sessionStorage["serviceType"];
|
||||
}
|
||||
|
||||
$("#topFrame").attr('src',url);
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
/**
|
||||
* 문자열 마스킹 처리를 위한 유틸리티
|
||||
*
|
||||
* 보안아키텍처 표준 마스킹 규칙(Java MaskingUtils)과 동일한 결과를 내도록 구현한다.
|
||||
* 이름 2자: 뒤 1자리(가나→가*) / 3자 이상: 앞뒤 제외 가운데 전체(가나다라→가**라)
|
||||
* 이메일 아이디 앞2·뒤2 마스킹, 4자 이하 전체(test→****, test123→**st1**)
|
||||
* 전화 2·3번째 세그먼트 각각 뒤 2자리(010-1234-1234→010-12**-12**)
|
||||
*/
|
||||
const StringMaskingUtil = {
|
||||
|
||||
@@ -13,51 +8,41 @@ const StringMaskingUtil = {
|
||||
return str != null && str !== '';
|
||||
},
|
||||
|
||||
// '*' 반복 생성
|
||||
_stars: function(count) {
|
||||
return count > 0 ? '*'.repeat(count) : '';
|
||||
},
|
||||
|
||||
// 세그먼트 뒤 count 자리 마스킹
|
||||
_maskSegmentTail: function(segment, count) {
|
||||
if (segment.length <= count) {
|
||||
return this._stars(segment.length);
|
||||
}
|
||||
return segment.substring(0, segment.length - count) + this._stars(count);
|
||||
},
|
||||
|
||||
// 이름 마스킹 처리 (2자: 뒤 1자리, 3자 이상: 앞뒤 제외 가운데 전체)
|
||||
// 이름 마스킹 처리
|
||||
maskName: function(name) {
|
||||
if (!this.isValidString(name)) {
|
||||
return name;
|
||||
}
|
||||
const len = name.length;
|
||||
if (len === 1) {
|
||||
return name;
|
||||
}
|
||||
if (len === 2) {
|
||||
return name.charAt(0) + '*';
|
||||
}
|
||||
return name.charAt(0) + this._stars(len - 2) + name.charAt(len - 1);
|
||||
return name.length > 1 ?
|
||||
name.substring(0, name.length - 1) + "*" :
|
||||
name;
|
||||
},
|
||||
|
||||
// 이메일 마스킹 처리 (아이디 앞2·뒤2, 4자 이하 전체)
|
||||
// 이메일 마스킹 처리
|
||||
maskEmail: function(email) {
|
||||
if (!this.isValidString(email) || !email.includes('@')) {
|
||||
return email;
|
||||
}
|
||||
|
||||
const atIdx = email.indexOf('@');
|
||||
const local = email.substring(0, atIdx);
|
||||
const domain = email.substring(atIdx);
|
||||
|
||||
if (local.length <= 4) {
|
||||
return this._stars(local.length) + domain;
|
||||
const parts = email.split('@');
|
||||
if (parts.length !== 2) {
|
||||
return email;
|
||||
}
|
||||
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) {
|
||||
if (!this.isValidString(number)) {
|
||||
return number;
|
||||
@@ -65,9 +50,7 @@ const StringMaskingUtil = {
|
||||
|
||||
const parts = number.split('-');
|
||||
if (parts.length === 3) {
|
||||
return parts[0] + '-' +
|
||||
this._maskSegmentTail(parts[1], 2) + '-' +
|
||||
this._maskSegmentTail(parts[2], 2);
|
||||
return parts[0] + '-****-' + parts[2];
|
||||
}
|
||||
return number;
|
||||
}
|
||||
|
||||
@@ -251,29 +251,6 @@ var url_view = '<c:url value="/onl/admin/common/propertyMan.view" />';
|
||||
|
||||
});
|
||||
|
||||
$("#btn_pop_encrypt").click(function() {
|
||||
var prpty2Val = $("input[name=prpty2Val]").val();
|
||||
if (prpty2Val == '') {
|
||||
alert("프라퍼티 값을 입력하세요");
|
||||
return;
|
||||
}
|
||||
var postData = {
|
||||
cmd : "ENCRYPT",
|
||||
prpty2Val : prpty2Val,
|
||||
};
|
||||
$.ajax({
|
||||
type : "POST",
|
||||
url : url,
|
||||
data : postData,
|
||||
success : function(args) {
|
||||
$("input[name=prpty2Val]").val(args);
|
||||
},
|
||||
error : function(e) {
|
||||
alert(e.responseText);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
buttonControl(key);
|
||||
titleControl(key);
|
||||
});
|
||||
@@ -312,17 +289,12 @@ var url_view = '<c:url value="/onl/admin/common/propertyMan.view" />';
|
||||
<table class="table_row" cellspacing="0">
|
||||
<tr>
|
||||
<th style="width:20%;"><%= localeMessage.getString("propertyDetail.propertyKey") %></th>
|
||||
<td>
|
||||
<input type="text" name="prptyName" style="width:calc(100% - 85px);" /> <!-- <img src="<c:url value="/img/btn_pop_input.png"/>" id="btn_pop_input" /></td> -->
|
||||
<td><input type="text" name="prptyName" style="width:calc(100% - 85px);" /> <!-- <img src="<c:url value="/img/btn_pop_input.png"/>" id="btn_pop_input" /></td> -->
|
||||
<button type="button" class="cssbtn smallBtn2" id="btn_pop_input" style="vertical-align:middle; font-weight:bold;"><i class="material-icons">input</i><%= localeMessage.getString("button.input") %></button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="width:20%;"><%= localeMessage.getString("propertyDetail.propertyValue") %></th>
|
||||
<td>
|
||||
<input type="text" name="prpty2Val" style="width:calc(100% - 85px);"/>
|
||||
<button type="button" class="cssbtn smallBtn2" id="btn_pop_encrypt" style="vertical-align:middle; font-weight:bold;"><i class="material-icons">lock</i><%= localeMessage.getString("button.encode") %></button>
|
||||
</td>
|
||||
<th><%= localeMessage.getString("propertyDetail.propertyValue") %></th>
|
||||
<td><input type="text" name="prpty2Val"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- grid -->
|
||||
|
||||
@@ -1,211 +0,0 @@
|
||||
<%@ 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>
|
||||
@@ -1,149 +0,0 @@
|
||||
<%@ 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,9 +20,7 @@
|
||||
var combo;
|
||||
|
||||
function formatNoticeType(cellvalue, options, rowObject) {
|
||||
if (!combo || !combo.noticeTypeList) {
|
||||
return cellvalue;
|
||||
}
|
||||
var name = "";
|
||||
for (var i = 0; i < combo.noticeTypeList.length; i++) {
|
||||
if (combo.noticeTypeList[i].CODE == cellvalue) {
|
||||
return combo.noticeTypeList[i].NAME;
|
||||
@@ -49,37 +47,6 @@
|
||||
return cellvalue + icon;
|
||||
}
|
||||
|
||||
function deleteSelectedRows() {
|
||||
var selectedIds = $("#grid").jqGrid('getGridParam', 'selarrrow');
|
||||
|
||||
if (selectedIds.length === 0) {
|
||||
alert('항목을 선택하세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm('선택된 ' + selectedIds.length + '개 항목을 삭제하시겠습니까?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: url,
|
||||
dataType: "json",
|
||||
data: {
|
||||
cmd: 'DELETE_BULK',
|
||||
ids: selectedIds.join(',')
|
||||
},
|
||||
success: function(data) {
|
||||
alert('삭제되었습니다.');
|
||||
$("#grid").jqGrid('clearGridData', true);
|
||||
$("#grid").trigger("reloadGrid");
|
||||
},
|
||||
error: function(e) {
|
||||
alert('삭제 중 오류가 발생했습니다.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function init(){
|
||||
$.ajax({
|
||||
@@ -93,9 +60,6 @@
|
||||
new makeOptions("CODE","NAME").setObj($("select[name=searchNoticeType]")).setNoValueInclude(true).setNoValue('','<%=localeMessage.getString("combo.all")%>').setData(json.noticeTypeList).rendering();
|
||||
|
||||
putSelectFromParam();
|
||||
|
||||
// 콤보(combo)가 채워진 뒤에 그리드를 생성해야 formatNoticeType 경합이 발생하지 않음
|
||||
buildGrid();
|
||||
},
|
||||
error:function(e){
|
||||
alert(e.responseText);
|
||||
@@ -103,7 +67,7 @@
|
||||
});
|
||||
}
|
||||
|
||||
function buildGrid(){
|
||||
$(document).ready(function() {
|
||||
|
||||
$('#grid').jqGrid({
|
||||
datatype:"json",
|
||||
@@ -139,8 +103,6 @@
|
||||
autowidth: true,
|
||||
viewrecords: true,
|
||||
rowList : eval('[${rmsDefaultRowList}]'),
|
||||
multiselect: true,
|
||||
multiboxonly: true,
|
||||
ondblClickRow: function(rowId) {
|
||||
var rowData = $(this).getRowData(rowId);
|
||||
var id = rowData['id'];
|
||||
@@ -177,14 +139,10 @@
|
||||
}
|
||||
});
|
||||
|
||||
resizeJqGridWidth('grid','content_middle','1000');
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
// 콤보 로드 → 콤보 성공 콜백에서 buildGrid() 호출 (경합 방지)
|
||||
init();
|
||||
|
||||
resizeJqGridWidth('grid','content_middle','1000');
|
||||
|
||||
$("#btn_search").click(function(){
|
||||
var postData = getSearchForJqgrid("cmd","LIST");
|
||||
$("#grid").setGridParam({ url:url,postData: postData ,page:1 }).trigger("reloadGrid");
|
||||
@@ -199,10 +157,6 @@
|
||||
goNav(url2);
|
||||
});
|
||||
|
||||
$("#btn_delete_selected").click(function(){
|
||||
deleteSelectedRows();
|
||||
});
|
||||
|
||||
$("select[name=searchUseYn], input[name^=search]").keydown(function(key){
|
||||
if (key.keyCode == 13){
|
||||
$("#btn_search").click();
|
||||
@@ -225,7 +179,6 @@
|
||||
<div class="search_wrap">
|
||||
<button type="button" class="cssbtn" id="btn_new" level="W"><i class="material-icons">add</i> <%= localeMessage.getString("button.new") %></button>
|
||||
<button type="button" class="cssbtn" id="btn_search" level="R"><i class="material-icons">search</i> <%= localeMessage.getString("button.search") %></button>
|
||||
<button type="button" class="cssbtn" id="btn_delete_selected" level="W" style="background-color: #dc3545; border-color: #dc3545; color: white;"><i class="material-icons">delete_forever</i> 선택 삭제</button>
|
||||
</div>
|
||||
<div class="title">공지사항 목록<span class="tooltip">공지사항을 관리합니다.</span></div>
|
||||
<form id="ajaxForm" onsubmit="return false;">
|
||||
|
||||
@@ -26,37 +26,6 @@ function formatFile(cellvalue, options, rowObject) {
|
||||
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() {
|
||||
$('#grid').jqGrid({
|
||||
datatype:"json",
|
||||
@@ -85,8 +54,6 @@ $(document).ready(function() {
|
||||
autowidth: true,
|
||||
viewrecords: true,
|
||||
rowList : eval('[${rmsDefaultRowList}]'),
|
||||
multiselect: true,
|
||||
multiboxonly: true,
|
||||
ondblClickRow: function(rowId) {
|
||||
var rowData = $(this).getRowData(rowId);
|
||||
var id = rowData['id'];
|
||||
@@ -131,10 +98,6 @@ $(document).ready(function() {
|
||||
$("#grid").setGridParam({ url:url,postData: postData ,page:1 }).trigger("reloadGrid");
|
||||
});
|
||||
|
||||
$("#btn_delete_selected").click(function(){
|
||||
deleteSelectedRows();
|
||||
});
|
||||
|
||||
$("input[name^=search]").keydown(function(key){
|
||||
if (key.keyCode == 13){
|
||||
$("#btn_search").click();
|
||||
@@ -157,9 +120,8 @@ $(document).ready(function() {
|
||||
<div class="content_middle" id="content_middle">
|
||||
<div class="search_wrap">
|
||||
<button type="button" class="cssbtn" id="btn_search" level="R" ><i class="material-icons">search</i> <%= localeMessage.getString("button.search") %></button>
|
||||
<button type="button" class="cssbtn" id="btn_delete_selected" level="W" style="background-color: #dc3545; border-color: #dc3545; color: white;"><i class="material-icons">delete_forever</i> 선택 삭제</button>
|
||||
</div>
|
||||
<div class="title">피드백/개선요청 신청 목록</div>
|
||||
<div class="title">사업제휴 신청 목록</div>
|
||||
<form id="ajaxForm" onsubmit="return false;">
|
||||
<table class="search_condition" cellspacing=0;>
|
||||
<tbody>
|
||||
|
||||
@@ -92,27 +92,6 @@ $(document).ready(function() {
|
||||
goNav(returnUrl);//LIST로 이동
|
||||
});
|
||||
|
||||
$("#btn_delete").click(function(){
|
||||
if (!confirm('선택한 항목을 삭제하시겠습니까?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: url,
|
||||
dataType: "json",
|
||||
data: { cmd: 'DELETE', id: key },
|
||||
success: function(data) {
|
||||
alert('삭제되었습니다.');
|
||||
var returnUrl = getReturnUrlForReturn();
|
||||
goNav(returnUrl);//LIST로 이동
|
||||
},
|
||||
error: function(e) {
|
||||
alert('삭제 중 오류가 발생했습니다.');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#btn_unmask").click(function () {
|
||||
const reason = prompt("마스킹 해제 사유를 입력하세요:");
|
||||
|
||||
@@ -155,10 +134,9 @@ $(document).ready(function() {
|
||||
<div class="content_middle">
|
||||
<div class="search_wrap">
|
||||
<button type="button" class="cssbtn" id="btn_unmask" level="W" status="DETAIL"><i class="material-icons">lock_open</i> 마스킹해제</button>
|
||||
<button type="button" class="cssbtn" id="btn_delete" level="W" status="DETAIL" style="background-color: #dc3545; border-color: #dc3545; color: white;"><i class="material-icons">delete_forever</i> 삭제</button>
|
||||
<button type="button" class="cssbtn" id="btn_previous" level="R" status="DETAIL,NEW"><i class="material-icons">arrow_back</i> <%= localeMessage.getString("button.previous") %></button>
|
||||
</div>
|
||||
<div class="title">피드백/개선요청 신청 상세</div>
|
||||
<div class="title">사업제휴 신청 상세</div>
|
||||
|
||||
<table id="grid" ></table>
|
||||
<div id="pager"></div>
|
||||
|
||||
@@ -29,16 +29,6 @@
|
||||
// 원본 행 데이터를 ID로 조회하기 위한 맵
|
||||
var gridRowDataMap = {};
|
||||
|
||||
// 휴대폰번호 중복 조회 모드 여부
|
||||
var isDupView = false;
|
||||
|
||||
// 현재 검색조건 + 중복 조회 모드 플래그를 합쳐 LIST 요청 파라미터를 생성
|
||||
function buildListPostData() {
|
||||
var postData = getSearchForJqgrid("cmd", "LIST");
|
||||
postData.onlyDuplicateMobile = isDupView;
|
||||
return postData;
|
||||
}
|
||||
|
||||
/* 선택된 사용자 삭제 - 상태에 따라 Soft/Hard Delete 자동 분기 */
|
||||
function deleteSelectedUsers() {
|
||||
var selectedIds = $("#grid").jqGrid('getGridParam', 'selarrrow');
|
||||
@@ -176,13 +166,6 @@
|
||||
|
||||
$('#grid').jqGrid('setColProp', 'approvalStatus', {editoptions: {value: select_approvalStatus}});
|
||||
$('#grid').jqGrid('setColProp', 'userStatus', {editoptions: {value: select_userStatus}});
|
||||
|
||||
// 휴대폰번호 중복 허용안함(기능 ON) + 중복 사용자 존재 시 배너 노출
|
||||
if (json.mobileDuplicateCheckEnabled && json.mobileDuplicateUserCount > 0) {
|
||||
$("#dupMobileCount").text(json.mobileDuplicateUserCount);
|
||||
$("#dupMobileBanner").show();
|
||||
}
|
||||
|
||||
$('#grid').trigger('reloadGrid');
|
||||
|
||||
},
|
||||
@@ -193,7 +176,7 @@
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
var gridPostData = buildListPostData();
|
||||
var gridPostData = getSearchForJqgrid("cmd", "LIST");
|
||||
|
||||
$('#grid').jqGrid({
|
||||
datatype: "json",
|
||||
@@ -205,7 +188,7 @@
|
||||
'<%= localeMessage.getString("portalUser.orgName") %>',
|
||||
'<%= localeMessage.getString("portalUser.name") %>',
|
||||
'<%= localeMessage.getString("portalUser.userId") %>',
|
||||
'<%= localeMessage.getString("portalUser.mobileNumber") %>',
|
||||
<%--'<%= localeMessage.getString("portalUser.mobile") %>',--%>
|
||||
'<%= localeMessage.getString("portalUser.roleName") %>',
|
||||
'<%= localeMessage.getString("portalUser.userStatus") %>',
|
||||
'<%= localeMessage.getString("portalUser.createOn") %>',
|
||||
@@ -218,7 +201,7 @@
|
||||
{name: 'portalOrgUIs.orgName', align: 'center', width: "120"},
|
||||
{name: 'userName', align: 'center', width: "80" },
|
||||
{name: 'loginId', align: 'center', width: "150" },
|
||||
{name: 'mobileNumber', align: 'center', width: "110", hidden: true},
|
||||
// {name: 'mobileNumber', align: 'center', width: "100" },
|
||||
{name: 'roleCodeDescription', align: 'center', width: "80"},
|
||||
{name: 'userStatusDescription', align: 'center', width: "50"},
|
||||
{name: 'createdDate', align: 'center', width: "100", formatter: timeStampFormat},
|
||||
@@ -291,20 +274,9 @@
|
||||
init();
|
||||
|
||||
$("#btn_search").click(function () {
|
||||
$("#grid").setGridParam({url: url, postData: buildListPostData(), page: 1}).trigger("reloadGrid");
|
||||
});
|
||||
var postData = getSearchForJqgrid("cmd", "LIST");
|
||||
|
||||
// 중복 휴대폰번호 사용자만 보기 / 전체 보기 토글
|
||||
$("#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");
|
||||
$("#grid").setGridParam({url: url, postData: postData, page: 1}).trigger("reloadGrid");
|
||||
});
|
||||
|
||||
$("#btn_new").click(function () {
|
||||
@@ -362,11 +334,6 @@
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<div id="dupMobileBanner" style="display:none; background:#f8d7da; border:1px solid #dc3545; padding:8px 15px; margin:5px 0; border-radius:4px; color:#721c24;">
|
||||
<strong>⚠ 중복 휴대폰번호 사용자 <span id="dupMobileCount">0</span>건 발견</strong>
|
||||
<button type="button" class="cssbtn" id="btn_dup_toggle" style="margin-left:10px;">중복만 보기</button>
|
||||
</div>
|
||||
|
||||
<table class="search_condition" cellspacing=0;>
|
||||
<tbody>
|
||||
<tr>
|
||||
@@ -398,16 +365,15 @@
|
||||
<td><input type="text" name="searchUserName"></td>
|
||||
|
||||
<th style="width:10%; min-width:100px;">
|
||||
<%= localeMessage.getString("portalUser.userId") %><span style="color:#dc3545;">(*)</span>
|
||||
<%= localeMessage.getString("portalUser.userId") %>
|
||||
</th>
|
||||
<td><input type="text" name="searchUserId" value="${param.searchUserId}"></td>
|
||||
<th style="width:10%; min-width:100px;"><%= localeMessage.getString("portalUser.mobileNumber") %><span style="color:#dc3545;">(*)</span>
|
||||
<th style="width:10%; min-width:100px;"><%= localeMessage.getString("portalUser.mobileNumber") %>
|
||||
</th>
|
||||
<td><input type="text" name="searchMobileNumber" value="${param.searchMobileNumber}"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div style="color:#dc3545; font-size:12px; margin:4px 2px;">(*) 암호화 항목 · 부분검색 불가(전체 일치)</div>
|
||||
|
||||
<table id="grid"></table>
|
||||
<div id="pager"></div>
|
||||
|
||||
@@ -222,7 +222,7 @@
|
||||
$("input[name=searchStartDate], input[name=searchEndDate]").datepicker();
|
||||
|
||||
var today = getToday();
|
||||
var startDate = today.substring(0,6)+"01";
|
||||
var startDate = today;
|
||||
var endDate = today;
|
||||
|
||||
|
||||
@@ -234,7 +234,6 @@
|
||||
}
|
||||
|
||||
list();
|
||||
search();
|
||||
|
||||
resizeJqGridWidth('grid', 'content_middle', '1200');
|
||||
|
||||
|
||||
Binary file not shown.
@@ -202,7 +202,7 @@ public class MainController implements InterceptorSkipController {
|
||||
|
||||
// SMS 인증번호 생성 및 발송
|
||||
String authCode = smsAuthService.generateAuthCode();
|
||||
boolean sent = smsAuthService.sendAuthCode(userInfo, authCode);
|
||||
boolean sent = smsAuthService.sendAuthCode(userInfo.getCphnno(), authCode);
|
||||
|
||||
if (!sent) {
|
||||
return LoginResponseDto.builder()
|
||||
|
||||
@@ -57,7 +57,7 @@ public class ClusteredSchedulerConfig {
|
||||
|
||||
quartzProperties.put("org.quartz.jobStore.misfireThreshold", "60000");
|
||||
quartzProperties.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");
|
||||
quartzProperties.put("org.quartz.jobStore.driverDelegateClass", "org.quartz.impl.jdbcjobstore.StdJDBCDelegate");
|
||||
quartzProperties.put("org.quartz.jobStore.driverDelegateClass", "org.quartz.impl.jdbcjobstore.PostgreSQLDelegate");
|
||||
quartzProperties.put("org.quartz.jobStore.useProperties", "false");
|
||||
quartzProperties.put("org.quartz.jobStore.dataSource", monitoringSchema);
|
||||
quartzProperties.put("org.quartz.jobStore.tablePrefix", monitoringSchema + ".QRTZ_");
|
||||
|
||||
@@ -15,6 +15,4 @@ public interface EMSScheduler {
|
||||
|
||||
void deleteAndAddJob(Scheduler scheduler, String jobName) throws SchedulerException, ClassNotFoundException;
|
||||
|
||||
void reloadJob(String jobName) throws SchedulerException, ClassNotFoundException;
|
||||
|
||||
}
|
||||
|
||||
@@ -120,50 +120,12 @@ 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 {
|
||||
String schedulerType = (inScheduler == clusteredScheduler) ? "clusteredScheduler" : "scheduler";
|
||||
JobKey key = new JobKey(jobNm, Scheduler.DEFAULT_GROUP);
|
||||
JobDetail jobDetail = inScheduler.getJobDetail(key);
|
||||
if (jobDetail != null) {
|
||||
inScheduler.deleteJob(key);
|
||||
log.info(jobNm + " has been scheduled to deleted");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +151,10 @@ public class EMSSchedulerImpl implements EMSScheduler {
|
||||
.withSchedule(cronSchedule(info.getCronExp()))
|
||||
.build();
|
||||
|
||||
inScheduler.scheduleJob(job, trigger);
|
||||
Date ft = inScheduler.scheduleJob(job, trigger);
|
||||
log
|
||||
.info("{} has been scheduled to run at: {} and repeat based on expression: {}", job.getKey(), ft,
|
||||
trigger.getCronExpression());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,9 +45,6 @@ public class SchedulerManController extends BaseAnnotationController {
|
||||
@Qualifier("monitoringSyncAgent")
|
||||
private MonitoringSyncAgent monitoringSyncAgent;
|
||||
|
||||
@Autowired
|
||||
private EMSScheduler emsScheduler;
|
||||
|
||||
@GetMapping(value = "/common/scheduler/schedulerMan.view")
|
||||
public String viewList() {
|
||||
return "/common/scheduler/schedulerMan";
|
||||
@@ -99,11 +96,6 @@ public class SchedulerManController extends BaseAnnotationController {
|
||||
@PostMapping(value = "/common/scheduler/schedulerMan.json", params = "cmd=UPDATE")
|
||||
public ResponseEntity<Void> update(JobInfoUI jobInfoUI) {
|
||||
service.update(jobInfoUI);
|
||||
try {
|
||||
emsScheduler.reloadJob(jobInfoUI.getId());
|
||||
} catch (Exception e) {
|
||||
logger.error("reloadJob error on current server", e);
|
||||
}
|
||||
syncReloadScheduler(jobInfoUI.getId());
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
@@ -123,7 +115,7 @@ public class SchedulerManController extends BaseAnnotationController {
|
||||
String url = "/monitoring/schedulerReload.do";
|
||||
|
||||
try {
|
||||
ret = monitoringSyncAgent.targetSync(url, paramMap);
|
||||
ret = monitoringSyncAgent.sync(url, paramMap);
|
||||
} catch (Exception e) {
|
||||
logger.error("syncReloadScheduler error", e);
|
||||
} finally {
|
||||
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
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
@@ -1,65 +0,0 @@
|
||||
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
@@ -1,29 +0,0 @@
|
||||
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,16 +13,10 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@Transactional(transactionManager = "transactionManagerForEMS")
|
||||
public class PortalNoticeService extends AbstractEMSDataSerivce<PortalNotice, String, PortalNoticeRepository> {
|
||||
|
||||
public void deleteByIds(List<String> ids) {
|
||||
repository.deleteAllById(ids);
|
||||
}
|
||||
|
||||
public Page<PortalNotice> findAll(Pageable pageable, PortalNoticeUISearch portalNoticeUISearch) {
|
||||
QPortalNotice qPortalNotice = QPortalNotice.portalNotice;
|
||||
|
||||
|
||||
-6
@@ -10,16 +10,10 @@ import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@Transactional(transactionManager = "transactionManagerForEMS")
|
||||
public class PortalPartnershipService extends AbstractEMSDataSerivce<PartnershipApplication, String, PortalPartnershipRepository> {
|
||||
|
||||
public void deleteByIds(List<String> ids) {
|
||||
repository.deleteAllById(ids);
|
||||
}
|
||||
|
||||
public Page<PartnershipApplication> findAll(Pageable pageable, String searchBizSubject, String searchBizDetail) {
|
||||
QPartnershipApplication qPartnershipApplication = QPartnershipApplication.partnershipApplication;
|
||||
|
||||
|
||||
+2
-56
@@ -8,14 +8,12 @@ import com.eactive.apim.portal.template.entity.QMessageRequest;
|
||||
import com.eactive.eai.rms.data.jpa.AbstractEMSDataSerivce;
|
||||
import com.querydsl.core.BooleanBuilder;
|
||||
import com.querydsl.core.types.dsl.BooleanExpression;
|
||||
import com.querydsl.jpa.JPAExpressions;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
@@ -38,22 +36,7 @@ public class PortalUserService extends AbstractEMSDataSerivce<PortalUser, String
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(portalUserUISearch.getSearchUserId())) {
|
||||
// login_id / email_addr 는 PersonalDataEncryptConverter 로 암호화 저장되는 컬럼이다.
|
||||
// 암호화는 부분문자열을 보존하지 않으므로 LIKE/contains 검색은 절대 매칭되지 않는다.
|
||||
// 결정적(deterministic) 암호화라 정확일치(eq)만 가능하므로, login_id 와 email_addr 둘 다 정확일치로 OR 검색한다.
|
||||
String searchUserId = portalUserUISearch.getSearchUserId().trim();
|
||||
BooleanBuilder loginIdPredicate = new BooleanBuilder()
|
||||
.or(qPortalUser.loginId.eq(searchUserId))
|
||||
.or(qPortalUser.emailAddr.eq(searchUserId));
|
||||
// 이메일 형식 값(예: user+1@unit-test.com)은 form-urlencoded 디코딩 과정에서
|
||||
// '+' 가 공백으로 치환되어 전달될 수 있다(프록시/컨테이너 단계라 앱에서 인코딩만으로 막기 어려움).
|
||||
// login_id/email 에는 공백이 올 수 없으므로, 공백 포함 검색어는 '+' 치환 값도 함께 매칭한다.
|
||||
if (searchUserId.indexOf(' ') >= 0) {
|
||||
String plusRestored = searchUserId.replace(' ', '+');
|
||||
loginIdPredicate.or(qPortalUser.loginId.eq(plusRestored))
|
||||
.or(qPortalUser.emailAddr.eq(plusRestored));
|
||||
}
|
||||
predicate.and(loginIdPredicate);
|
||||
predicate.and(qPortalUser.loginId.containsIgnoreCase(portalUserUISearch.getSearchUserId()));
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(portalUserUISearch.getSearchUserName())) {
|
||||
@@ -64,49 +47,16 @@ public class PortalUserService extends AbstractEMSDataSerivce<PortalUser, String
|
||||
predicate.and(qPortalUser.portalOrg.id.eq(portalUserUISearch.getSearchOrgId()));
|
||||
}
|
||||
if (StringUtils.isNotBlank(portalUserUISearch.getSearchMobileNumber())) {
|
||||
// mobile_number 도 암호화 저장 컬럼이라 부분문자열이 보존되지 않는다.
|
||||
// LIKE/contains 는 매칭되지 않으므로 정확일치(eq)로만 검색한다.
|
||||
predicate.and(qPortalUser.mobileNumber.eq(portalUserUISearch.getSearchMobileNumber().trim()));
|
||||
predicate.and(qPortalUser.mobileNumber.contains(portalUserUISearch.getSearchMobileNumber()));
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(portalUserUISearch.getSearchRoleCode())) {
|
||||
predicate.and(qPortalUser.roleCode.eq(PortalUserEnums.RoleCode.valueOf(portalUserUISearch.getSearchRoleCode())));
|
||||
}
|
||||
|
||||
// 휴대폰번호 중복(2명 이상 공유) 사용자만 조회
|
||||
if (portalUserUISearch.isOnlyDuplicateMobile()) {
|
||||
// 자바 리스트 .in() 은 컨버터가 IN 파라미터를 암호화하므로 레거시 평문 데이터와 매칭되지 않는다.
|
||||
// 대신 서브쿼리 .in() 으로 컬럼 대 컬럼(SQL 내부) 비교를 하여 평문/암호문 저장 방식과 무관하게 동작시킨다.
|
||||
QPortalUser dupUser = new QPortalUser("dupUser");
|
||||
predicate.and(qPortalUser.mobileNumber.in(
|
||||
JPAExpressions.select(dupUser.mobileNumber)
|
||||
.from(dupUser)
|
||||
.where(dupUser.mobileNumber.isNotNull()
|
||||
.and(dupUser.userStatus.ne(PortalUserEnums.UserStatus.REMOVED)))
|
||||
.groupBy(dupUser.mobileNumber)
|
||||
.having(dupUser.count().gt(1L))));
|
||||
}
|
||||
|
||||
return repository.findAll(predicate, pageable);
|
||||
}
|
||||
|
||||
/**
|
||||
* 휴대폰번호가 중복되는 사용자 총 인원수(중복 그룹에 속한 사용자 수의 합)를 반환한다.
|
||||
*/
|
||||
public long countDuplicateMobileUsers() {
|
||||
QPortalUser qPortalUser = QPortalUser.portalUser;
|
||||
// Oracle 빈 문자열=NULL 이슈로 isNotNull 만 사용 (findDuplicateMobileNumbers 와 동일 기준)
|
||||
List<Long> duplicateGroupCounts = getJPAQueryFactory()
|
||||
.select(qPortalUser.count())
|
||||
.from(qPortalUser)
|
||||
.where(qPortalUser.mobileNumber.isNotNull()
|
||||
.and(qPortalUser.userStatus.ne(PortalUserEnums.UserStatus.REMOVED)))
|
||||
.groupBy(qPortalUser.mobileNumber)
|
||||
.having(qPortalUser.count().gt(1L))
|
||||
.fetch();
|
||||
return duplicateGroupCounts.stream().mapToLong(Long::longValue).sum();
|
||||
}
|
||||
|
||||
public boolean existsByLoginId(String loginId) {
|
||||
BooleanExpression predicate = QPortalUser.portalUser.loginId.eq(loginId);
|
||||
return repository.exists(predicate);
|
||||
@@ -127,10 +77,6 @@ public class PortalUserService extends AbstractEMSDataSerivce<PortalUser, String
|
||||
}
|
||||
|
||||
public PortalUser findByUserId(String userId) {
|
||||
if (StringUtils.isBlank(userId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
QPortalUser qPortalUser = QPortalUser.portalUser;
|
||||
|
||||
return getJPAQueryFactory()
|
||||
|
||||
-3
@@ -23,7 +23,4 @@ public class PortalUserUISearch {
|
||||
|
||||
private boolean includeRemoved;
|
||||
|
||||
/** true 인 경우 휴대폰번호가 중복(2명 이상 공유)되는 사용자만 조회 */
|
||||
private boolean onlyDuplicateMobile;
|
||||
|
||||
}
|
||||
|
||||
@@ -39,10 +39,11 @@ public interface ApiStatusRepository extends BaseRepository<ApiStatus, String> {
|
||||
+ " FROM ("
|
||||
+ " SELECT A.EAISVCNAME"
|
||||
+ " , NVL(B.STATUS_CODE, 'N') AS STATUS_CODE"
|
||||
+ " , NVL(( SELECT MAX('Y') FROM EMSAPP.DJB_APISTATUS_INCIDENT_API X"
|
||||
+ " WHERE EXISTS (SELECT 1 FROM EMSAPP.DJB_APISTATUS_INCIDENT Y WHERE X.INCIDENT_ID = Y.INCIDENT_ID "
|
||||
+ " AND SYSTIMESTAMP BETWEEN STARTED_AT AND END_AT) "
|
||||
+ " AND A.EAISVCNAME = X.API_ID),'N') AS CTRL_YN "
|
||||
//TODO: 게시판 임시로 생성하여 사용. 유차장이 완료하면 변경할것
|
||||
+ " , NVL(( SELECT 'Y' FROM PTL_NOTICE_API X "
|
||||
+ " WHERE EXISTS (SELECT 1 FROM EMSAPP.PTL_NOTICE Y WHERE X.NOTICE_ID = Y.ID)"
|
||||
//+ " AND TO_CHAR(SYSDATE, 'YYYYMMDDHH24MI') BETWEEN START_TIME AND END_TIME) "
|
||||
+ " AND A.EAISVCNAME = X.API_NAME),'N') AS CTRL_YN "
|
||||
+ " , (SELECT CASE WHEN TOTAL = 0 THEN 'X'"
|
||||
+ " WHEN (TOTAL - SUCCESS) * 100 / TOTAL > :errorRate THEN 'Y'"
|
||||
+ " ELSE 'N' END"
|
||||
|
||||
@@ -55,8 +55,7 @@ public class ApiStatusService {
|
||||
apiStatus.setEaisvcname(event.getEaisvcname());
|
||||
apiStatus.setStatusCode(newStatusCode);
|
||||
|
||||
apiStatusRepository.saveAndFlush(apiStatus); // PK 있으면 UPDATE, 없으면 INSERT (즉시 flush)
|
||||
|
||||
apiStatusRepository.save(apiStatus); // PK 있으면 UPDATE, 없으면 INSERT
|
||||
log.debug("API 상태 변경: {}-{} {} → {}", event.getEaisvcname(), event.getEaisvcdesc(), event.getEvent(), newStatusCode);
|
||||
|
||||
|
||||
@@ -76,8 +75,8 @@ public class ApiStatusService {
|
||||
String event = entry.getKey();
|
||||
List<String> apiIds = entry.getValue();
|
||||
|
||||
HashMap<String, Object> params = getSwingChatMessage(event, apiIds);
|
||||
ums.send("api-monitor", MessageCode.API_STATUS_CHANGED, params);
|
||||
String message = getSwingChatMessage(event, apiIds);
|
||||
ums.sendMessenger("api-monitor", MessageCode.API_STATUS_CHANGED, message);
|
||||
|
||||
//제휴사 웹훅 발송
|
||||
ums.sendWebhook(event, apiIds);
|
||||
@@ -103,7 +102,7 @@ public class ApiStatusService {
|
||||
}
|
||||
|
||||
|
||||
private HashMap<String, Object> getSwingChatMessage(String event, List<String> apiIds) {
|
||||
private String getSwingChatMessage(String event, List<String> apiIds) {
|
||||
String message = "";
|
||||
switch (event) {
|
||||
case "CONTROL_START":
|
||||
@@ -128,16 +127,7 @@ public class ApiStatusService {
|
||||
message = "";
|
||||
}
|
||||
|
||||
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;
|
||||
return message + "\n- " + String.join(",", apiIds);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.eactive.eai.rms.ext.djb.inflow;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@@ -10,6 +9,7 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.eactive.apim.portal.template.entity.MessageCode;
|
||||
import com.eactive.eai.rms.data.entity.man.role.Role;
|
||||
import com.eactive.eai.rms.data.entity.onl.djb.inflow.InflowTokenInsufficient;
|
||||
import com.eactive.eai.rms.ext.djb.ums.UmsManager;
|
||||
|
||||
@@ -32,9 +32,8 @@ public class InflowTokenService {
|
||||
for (InflowTokenInsufficient info : rows) {
|
||||
log.debug("유량제어 토큰 획득 실패: {}-{} 최근 {}분 동안 {}건", info.getEaisvcname(), info.getEaisvcdesc(), rangeMinute, info.getCnt());
|
||||
String message = "유량제어 토큰 획득 실패하였습니다 \n" + info.getEaisvcname();
|
||||
HashMap<String,Object> params = new HashMap<String,Object>();
|
||||
params.put("message", message);
|
||||
ums.send("api-monitor", MessageCode.INFLOW_TOKEN_FAILED, params);
|
||||
|
||||
ums.sendMessenger("api-monitor", MessageCode.INFLOW_TOKEN_FAILED, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package com.eactive.eai.rms.ext.djb.job;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.quartz.DisallowConcurrentExecution;
|
||||
@@ -71,24 +68,10 @@ public class ApiStatusMonitorJob implements Job {
|
||||
|
||||
@Override
|
||||
public void execute(JobExecutionContext context) throws JobExecutionException {
|
||||
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
|
||||
|
||||
// execute.instances 가 지정된 경우 해당 인스턴스에서만 실행
|
||||
String allowedInstances = jobDataMap.getString("execute.instances");
|
||||
if (StringUtils.isNotEmpty(allowedInstances)) {
|
||||
Set<String> instanceSet = Arrays.stream(allowedInstances.split(","))
|
||||
.map(s -> s.trim().toLowerCase())
|
||||
.collect(Collectors.toSet());
|
||||
String currentInstance = StringUtils.defaultString(System.getProperty("inst.Name")).trim().toLowerCase();
|
||||
if (!instanceSet.contains(currentInstance)) {
|
||||
log.debug("Job 실행 인스턴스 제한 - 현재: {}, 허용: {}", currentInstance, allowedInstances);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
log.info("*** START ApiStatusUpdateJob run({})", CommonUtil.getToday("yyyy-MM-dd HH:mm"));
|
||||
|
||||
// Job 파라미터 로깅
|
||||
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
|
||||
logJobParameters(jobDataMap);
|
||||
|
||||
HashMap<String, String> param = this.checkParameters(jobDataMap);
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
package com.eactive.eai.rms.ext.djb.job;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.quartz.DisallowConcurrentExecution;
|
||||
import org.quartz.Job;
|
||||
import org.quartz.JobDataMap;
|
||||
@@ -52,21 +48,6 @@ public class InflowTokenMonitorJob implements Job {
|
||||
|
||||
@Override
|
||||
public void execute(JobExecutionContext context) throws JobExecutionException {
|
||||
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
|
||||
|
||||
// execute.instances 가 지정된 경우 해당 인스턴스에서만 실행
|
||||
String allowedInstances = jobDataMap.getString("execute.instances");
|
||||
if (StringUtils.isNotEmpty(allowedInstances)) {
|
||||
Set<String> instanceSet = Arrays.stream(allowedInstances.split(","))
|
||||
.map(s -> s.trim().toLowerCase())
|
||||
.collect(Collectors.toSet());
|
||||
String currentInstance = StringUtils.defaultString(System.getProperty("inst.Name")).trim().toLowerCase();
|
||||
if (!instanceSet.contains(currentInstance)) {
|
||||
log.debug("Job 실행 인스턴스 제한 - 현재: {}, 허용: {}", currentInstance, allowedInstances);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
log.info("*** START InflowTokenFailMonitorJob run({})", CommonUtil.getToday("yyyy-MM-dd HH:mm"));
|
||||
|
||||
long execMinute = 1; //default 배치 실행주기(분)
|
||||
|
||||
@@ -5,20 +5,14 @@ import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
|
||||
import com.eactive.eai.rms.common.util.CommonUtil;
|
||||
import com.eactive.eai.rms.onl.apim.portalInquiry.PortalInquiryClosingService;
|
||||
import com.eactive.eai.rms.onl.common.util.DateUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.quartz.DisallowConcurrentExecution;
|
||||
import org.quartz.Job;
|
||||
import org.quartz.JobDataMap;
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.quartz.JobExecutionException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 매일 00:05 실행 - RESPONDED 상태 문의글 자동 종료 배치
|
||||
*
|
||||
@@ -50,21 +44,6 @@ public class PortalInquiryClosingJob implements Job {
|
||||
|
||||
@Override
|
||||
public void execute(JobExecutionContext context) throws JobExecutionException {
|
||||
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
|
||||
|
||||
// execute.instances 가 지정된 경우 해당 인스턴스에서만 실행
|
||||
String allowedInstances = jobDataMap.getString("execute.instances");
|
||||
if (StringUtils.isNotEmpty(allowedInstances)) {
|
||||
Set<String> instanceSet = Arrays.stream(allowedInstances.split(","))
|
||||
.map(s -> s.trim().toLowerCase())
|
||||
.collect(Collectors.toSet());
|
||||
String currentInstance = StringUtils.defaultString(System.getProperty("inst.Name")).trim().toLowerCase();
|
||||
if (!instanceSet.contains(currentInstance)) {
|
||||
log.debug("Job 실행 인스턴스 제한 - 현재: {}, 허용: {}", currentInstance, allowedInstances);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
log.info("*** START PortalInquiryCommentClosingService run({})", CommonUtil.getToday("yyyy-MM-dd HH:mm"));
|
||||
|
||||
int closingDays = DEFAULT_CLOSING_DAYS;
|
||||
|
||||
@@ -85,7 +85,7 @@ public class ApiUseStatsService
|
||||
+ " ORDER BY ORGNAME";
|
||||
} else if ("API".equals(search.getSearchType())) {
|
||||
dataSql = "SELECT COUNT(*) OVER () AS RECORDS "
|
||||
+ ", A.API_NAME"
|
||||
+ ", C.EAISVCDESC"
|
||||
+ ", SUM(A.TOTAL_CNT)"
|
||||
+ ", SUM(A.SUCCESS_CNT)"
|
||||
+ ", CASE WHEN SUM(A.TOTAL_CNT) > 0 THEN SUM(A.SUCCESS_CNT) * 100 / SUM(A.TOTAL_CNT) ELSE NULL END"
|
||||
@@ -99,8 +99,8 @@ public class ApiUseStatsService
|
||||
+ " LEFT OUTER JOIN TSEAIAU01 B ON A.CLIENT_ID = B.CLIENTID"
|
||||
+ " LEFT OUTER JOIN TSEAIHE01 C ON A.API_NAME = C.EAISVCNAME "
|
||||
+ where
|
||||
+ " GROUP BY A.API_NAME"
|
||||
+ " ORDER BY API_NAME";
|
||||
+ " GROUP BY C.EAISVCDESC"
|
||||
+ " ORDER BY EAISVCDESC";
|
||||
} else if ("DATE".equals(search.getSearchType())) {
|
||||
dataSql = "SELECT COUNT(*) OVER () AS RECORDS "
|
||||
+ ", TO_CHAR(STAT_TIME,'YYYY-MM-DD') AS STAT_TIME "
|
||||
|
||||
@@ -29,13 +29,14 @@ public class UmsManager {
|
||||
private final WebhookService webhookService;
|
||||
|
||||
/**
|
||||
* roleId에 해당하는 역할을 가진 직원에게 UMS(sms,email,messenger) 발송
|
||||
* role 역할을 가진 내부직원에게 메신저(Swing chat) 발송
|
||||
* @param role
|
||||
* @param messageCode
|
||||
* @param params
|
||||
* @param message
|
||||
* @return
|
||||
*/
|
||||
public void send(String roleId, MessageCode messageCode, HashMap<String, Object> params) {
|
||||
public void sendMessenger(String roleId, MessageCode messageCode, String message) {
|
||||
|
||||
List<UserInfo> users = userInfoService.findAllByRoleId(roleId);
|
||||
if (users.isEmpty()) {
|
||||
log.info("sendMessenger: no users found for role '{}'", roleId);
|
||||
@@ -43,33 +44,33 @@ public class UmsManager {
|
||||
}
|
||||
|
||||
for (UserInfo user : users) {
|
||||
String cleanedPhone = user.getCphnno().replaceAll("[^0-9]", "").trim();
|
||||
MessageRecipient recipient = MessageRecipient.builder()
|
||||
.userId(user.getUserid())
|
||||
.username(user.getUsername())
|
||||
.email(user.getEmad())
|
||||
.phone(cleanedPhone)
|
||||
.messengerId(user.getUserid())
|
||||
.build();
|
||||
|
||||
HashMap<String, Object> params = new HashMap<>();
|
||||
params.put("message", message);
|
||||
|
||||
messageHandlerService.publishEvent(messageCode, recipient, params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 직원 또는 회원에게 UMS(sms,email,messenger) 발송
|
||||
* 내부직원에게 메신저 발송
|
||||
* @param user
|
||||
* @param messageCode
|
||||
* @param params
|
||||
* @param message
|
||||
*/
|
||||
public void send(UserInfo user, MessageCode messageCode, HashMap<String, Object> params) {
|
||||
String cleanedPhone = user.getCphnno().replaceAll("[^0-9]", "").trim();
|
||||
public void sendMessenger(UserInfo user, MessageCode messageCode, String message) {
|
||||
MessageRecipient recipient = MessageRecipient.builder()
|
||||
.userId(user.getUserid())
|
||||
.username(user.getUsername())
|
||||
.email(user.getEmad())
|
||||
.phone(cleanedPhone)
|
||||
.messengerId(user.getUserid())
|
||||
.build();
|
||||
|
||||
HashMap<String, Object> params = new HashMap<>();
|
||||
params.put("message", message);
|
||||
|
||||
messageHandlerService.publishEvent(messageCode, recipient, params);
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ public class SmsAuthController implements InterceptorSkipController {
|
||||
|
||||
// 인증번호 생성 및 발송
|
||||
String authCode = getSmsAuthService().generateAuthCode();
|
||||
boolean sent = getSmsAuthService().sendAuthCode(userInfo, authCode);
|
||||
boolean sent = getSmsAuthService().sendAuthCode(userInfo.getCphnno(), authCode);
|
||||
|
||||
if (sent) {
|
||||
// 세션에 인증번호 저장 (기존 UserInfo, LoginVo 유지)
|
||||
@@ -106,7 +106,7 @@ public class SmsAuthController implements InterceptorSkipController {
|
||||
|
||||
// 새 인증번호 생성 및 발송
|
||||
String authCode = getSmsAuthService().generateAuthCode();
|
||||
boolean sent = getSmsAuthService().sendAuthCode(userInfo, authCode);
|
||||
boolean sent = getSmsAuthService().sendAuthCode(userInfo.getCphnno(), authCode);
|
||||
|
||||
if (sent) {
|
||||
LoginVo loginVo = getSmsAuthService().getLoginVoFromSession(session);
|
||||
|
||||
@@ -1,25 +1,22 @@
|
||||
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.eai.rms.common.login.LoginVo;
|
||||
import com.eactive.eai.rms.ext.djb.ums.UmsManager;
|
||||
import com.eactive.ext.kjb.common.KjbProperty;
|
||||
import com.eactive.ext.kjb.common.KjbPropertyHolder;
|
||||
import com.eactive.ext.kjb.common.KjbUmsException;
|
||||
import com.eactive.ext.kjb.ums.KjbUmsService;
|
||||
|
||||
import com.eactive.ext.kjb.ums.UmsBizWorkCode;
|
||||
import com.eactive.ext.kjb.ums.gson.GsonUtil;
|
||||
import com.eactive.ext.kjb.ums.gson.VerifyPhoneUmsTemplate;
|
||||
import com.google.gson.JsonObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.security.SecureRandom;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
@Slf4j
|
||||
public class SmsAuthService {
|
||||
@@ -42,9 +39,6 @@ public class SmsAuthService {
|
||||
|
||||
private final SecureRandom random = new SecureRandom();
|
||||
|
||||
@Autowired
|
||||
private UmsManager ums;
|
||||
|
||||
private SmsAuthService() {
|
||||
// 싱글톤
|
||||
}
|
||||
@@ -122,25 +116,20 @@ public class SmsAuthService {
|
||||
/**
|
||||
* SMS 인증번호 발송
|
||||
*/
|
||||
public boolean sendAuthCode(UserInfo userInfo, String authCode) {
|
||||
public boolean sendAuthCode(String phone, String authCode) {
|
||||
try {
|
||||
// String cleanedPhone = phone.replaceAll("[^0-9]", "").trim();
|
||||
// String guid = String.format("EAPIM_EMS-%s",
|
||||
// DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS").format(LocalDateTime.now()));
|
||||
//
|
||||
// JsonObject content = GsonUtil.toJsonObject(VerifyPhoneUmsTemplate.builder().authNumber(authCode).build());
|
||||
//
|
||||
// getUmsService().sendKakaoAlimTalk(guid, UmsBizWorkCode.VERIFY_PHONE, cleanedPhone, content);
|
||||
// log.info("SMS 인증번호 발송 완료 - guid: {}, phone: {}****{}",
|
||||
// guid, cleanedPhone.substring(0, 3), cleanedPhone.substring(cleanedPhone.length() - 4));
|
||||
// log.debug("SMS 인증번호 발송 - authCode: {}", authCode);
|
||||
String cleanedPhone = phone.replaceAll("[^0-9]", "").trim();
|
||||
String guid = String.format("EAPIM_EMS-%s",
|
||||
DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS").format(LocalDateTime.now()));
|
||||
|
||||
HashMap<String,Object> params = new HashMap<String, Object>();
|
||||
params.put("authNumber", authCode);
|
||||
JsonObject content = GsonUtil.toJsonObject(VerifyPhoneUmsTemplate.builder().authNumber(authCode).build());
|
||||
|
||||
ums.send(userInfo, MessageCode.USER_VERIFICATION_MOBILEPHONE, params);
|
||||
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);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
} catch (KjbUmsException e) {
|
||||
log.error("SMS 인증번호 발송 실패", e);
|
||||
return false;
|
||||
}
|
||||
@@ -278,14 +267,17 @@ public class SmsAuthService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 전화번호 마스킹 (표준 규칙: 2·3번째 세그먼트 각각 뒤 2자리)
|
||||
* 예: 010-1234-5678 → 010-12**-56**
|
||||
* 전화번호 마스킹
|
||||
*/
|
||||
public String maskPhoneNumber(String phone) {
|
||||
if (StringUtils.isEmpty(phone)) {
|
||||
return "****";
|
||||
}
|
||||
return com.eactive.eai.common.util.MaskingUtils.maskPhoneNumber(phone);
|
||||
String cleaned = phone.replaceAll("[^0-9]", "");
|
||||
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: {}",
|
||||
userId,
|
||||
MaskingUtils.maskName(name),
|
||||
MaskingUtils.maskPhoneNumber(cellPhoneNo),
|
||||
MaskingUtils.maskEmailId(email));
|
||||
maskPhoneNumber(cellPhoneNo),
|
||||
maskEmail(email));
|
||||
|
||||
// 사용자 생성 또는 업데이트
|
||||
UserInfo userInfo = findOrCreateUser(info, extendedInfo);
|
||||
@@ -341,4 +341,33 @@ public class SsoController implements InterceptorSkipController {
|
||||
}
|
||||
return phone.replaceAll("[^0-9]", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* 휴대폰번호 마스킹 (로그용)
|
||||
*/
|
||||
private String maskPhoneNumber(String phone) {
|
||||
if (StringUtils.isEmpty(phone) || phone.length() < 4) {
|
||||
return "****";
|
||||
}
|
||||
// 010-1234-5678 → 010-****-5678
|
||||
String cleaned = phone.replaceAll("[^0-9]", "");
|
||||
if (cleaned.length() < 7) {
|
||||
return "****";
|
||||
}
|
||||
return cleaned.substring(0, 3) + "****" + cleaned.substring(cleaned.length() - 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* 이메일 마스킹 (로그용)
|
||||
*/
|
||||
private String maskEmail(String email) {
|
||||
if (StringUtils.isEmpty(email) || !email.contains("@")) {
|
||||
return "****";
|
||||
}
|
||||
int atIndex = email.indexOf("@");
|
||||
if (atIndex <= 2) {
|
||||
return "**" + email.substring(atIndex);
|
||||
}
|
||||
return email.substring(0, 2) + "**" + email.substring(atIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,52 +1,83 @@
|
||||
package com.eactive.eai.rms.onl.apim.masking;
|
||||
|
||||
/**
|
||||
* 개인정보 마스킹 유틸리티 (eapim-admin)
|
||||
*
|
||||
* <p>실제 마스킹 규칙은 보안아키텍처 표준 구현인
|
||||
* {@link com.eactive.eai.common.util.MaskingUtils}(elink-online-core)에 위임한다.
|
||||
* 기존 호출부 호환을 위해 메서드명/시그니처는 그대로 유지한다.</p>
|
||||
*
|
||||
* <pre>
|
||||
* 이메일 eact***@gmail.com (구) → **ct1**@gmail.com (앞2·뒤2, 4자 이하 전체)
|
||||
* 이름 홍길* (구) → 홍*동 (2자 뒤1, 3자↑ 가운데 전체)
|
||||
* 전화 010-****-5678 (구) → 010-12**-56** (세그먼트별 뒤 2자리)
|
||||
* IPv4 192.168.***.1 (3번째 옥텟) / IPv6 마지막 그룹, 콤마 다중 IP 지원
|
||||
* </pre>
|
||||
*/
|
||||
import com.eactive.eai.rms.common.util.StringUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class MaskingUtils {
|
||||
|
||||
private MaskingUtils() {}
|
||||
|
||||
/**
|
||||
* 이메일 주소의 ID 부분을 마스킹 처리
|
||||
* 예: aajjbacc@gmail.com => **jjba**@gmail.com
|
||||
* 예: eactive@gmail.com => eact***@gmail.com
|
||||
*/
|
||||
public static String maskEmailId(String email) {
|
||||
return com.eactive.eai.common.util.MaskingUtils.maskEmail(email);
|
||||
if (email == null || email.isEmpty() || !email.contains("@")) {
|
||||
return email;
|
||||
}
|
||||
|
||||
String[] parts = email.split("@");
|
||||
String id = parts[0];
|
||||
String domain = parts[1];
|
||||
|
||||
if (id.length() <= 3) {
|
||||
return "***" + "@" + domain;
|
||||
}
|
||||
|
||||
String maskedId = id.substring(0, id.length() - 3) + "***";
|
||||
return maskedId + "@" + domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* 이름 마스킹 처리 (2자: 뒤 1자리, 3자 이상: 앞뒤 제외 가운데 전체)
|
||||
* 예: 홍길동 => 홍*동
|
||||
* 이름의 마지막 글자를 마스킹 처리
|
||||
*/
|
||||
public static String maskName(String name) {
|
||||
return com.eactive.eai.common.util.MaskingUtils.maskKoreanName(name);
|
||||
if (name == null || name.isEmpty() || name.length() < 2) {
|
||||
return name;
|
||||
}
|
||||
|
||||
return name.substring(0, name.length() - 1) + "*";
|
||||
}
|
||||
|
||||
/**
|
||||
* 전화번호/휴대폰/FAX 마스킹 처리 (2·3번째 세그먼트 각각 뒤 2자리)
|
||||
* 예: 010-1234-5678 => 010-12**-56**
|
||||
* 전화번호 마스킹 처리
|
||||
* 예: 010-1234-5678 => 010-****-5678
|
||||
*/
|
||||
public static String maskPhoneNumber(String phoneNumber) {
|
||||
return com.eactive.eai.common.util.MaskingUtils.maskPhoneNumber(phoneNumber);
|
||||
if (phoneNumber == null || phoneNumber.isEmpty()) {
|
||||
return phoneNumber;
|
||||
}
|
||||
|
||||
String[] parts = phoneNumber.split("-");
|
||||
if (parts.length != 3) {
|
||||
return phoneNumber;
|
||||
}
|
||||
|
||||
return parts[0] + "-****-" + parts[2];
|
||||
}
|
||||
|
||||
/**
|
||||
* IP 주소 마스킹 처리 (IPv4 3번째 옥텟 / IPv6 마지막 그룹, 콤마 다중 IP 지원)
|
||||
* 예: 192.168.1.1 => 192.168.***.1
|
||||
* IP 주소 마스킹 처리
|
||||
* 예: 192.168.1.1 => 192.168.*.*
|
||||
*/
|
||||
public static String maskIpAddress(String ipAddresses) {
|
||||
return com.eactive.eai.common.util.MaskingUtils.maskIpAddress(ipAddresses);
|
||||
if (StringUtils.isBlank(ipAddresses)) {
|
||||
return ipAddresses;
|
||||
}
|
||||
|
||||
String[] ips = ipAddresses.split(",");
|
||||
List<String> maskedIps = Arrays.stream(ips)
|
||||
.map(ip -> {
|
||||
String[] parts = ip.trim().split("\\.");
|
||||
if (parts.length == 4) {
|
||||
// 세 번째 octet을 ***로 마스킹
|
||||
parts[2] = "***";
|
||||
return String.join(".", parts);
|
||||
}
|
||||
return ip; // 유효하지 않은 IP 형식은 그대로 반환
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return String.join(",", maskedIps);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
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
@@ -1,67 +0,0 @@
|
||||
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
@@ -1,106 +0,0 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
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,10 +80,4 @@ public class PortalNoticeManController extends BaseAnnotationController {
|
||||
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,7 +33,6 @@ import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@@ -283,20 +282,4 @@ public class PortalNoticeManService extends BaseService {
|
||||
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,16 +55,4 @@ public class PortalPartnershipManController extends BaseAnnotationController {
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
-38
@@ -15,10 +15,6 @@ import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
@Transactional(transactionManager = "transactionManagerForEMS")
|
||||
public class PortalPartnershipManService extends BaseService {
|
||||
@@ -55,48 +51,18 @@ public class PortalPartnershipManService extends BaseService {
|
||||
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) {
|
||||
PortalPartnershipUI partnershipUI = portalPartnershipUIMapper.toVo(partnership);
|
||||
PortalUser user = findUser(partnership.getCreatedBy());
|
||||
|
||||
// 작성자(createdBy)에 해당하는 포털 사용자가 없을 수 있음(탈퇴/삭제, 시스템 작성 등)
|
||||
String userName = user != null ? user.getUserName() : null;
|
||||
String loginId = user != null ? user.getLoginId() : null;
|
||||
|
||||
if (isMasked) {
|
||||
// 마스킹 처리
|
||||
partnershipUI.setCreatedByName(MaskingUtils.maskName(userName));
|
||||
partnershipUI.setCreatedById(MaskingUtils.maskEmailId(loginId));
|
||||
partnershipUI.setCreatedByName(MaskingUtils.maskName(user.getUserName()));
|
||||
partnershipUI.setCreatedById(MaskingUtils.maskEmailId(user.getLoginId()));
|
||||
} else {
|
||||
// 마스킹 해제 상태
|
||||
partnershipUI.setCreatedByName(userName);
|
||||
partnershipUI.setCreatedById(loginId);
|
||||
partnershipUI.setCreatedByName(user.getUserName());
|
||||
partnershipUI.setCreatedById(user.getLoginId());
|
||||
}
|
||||
|
||||
// 첨부파일 처리
|
||||
|
||||
@@ -7,7 +7,6 @@ import com.eactive.apim.portal.agreements.service.AgreementsService;
|
||||
import com.eactive.apim.portal.file.entity.FileDetail;
|
||||
import com.eactive.apim.portal.file.entity.FileInfo;
|
||||
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.data.entity.man.user.UserInfoService;
|
||||
import com.eactive.eai.rms.data.entity.onl.apim.portalterms.PortalTermsService;
|
||||
@@ -50,14 +49,7 @@ public class PortalTermsManService extends BaseService {
|
||||
}
|
||||
|
||||
private String findName(String userId) {
|
||||
if (StringUtils.isBlank(userId)) {
|
||||
return "";
|
||||
}
|
||||
// 등록자/수정자 ID로 사용자를 찾지 못해도(삭제된 사용자, 레거시 시드 데이터 등)
|
||||
// 화면 전체가 깨지지 않도록 방어적으로 조회한다. (PortalFaq/PortalInquiry 컨벤션과 동일)
|
||||
return userInfoService.findById(userId)
|
||||
.map(UserInfo::getUsername)
|
||||
.orElse("Unknown");
|
||||
return userInfoService.getById(userId).getUsername();
|
||||
}
|
||||
|
||||
public Page<PortalTermsUI> selectList(Pageable pageable, String searchAgreementsType) {
|
||||
|
||||
@@ -102,12 +102,6 @@ public class PortalUserManController extends BaseAnnotationController {
|
||||
resultMap.put("userStatusRows", userStatusList);
|
||||
resultMap.put("roleCodeRows", roleCodeList);
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import com.eactive.apim.portal.file.entity.FileDetail;
|
||||
import com.eactive.apim.portal.file.entity.FileInfo;
|
||||
import com.eactive.apim.portal.file.service.FileService;
|
||||
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.PortalUserEnums;
|
||||
import com.eactive.apim.portal.portaluser.entity.PortalUserPrivacyAgreement;
|
||||
@@ -32,9 +31,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
@Transactional(transactionManager = "transactionManagerForEMS")
|
||||
@@ -42,11 +39,6 @@ import java.util.Map;
|
||||
@Slf4j
|
||||
public class PortalUserManService extends BaseService {
|
||||
|
||||
/** PortalProperty 그룹명 */
|
||||
private static final String PORTAL_PROPERTY_GROUP = "Portal";
|
||||
/** 사용자 별 휴대폰 번호 중복 허용 프로퍼티 키 (true:허용, false:허용안함, 기본 false) */
|
||||
private static final String PROP_MOBILE_DUPLICATE_ALLOW = "user.mobile.duplicate.allow";
|
||||
|
||||
private final PortalUserService portalUserService;
|
||||
private final PortalUserUIMapper portalUserUIMapper;
|
||||
private final LocaleMessage localeMessage;
|
||||
@@ -58,7 +50,6 @@ public class PortalUserManService extends BaseService {
|
||||
private final PortalUserTermsService portalUserTermsService;
|
||||
private final ApprovalService approvalService;
|
||||
private final PortalInquiryService portalInquiryService;
|
||||
private final PortalPropertyService portalPropertyService;
|
||||
|
||||
|
||||
public Page<PortalUserUI> selectList(Pageable pageable, PortalUserUISearch portalUserUISearch) {
|
||||
@@ -66,19 +57,6 @@ public class PortalUserManService extends BaseService {
|
||||
return portalUser.map(entity -> {
|
||||
PortalUserUI ui = convertToUIWithMaskOption(entity, false);
|
||||
|
||||
// 목록의 개인정보(사용자명/이메일아이디/휴대폰번호)는 항상 마스킹하여 노출 (상세 조회에서만 마스킹 해제)
|
||||
if (ui != null) {
|
||||
if (StringUtils.isNotBlank(ui.getUserName())) {
|
||||
ui.setUserName(MaskingUtils.maskName(ui.getUserName()));
|
||||
}
|
||||
if (StringUtils.isNotBlank(ui.getLoginId())) {
|
||||
ui.setLoginId(MaskingUtils.maskEmailId(ui.getLoginId()));
|
||||
}
|
||||
if (StringUtils.isNotBlank(ui.getMobileNumber())) {
|
||||
ui.setMobileNumber(MaskingUtils.maskPhoneNumber(ui.getMobileNumber()));
|
||||
}
|
||||
}
|
||||
|
||||
if (entity.getPortalOrg() != null) {
|
||||
setPortalOrgUI(entity, ui);
|
||||
}
|
||||
@@ -86,30 +64,6 @@ public class PortalUserManService extends BaseService {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자 별 휴대폰 번호 중복 허용 프로퍼티(Portal/user.mobile.duplicate.allow)를 확인한다.
|
||||
* 프로퍼티가 없으면 기본값 'false'(허용안함)으로 생성하며, '허용안함'일 때 중복 휴대폰번호 사용자 수를 함께 반환한다.
|
||||
*
|
||||
* @return enabled(boolean): 중복 조회 기능 활성 여부, count(long): 중복 휴대폰번호 사용자 수
|
||||
*/
|
||||
public Map<String, Object> getMobileDuplicateInfo() {
|
||||
String allowValue = portalPropertyService.getOrCreateProperty(
|
||||
PORTAL_PROPERTY_GROUP,
|
||||
PROP_MOBILE_DUPLICATE_ALLOW,
|
||||
"false",
|
||||
"사용자 별 휴대폰 번호 중복 허용 여부 (true:허용, false:허용안함). 허용안함일 때 사용자 관리 목록에 중복 휴대폰번호 사용자 조회 기능을 노출");
|
||||
|
||||
// 레거시 Y/N 값도 허용으로 함께 인식 (true/Y → 허용, 그 외 → 허용안함)
|
||||
boolean allowed = "true".equalsIgnoreCase(allowValue) || "Y".equalsIgnoreCase(allowValue);
|
||||
boolean checkEnabled = !allowed;
|
||||
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) {
|
||||
PortalUser portalUser = portalUserService.getById(id);
|
||||
PortalUserUI portalUserUI = convertToUIWithMaskOption(portalUser, isMasked);
|
||||
|
||||
+14
-11
@@ -1,10 +1,10 @@
|
||||
package com.eactive.eai.rms.onl.common.controller;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import com.eactive.eai.rms.common.interceptor.InterceptorSkipController;
|
||||
import com.eactive.eai.rms.common.scheduler.EMSScheduler;
|
||||
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;
|
||||
import org.quartz.Scheduler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -15,11 +15,9 @@ import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import com.eactive.eai.rms.common.interceptor.InterceptorSkipController;
|
||||
import com.eactive.eai.rms.common.scheduler.EMSScheduler;
|
||||
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;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Controller
|
||||
public class SchedulerReloadController implements InterceptorSkipController {
|
||||
@@ -39,11 +37,16 @@ public class SchedulerReloadController implements InterceptorSkipController {
|
||||
@Qualifier("scheduler")
|
||||
private Scheduler scheduler;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("clusteredScheduler")
|
||||
private Scheduler clusteredScheduler;
|
||||
|
||||
@RequestMapping("/schedulerReload.do")
|
||||
public ResponseEntity<Map<String, Object>> rmsSchedulerReload(String jobName) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
try {
|
||||
emsScheduler.reloadJob(jobName);
|
||||
emsScheduler.deleteAndAddJob(scheduler, jobName);
|
||||
emsScheduler.deleteAndAddJob(clusteredScheduler, jobName);
|
||||
result.put("status", "success");
|
||||
} catch (Exception e) {
|
||||
logger.error("", e);
|
||||
|
||||
@@ -19,7 +19,6 @@ import com.eactive.eai.agent.encryption.ReloadEncryptionYNCommand;
|
||||
import com.eactive.eai.agent.property.ReloadPropertyCommand;
|
||||
import com.eactive.eai.rms.common.base.OnlBaseAnnotationController;
|
||||
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.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -153,11 +152,4 @@ public class PropertyController extends OnlBaseAnnotationController {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
+5
-1
@@ -279,7 +279,11 @@ public class InflowGroupControlManService extends BaseService {
|
||||
for (Map<String, String> server : serverList) {
|
||||
String serverName = server.get("EAISVRINSTNM");
|
||||
String serverIp = server.get("EAISVRIP");
|
||||
String serverPort = server.get("EAISVRLSNPORT");
|
||||
String serverPort = server.get("WEBSERVERPORT");
|
||||
|
||||
if (StringUtils.isEmpty(serverPort)) {
|
||||
serverPort = server.get("EAISVRLSNPORT");
|
||||
}
|
||||
|
||||
Map<String, Object> serverStatus = new HashMap<>();
|
||||
serverStatus.put("serverName", serverName);
|
||||
|
||||
+5
-1
@@ -164,7 +164,11 @@ public class InflowControlManService extends BaseService {
|
||||
for (Map<String, String> server : serverList) {
|
||||
String serverName = server.get("EAISVRINSTNM");
|
||||
String serverIp = server.get("EAISVRIP");
|
||||
String serverPort = server.get("EAISVRLSNPORT");
|
||||
String serverPort = server.get("WEBSERVERPORT");
|
||||
|
||||
if (StringUtils.isEmpty(serverPort)) {
|
||||
serverPort = server.get("EAISVRLSNPORT");
|
||||
}
|
||||
|
||||
Map<String, Object> serverStatus = new HashMap<>();
|
||||
serverStatus.put("serverName", serverName);
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
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
@@ -1,135 +0,0 @@
|
||||
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