This commit is contained in:
eastargh
2026-06-16 14:26:21 +09:00
19 changed files with 453 additions and 356 deletions
+140
View File
@@ -0,0 +1,140 @@
name: eapim-admin CI
on:
push:
branches:
- master
- develop
- stage
- feats/ci-test
jobs:
build:
runs-on: [self-hosted, linux, eapim-admin]
env:
JAVA_HOME: /apps/opts/jdk8
JAVA_HOME_TOMCAT: /apps/opts/jdk17
CATALINA_BASE: /prod/eapim/adminportal
CATALINA_HOME: /prod/eapim/apache-tomcat-9.0.116
CATALINA_PID: /prod/eapim/adminportal/temp/catalina.pid
DEPLOY_HTTP_PORT: "30120"
defaults:
run:
working-directory: eapim-admin
steps:
- name: Checkout eapim-admin (trigger SHA)
uses: http://172.30.1.50:3000/actions/checkout@v4
with:
path: eapim-admin
- name: Checkout sibling modules (master)
working-directory: ${{ github.workspace }}
run: |
set -e
mkdir -p eapim-online
# eapim-online/* — 5 modules referenced from settings.gradle
for REPO in elink-online-core elink-online-core-jpa elink-online-transformer elink-online-common elink-online-emsclient; do
rm -rf "eapim-online/$REPO"
git clone --depth=1 --branch master \
http://oauth2:${{ secrets.CI_TOKEN }}@172.30.1.50:3000/djb-eapim/$REPO.git \
"eapim-online/$REPO"
done
# standalone siblings
for REPO in elink-portal-common eapim-admin-djb; do
rm -rf "$REPO"
git clone --depth=1 --branch master \
http://oauth2:${{ secrets.CI_TOKEN }}@172.30.1.50:3000/djb-eapim/$REPO.git \
"$REPO"
done
- name: Verify toolchain
run: |
java -version
gradle --version
- name: Gradle build (no tests)
run: gradle clean build -x test --no-daemon
- name: Upload WAR artifact
uses: http://172.30.1.50:3000/actions/upload-artifact@v3
with:
name: eapim-admin-war-${{ github.sha }}
path: eapim-admin/build/libs/eapim-admin.war
if-no-files-found: error
- name: Sync systemd user unit
working-directory: ${{ github.workspace }}
run: |
install -D -m 644 eapim-admin/deploy/systemd/eapim-admin.service \
"$HOME/.config/systemd/user/eapim-admin.service"
systemctl --user daemon-reload
systemctl --user enable eapim-admin 2>/dev/null || true
- name: Stop tomcat (pre-clean)
working-directory: ${{ github.workspace }}
run: |
# 1) systemd 관리 인스턴스 정지 (이번 워크플로가 띄운 것)
systemctl --user stop eapim-admin 2>/dev/null || true
# 2) systemd 외부 detached 인스턴스 정리
if [ -f "$CATALINA_PID" ] && kill -0 "$(cat $CATALINA_PID)" 2>/dev/null; then
JAVA_HOME=$JAVA_HOME_TOMCAT "$CATALINA_HOME/bin/shutdown.sh" 30 -force || true
for i in $(seq 1 30); do
[ -f "$CATALINA_PID" ] && kill -0 "$(cat $CATALINA_PID)" 2>/dev/null || break
sleep 1
done
fi
rm -f "$CATALINA_PID"
- name: Deploy war as /monitoring
working-directory: ${{ github.workspace }}
run: |
DEPLOY_DIR="$CATALINA_BASE/webapps"
rm -rf "$DEPLOY_DIR/monitoring" "$DEPLOY_DIR/monitoring.war"
cp eapim-admin/build/libs/eapim-admin.war "$DEPLOY_DIR/monitoring.war"
ls -la "$DEPLOY_DIR"
- name: Start tomcat and readiness probe (timeout 120s)
working-directory: ${{ github.workspace }}
run: |
LOG="$CATALINA_BASE/logs/catalina.$(date +%Y-%m-%d).log"
BEFORE=0
[ -f "$LOG" ] && BEFORE=$(stat -c %s "$LOG")
# systemd 가 Tomcat 을 새 cgroup 으로 띄움 — runner step 종료와 무관하게 살아남음
systemctl --user start eapim-admin
DEADLINE=$(($(date +%s) + 120))
STATUS=000
while [ $(date +%s) -lt $DEADLINE ]; do
STATUS=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 3 \
"http://localhost:$DEPLOY_HTTP_PORT/monitoring/login.do" 2>/dev/null || echo "000")
case "$STATUS" in
200|302|401)
echo "Readiness OK (/monitoring/login.do HTTP $STATUS)"
break
;;
esac
if [ -f "$LOG" ] && tail -c +$((BEFORE+1)) "$LOG" | grep -qE "Application listener .* Exception|SEVERE.*startup.Catalina"; then
echo "::error::Startup error detected in log"
tail -c +$((BEFORE+1)) "$LOG" | tail -80
exit 1
fi
sleep 2
done
case "$STATUS" in
200|302|401) ;;
*)
echo "::error::Readiness probe failed within 120s (last HTTP status: $STATUS)"
[ -f "$LOG" ] && tail -c +$((BEFORE+1)) "$LOG" | tail -100
exit 1
;;
esac
echo "--- key boot log lines ---"
tail -c +$((BEFORE+1)) "$LOG" | grep -E "Server startup|Deployment of web" | head -10 || true
+3
View File
@@ -246,3 +246,6 @@ eapim-admin-kjb/
*.report.md
*.claude.md
tmpclaude-*-cwd
logs
rinjae_
tomcat-base
+36 -28
View File
@@ -10,7 +10,7 @@ AI 어시스턴트가 작업을 시작하기 전에 반드시 이해해야 할
- **신규 기능**: 항상 **JPA**와 Spring Data 리포지토리를 사용해야 합니다.
- **레거시 코드**: 기존 iBATIS (`*.xml` 매퍼) 코드를 수정할 때는 해당 패턴을 따라야 합니다.
- **멀티 모듈 Gradle 프로젝트**: `settings.gradle`에 정의된 것처럼 여러 하위 모듈(`elink-online-*`, `kjb-*` 등)로 구성되어 있습니다. 특정 기능은 다른 모듈에 위치할 수 있습니다.
- **QueryDSL 코드 생성**: 빌드 시 `QueryDSL`을 사용하여 Q-class가 자동으로 생성됩니다. IDE에서 엔티티를 찾을 수 없다고 표시되면, `kjb-gradle.sh compileJava`를 먼저 실행하여 코드를 생성해야 합니다.
- **QueryDSL 코드 생성**: 빌드 시 `QueryDSL`을 사용하여 Q-class가 자동으로 생성됩니다. IDE에서 엔티티를 찾을 수 없다고 표시되면, `./gradlew compileJava` 를 먼저 실행하여 코드를 생성해야 합니다.
- **고객사별 커스터마이징**: `ext.kjb` 또는 `custom` 패키지는 특정 고객사(광주은행)를 위한 코드를 포함하므로, 일반적인 기능 수정 시에는 주의가 필요합니다.
## 프로젝트 개요
@@ -39,50 +39,48 @@ eLink EMS (eLink Management System)는 eLink의 웹 기반 관리 서비스로,
### 빌드 명령어
**⚠️ 중요**: 환경변수 문제로 인해 `gradle` 대신 `kjb-gradle.sh` 스크립트를 사용해야 합니다.
- 스크립트 위치: `/c/eactive/workspaces/shell-scripts/kjb-gradle.sh`
- PATH에 등록되어 있으므로 바로 `kjb-gradle.sh` 명령어 사용 가능
프로젝트 루트의 Gradle wrapper(`./gradlew`)를 사용합니다. JDK 8 toolchain 위치는 `.envrc` 등으로 잡습니다(아래 "기술 스택" 섹션 참조).
```bash
# 표준 빌드
kjb-gradle.sh build
./gradlew build
# Weblogic 배포용 빌드 (테스트 제외)
kjb-gradle.sh build -x test -Pprofile=weblogic
./gradlew build -x test -Pprofile=weblogic
# WAR 파일 빌드
kjb-gradle.sh war
./gradlew war
# 클린 빌드
kjb-gradle.sh clean build
./gradlew clean build
```
### 테스트 실행
```bash
# 모든 테스트 실행
kjb-gradle.sh test
./gradlew test
# 특정 테스트 클래스 실행
kjb-gradle.sh test --tests "com.example.ClassName"
./gradlew test --tests "com.example.ClassName"
# 특정 테스트 패키지 실행 (JUnit 플랫폼 사용)
kjb-gradle.sh test --tests "com.eactive.eai.rms.*"
./gradlew test --tests "com.eactive.eai.rms.*"
```
### 개발 태스크
```bash
# 패키징 없이 클래스만 컴파일
kjb-gradle.sh classes
./gradlew classes
# QueryDSL Q-classes 및 기타 애노테이션 생성
kjb-gradle.sh compileJava
./gradlew compileJava
# 의존성 트리 보기
kjb-gradle.sh dependencies
./gradlew dependencies
# 사용 가능한 모든 태스크 목록
kjb-gradle.sh tasks --all
./gradlew tasks --all
```
## 로컬 개발 빠른 시작
@@ -92,26 +90,26 @@ kjb-gradle.sh tasks --all
1. **IDE 설정 파일 생성 (최초 1회)**
```bash
# Eclipse 사용 시 (기본)
kjb-gradle.sh eclipse
./gradlew eclipse
# IntelliJ IDEA 사용 시 (build.gradle.intellij 파일 사용 필요)
# 1. build.gradle을 build.gradle.eclipse로 백업
# 2. build.gradle.intellij를 build.gradle로 복사
# 3. kjb-gradle.sh idea 실행
# 3. ./gradlew idea 실행
```
2. **QueryDSL 등 소스 코드 생성**
```bash
kjb-gradle.sh compileJava
./gradlew compileJava
```
3. **전체 빌드 및 테스트 실행**
```bash
kjb-gradle.sh build
./gradlew build
```
4. **IDE에서 프로젝트 열기**
- Eclipse: `kjb-gradle.sh eclipse` 실행 후, IDE에서 프로젝트를 엽니다.
- Eclipse: `./gradlew eclipse` 실행 후, IDE에서 프로젝트를 엽니다.
- IntelliJ: IntelliJ 전용 `build.gradle.intellij`를 `build.gradle`로 교체 후 사용합니다.
5. **실행 구성(Run Configuration) 설정**
@@ -162,14 +160,14 @@ eapim-admin (root project)
#### Q-Class 생성 위치
Q-classes는 `build/generated/java/` 디렉토리에 생성됩니다.
- `kjb-gradle.sh compileJava` 또는 `kjb-gradle.sh build` 실행 시 Gradle이 자동으로 생성
- `./gradlew compileJava` 또는 `./gradlew build` 실행 시 Gradle이 자동으로 생성
- 이 디렉토리는 `.gitignore`에 포함되어 있으며 커밋해서는 안 됩니다
#### 생성된 코드 작업하기
- **Gradle 빌드**: `build/generated/java/`의 Q-classes가 자동으로 classpath에 포함됩니다
- **업데이트 pull 후**: `kjb-gradle.sh compileJava`를 실행하여 Q-classes를 재생성합니다
- **Q-classes IDE 오류 시**: `kjb-gradle.sh clean compileJava`로 재생성합니다
- **업데이트 pull 후**: `./gradlew compileJava` 를 실행하여 Q-classes를 재생성합니다
- **Q-classes IDE 오류 시**: `./gradlew clean compileJava` 로 재생성합니다
#### IDE별 빌드 설정 파일
@@ -255,12 +253,21 @@ IntelliJ를 사용하려면 `build.gradle.intellij`를 `build.gradle`로 교체
WebLogic용 빌드 시, DefaultServlet 문제를 해결하기 위해 `web.xml`을 `weblogic-web.xml`로 교체하는 `weblogic` 프로필을 사용합니다:
```bash
kjb-gradle.sh build -x test -Pprofile=weblogic
./gradlew build -x test -Pprofile=weblogic
```
## 기술 스택
- **Java 8**: Gradle toolchain을 통해 강제
- **Java 8**: Gradle toolchain을 통해 강제 (`JavaLanguageVersion.of(8)`). 프로젝트의 `gradle.properties` 에는 JDK 경로를 기입하지 않습니다 — 개인 환경별로 `.envrc`(direnv) 또는 셸 rc 파일에서 `JAVA_HOME` 을 JDK 8 위치로 export 하세요. Gradle 8 의 toolchain auto-detect 가 `JAVA_HOME` 도 후보 경로로 인식합니다.
```bash
# 예: 프로젝트 루트의 .envrc (Zulu 8 / macOS Apple Silicon)
JAVA_HOME=${HOME}/opts/jdks/zulu8.94.0.17-ca-jdk8.0.492-macosx_aarch64
GRADLE_USER_HOME=${HOME}/eactive/djb-eapim/gradle-home
export PATH=${JAVA_HOME}/bin:${PATH}
```
Gradle 표준 위치(`/Library/Java/JavaVirtualMachines/...`, Homebrew, SDKMAN 등)에 설치한 경우는 별도 설정 없이도 auto-detect 됩니다.
- **Spring Framework 5.3.27**: 코어 프레임워크 (MVC, Data JPA)
- **Spring Data JPA 2.5.2**: 리포지토리 추상화
- **Hibernate 5.4.33/5.6.15**: ORM 구현 (JPA/ORM)
@@ -271,9 +278,10 @@ kjb-gradle.sh build -x test -Pprofile=weblogic
- **Jackson 2.13.1**: JSON 처리
- **Logback 1.2.10**: 로깅 프레임워크 (SLF4J와 함께)
- **AWS SDK 2.20.142**: S3 연동
- **Kubernetes Client 18.0.1**: K8s 관리
- **EhCache**: 캐싱, **Lombok**: 코드 생성, **MapStruct**: 객체 매핑
> Kubernetes Client 의존성은 JDK 8 호환 문제로 제거됨(`io.kubernetes:client-java:18.0.1` 은 JDK 11+ 필요). K8s 모드 기능이 필요하면 `≤13.0.2` 로 다운그레이드 후 재도입 검토.
## 프로젝트 규칙
### 패키지 구조
@@ -376,7 +384,7 @@ com.eactive.eai
### IntelliJ IDEA (권장)
```bash
kjb-gradle.sh idea
./gradlew idea
```
또는 IntelliJ에서 직접 "Import Gradle Project"를 사용합니다.
@@ -395,7 +403,7 @@ cp build.gradle build.gradle.intellij
cp build.gradle.eclipse build.gradle
# 3. Eclipse 프로젝트 생성
kjb-gradle.sh eclipse
./gradlew eclipse
```
Eclipse 전용 설정(`build.gradle.eclipse`)은 다음을 포함합니다:
@@ -115,7 +115,7 @@
</bean>
<bean id="ioAcceptor" class="com.eactive.eai.rms.onl.server.UdpServer" init-method="init" destroy-method="destory">
<property name="port" value="10800" />
<property name="port" value="39126" />
<property name="handler" ref="handler"/>
</bean>
@@ -17,6 +17,7 @@
<script language="javascript" >
var url = '<c:url value="/onl/apim/approval/portalApprovalMan.json" />';
var url_view = '<c:url value="/onl/apim/approval/portalApprovalMan.view" />';
var hardDeleteEnabled = ${hardDeleteEnabled};
const APPROVAL_TYPE_DISPLAY = {
'USER': '법인사용자',
'APP': 'API 사용'
@@ -30,6 +31,46 @@
cellvalue.substr(6, 2) ;
}
function deleteSelectedApprovals() {
var selectedIds = $("#grid").jqGrid('getGridParam', 'selarrrow');
if (selectedIds.length === 0) {
showAlert("삭제할 승인 요청을 선택해주세요.", {type: 'warning'});
return;
}
if (!hardDeleteEnabled) {
showAlert("완전삭제 기능이 비활성화되어 있습니다.", {type: 'error'});
return;
}
var confirmMsg = "선택된 " + selectedIds.length + "건을 완전삭제(DB 영구 삭제)하시겠습니까?";
showConfirm(confirmMsg, {
type: 'error',
onConfirm: function() {
showConfirm("최종 확인: 완전삭제는 되돌릴 수 없습니다. 정말 실행하시겠습니까?", {
type: 'error',
title: '최종 확인',
onConfirm: function() {
$.ajax({
type: "POST", url: url, dataType: "json",
data: { cmd: 'HARD_DELETE_MULTIPLE', ids: selectedIds.join(',') },
success: function(data) {
showAlert(data.message, {
type: data.status === 'success' ? 'success' : 'error',
onClose: function() { $("#grid").trigger("reloadGrid"); }
});
},
error: function(e) {
showAlert("완전삭제 오류: " + e.responseText, {type: 'error'});
}
});
}
});
}
});
}
$(document).ready(function() {
$('#grid').jqGrid({
datatype: "json",
@@ -62,6 +103,8 @@
viewrecords: true,
autowidth: true,
height: 'auto',
multiselect: true,
multiboxonly: true,
ondblClickRow: function(rowId) {
var rowData = $(this).getRowData(rowId);
var id = rowData['id'];
@@ -102,6 +145,10 @@
$("#grid").setGridParam({ url: url, postData: postData, page: 1 }).trigger("reloadGrid");
});
$("#btn_delete_selected").click(function() {
deleteSelectedApprovals();
});
$("select[name^=search], input[name^=search]").keydown(function(key) {
if (key.keyCode == 13) {
$("#btn_search").click();
@@ -121,9 +168,17 @@
</div><!-- end content_top -->
<div class="content_middle" id="content_middle">
<div class="search_wrap">
<c:if test="${hardDeleteEnabled}">
<button type="button" class="cssbtn" id="btn_delete_selected" level="W" style="background-color: #dc3545; border-color: #dc3545; color: white;"><i class="material-icons">delete_forever</i> 선택 삭제</button>
</c:if>
<button type="button" class="cssbtn" id="btn_search" level="R"><i class="material-icons">search</i> <%= localeMessage.getString("button.search") %></button>
</div>
<div class="title">승인 요청 목록<span class="tooltip">승인 요청을 관리합니다.</span></div>
<c:if test="${hardDeleteEnabled}">
<div style="background:#fff3cd; border:1px solid #ffc107; padding:8px 15px; margin:5px 0; border-radius:4px; color:#856404;">
<strong>[주의]</strong> 선택 삭제 시 승인 요청과 승인자 이력이 DB에서 영구 삭제됩니다.
</div>
</c:if>
<form id="ajaxForm" onsubmit="return false;">
<table class="search_condition" cellspacing="0">
<tbody>
+10 -6
View File
@@ -201,14 +201,13 @@ dependencies {
}
implementation 'software.amazon.awssdk:sso:2.20.142'
implementation 'software.amazon.awssdk:sts:2.20.142'
implementation ('io.kubernetes:client-java:18.0.1') {
exclude group: 'org.slf4j', module: 'slf4j-api'
exclude group: 'org.slf4j', module: 'logback-classic'
}
implementation group: 'commons-net', name: 'commons-net', version: '3.5'
// JDK 8 의 rt.jar 에는 org.w3c.dom.ElementTraversal 이 없어 xercesImpl 가 NCDFE 를 일으킴.
// xml-apis 1.4.01 에 그 클래스가 포함됨. 직접 의존성으로 묶어 WAR 에 패키징되도록 함.
implementation 'xml-apis:xml-apis:1.4.01'
testRuntimeOnly 'com.h2database:h2:2.1.214'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
@@ -228,6 +227,11 @@ configurations.all {
cacheDynamicVersionsFor 10, 'minutes'
// Do not cache changing modules
cacheChangingModulesFor 0, 'seconds'
// JDK 8 의 rt.jar 에는 org.w3c.dom.ElementTraversal 이 없음.
// 일부 transitive 가 끌어오는 xml-apis:1.0.b2 (2002, DOM L2) 는 이 클래스를 포함하지 않아
// xercesImpl 가 NoClassDefFoundError 를 일으킴 → 1.4.01 로 강제 통일.
force 'xml-apis:xml-apis:1.4.01'
}
}
+51
View File
@@ -0,0 +1,51 @@
# DJBank EAPIM Admin — user-scope systemd unit
#
# Install (one-time, on the host as `djb`):
# sudo loginctl enable-linger djb # allow services to outlive ssh logout
# mkdir -p ~/.config/systemd/user
# # workflow copies this file in on each deploy; for first boot you can also do:
# # cp /prod/.../deploy/systemd/eapim-admin.service ~/.config/systemd/user/
# systemctl --user daemon-reload
# systemctl --user enable eapim-admin
# systemctl --user start eapim-admin
#
# Why this exists:
# - `catalina.sh start` + `nohup &` does NOT escape the Gitea Actions runner
# process group. When the workflow step finishes, the runner sends SIGTERM
# to the process tree and Tomcat dies seconds after boot.
# - Running Tomcat as a systemd user service detaches lifecycle from the
# runner entirely. The workflow just calls `systemctl --user restart` and
# systemd owns the process.
[Unit]
Description=DJBank EAPIM Admin (Tomcat 9)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
Environment=JAVA_HOME=/apps/opts/jdk17
Environment=CATALINA_BASE=/prod/eapim/adminportal
Environment=CATALINA_HOME=/prod/eapim/apache-tomcat-9.0.116
Environment=CATALINA_PID=/prod/eapim/adminportal/temp/catalina.pid
# `catalina.sh run` keeps Tomcat in the foreground so systemd can supervise it.
ExecStart=/prod/eapim/apache-tomcat-9.0.116/bin/catalina.sh run
# Graceful shutdown via Tomcat's shutdown port, 30s window.
ExecStop=/prod/eapim/apache-tomcat-9.0.116/bin/catalina.sh stop 30
# Tomcat returns 143 on SIGTERM during normal stop — treat as success.
SuccessExitStatus=143
TimeoutStartSec=180
TimeoutStopSec=60
Restart=on-failure
RestartSec=10
# Memory / fd limits — adjust as needed.
LimitNOFILE=65535
[Install]
WantedBy=default.target
+17 -207
View File
@@ -9,7 +9,7 @@
## 기술 스택
- Spring Framework 5.3.27
- Java 8
- Java 17
- Gradle 8.7
- Oracle 19c
- Spring Data JPA 2.5.2
@@ -23,251 +23,61 @@
### 사전 요구사항
- JDK 8 이상
- JDK 17 이상
- Gradle 8.7
- Git
- Oracle 19c (또는 개발 환경에 따라 접근 가능한 DB)
### Git Bash에서 초기 세팅
```bash
# ====================================
# 1. 변수 설정 (프로젝트 경로 및 브랜치)
# ====================================
export WORKSPACE_DIR="/c/eactive/workspaces/kjb-eapim"
export PROJECT_BRANCH="jenkins_with_weblogic"
# Git Repository URL 설정
# 기본: ssh://git@192.168.240.178:18081/eapim
# 대체: https://git.eactive.synology.me:8090/kjb-eapim
export GIT_REPO_BASE="ssh://git@192.168.240.178:18081/eapim"
# export GIT_REPO_BASE="https://git.eactive.synology.me:8090/kjb-eapim"
# Gradle 명령어 설정
# 일반 환경: gradle
# msi-gf63 환경: /c/eactive/workspaces/shell-scripts/kjb-gradle.sh
export GRADLE_CMD="gradle"
# export GRADLE_CMD="/c/eactive/workspaces/shell-scripts/kjb-gradle.sh"
# ====================================
# 2. 작업 디렉토리 생성
# ====================================
mkdir -p $WORKSPACE_DIR
cd $WORKSPACE_DIR
# ====================================
# 3. eapim-admin 프로젝트 클론
# ====================================
git clone ${GIT_REPO_BASE}/eapim-admin.git
cd eapim-admin
# 프로젝트 브랜치로 체크아웃
git checkout $PROJECT_BRANCH
# ====================================
# 4. eapim-online 프로젝트 클론 (코어 모듈 포함)
# ====================================
cd $WORKSPACE_DIR
git clone ${GIT_REPO_BASE}/eapim-online.git
cd eapim-online
git checkout $PROJECT_BRANCH
# ====================================
# 5. 의존 모듈 클론
# ====================================
cd $WORKSPACE_DIR
# elink-portal-common 클론
git clone ${GIT_REPO_BASE}/elink-portal-common.git
cd elink-portal-common
git checkout master # 또는 필요한 브랜치
# kjb-safedb 클론
cd $WORKSPACE_DIR
git clone ${GIT_REPO_BASE}/kjb-safedb.git
cd kjb-safedb
git checkout master # 또는 필요한 브랜치
# ====================================
# 6. 최종 폴더 구조 확인
# ====================================
# kjb-eapim/
# ├── eapim-admin/ # Admin 관리 콘솔 (현재 프로젝트)
# ├── eapim-online/ # Online 게이트웨이 코어 모듈
# │ ├── elink-online-core/
# │ ├── elink-online-core-jpa/
# │ ├── elink-online-transformer/
# │ ├── elink-online-common/
# │ ├── elink-online-emsclient/
# │ └── elink-online-adapter/
# ├── elink-portal-common/ # 포털 공통 컴포넌트 (JPA 엔티티)
# └── kjb-safedb/ # SafeDB 암호화 라이브러리
# ====================================
# 7. eapim-admin 프로젝트로 이동 및 디펜던시 다운로드
# ====================================
cd $WORKSPACE_DIR/eapim-admin
# Gradle 디펜던시 다운로드
$GRADLE_CMD dependencies
```
### Windows CMD에서 초기 세팅
```cmd
REM ====================================
REM 1. 변수 설정 (프로젝트 경로 및 브랜치)
REM ====================================
set WORKSPACE_DIR=C:\eactive\workspaces\kjb-eapim
set PROJECT_BRANCH=jenkins_with_weblogic
REM Git Repository URL 설정
REM 기본: ssh://git@192.168.240.178:18081/eapim
REM 대체: https://git.eactive.synology.me:8090/kjb-eapim
set GIT_REPO_BASE=ssh://git@192.168.240.178:18081/eapim
REM set GIT_REPO_BASE=https://git.eactive.synology.me:8090/kjb-eapim
REM Gradle 명령어 설정
REM 일반 환경: gradle
REM msi-gf63 환경: C:\eactive\workspaces\shell-scripts\kjb-gradle.sh
set GRADLE_CMD=gradle
REM set GRADLE_CMD=C:\eactive\workspaces\shell-scripts\kjb-gradle.sh
REM ====================================
REM 2. 작업 디렉토리 생성
REM ====================================
if not exist "%WORKSPACE_DIR%" mkdir "%WORKSPACE_DIR%"
cd /d "%WORKSPACE_DIR%"
REM ====================================
REM 3. eapim-admin 프로젝트 클론
REM ====================================
git clone %GIT_REPO_BASE%/eapim-admin.git
cd eapim-admin
REM 프로젝트 브랜치로 체크아웃
git checkout %PROJECT_BRANCH%
REM ====================================
REM 4. eapim-online 프로젝트 클론 (코어 모듈 포함)
REM ====================================
cd /d "%WORKSPACE_DIR%"
git clone %GIT_REPO_BASE%/eapim-online.git
cd eapim-online
git checkout %PROJECT_BRANCH%
REM ====================================
REM 5. 의존 모듈 클론
REM ====================================
cd /d "%WORKSPACE_DIR%"
REM elink-portal-common 클론
git clone %GIT_REPO_BASE%/elink-portal-common.git
cd elink-portal-common
git checkout master
REM kjb-safedb 클론
cd /d "%WORKSPACE_DIR%"
git clone %GIT_REPO_BASE%/kjb-safedb.git
cd kjb-safedb
git checkout master
REM ====================================
REM 6. 최종 폴더 구조 확인
REM ====================================
REM kjb-eapim\
REM ├── eapim-admin\ # Admin 관리 콘솔 (현재 프로젝트)
REM ├── eapim-online\ # Online 게이트웨이 코어 모듈
REM │ ├── elink-online-core\
REM │ ├── elink-online-core-jpa\
REM │ ├── elink-online-transformer\
REM │ ├── elink-online-common\
REM │ ├── elink-online-emsclient\
REM │ └── elink-online-adapter\
REM ├── elink-portal-common\ # 포털 공통 컴포넌트 (JPA 엔티티)
REM └── kjb-safedb\ # SafeDB 암호화 라이브러리
REM ====================================
REM 7. eapim-admin 프로젝트로 이동 및 디펜던시 다운로드
REM ====================================
cd /d "%WORKSPACE_DIR%\eapim-admin"
REM Gradle 디펜던시 다운로드
%GRADLE_CMD% dependencies
```
### 환경별 설정 파일
### 환경별 설정 파일
프로젝트 클론 후 필요한 환경 설정을 수정하세요:
- `WebContent/WEB-INF/properties/env.D.properties` - 개발 환경
- `WebContent/WEB-INF/properties/env.T.properties` - 테스트/스테이징 환경
- `WebContent/WEB-INF/properties/env.P.properties` - 운영 환경
### 주의사항
- **gradlew 사용 금지**: 반드시 시스템에 설치된 gradle을 직접 사용하거나 환경에 맞는 스크립트를 사용하세요
- **Gradle 명령어 변수화**: 위의 `GRADLE_CMD` 변수를 설정하면 환경별로 다른 gradle 명령어 사용 가능
### 주의사항
- **SafeDB**: 암호화 기능이 필요한 경우 `-Dkjb_safedb.mode=fake` 설정 (개발 환경) 또는 실제 SafeDB 라이브러리 설치
- **멀티 모듈 구조**: eapim-online의 여러 모듈들을 참조하므로 반드시 eapim-online도 클론해야 합니다
## 빌드 및 실행
### Gradle 명령어 설정
### Gradle 명령어
사용 환경에 따라 적절한 gradle 명령어를 설정하세요:
**Git Bash:**
```bash
# 일반 환경
export GRADLE_CMD="gradle"
# msi-gf63 환경
export GRADLE_CMD="/c/eactive/workspaces/shell-scripts/kjb-gradle.sh"
```
**Windows CMD:**
```cmd
REM 일반 환경
set GRADLE_CMD=gradle
REM msi-gf63 환경
set GRADLE_CMD=C:\eactive\workspaces\shell-scripts\kjb-gradle.sh
```
프로젝트 루트의 Gradle wrapper(`./gradlew`) 를 사용합니다.
### 빌드 및 실행 명령어
```bash
# 표준 빌드
$GRADLE_CMD build
./gradlew build
# Weblogic 배포용 빌드 (테스트 제외)
$GRADLE_CMD build -x test -Pprofile=weblogic
./gradlew build -x test -Pprofile=weblogic
# WAR 파일 빌드
$GRADLE_CMD war
# WAR 파일 빌드[ems_stdout.log](../../logs/emsSvr-RinjaeMA/ems_stdout.log)
./gradlew war
# 클린 빌드
$GRADLE_CMD clean build
./gradlew clean build
# 테스트 실행
$GRADLE_CMD test
./gradlew test
# 패키징 없이 클래스만 컴파일
$GRADLE_CMD classes
./gradlew classes
# QueryDSL Q-classes 및 기타 애노테이션 생성
$GRADLE_CMD compileJava
./gradlew compileJava
# 의존성 트리 보기
$GRADLE_CMD dependencies
./gradlew dependencies
# 사용 가능한 모든 태스크 목록
$GRADLE_CMD tasks --all
./gradlew tasks --all
```
**주의**: gradlew 사용 금지 - offline gradle 단독 실행
## 프로젝트 구조
### 전체 프로젝트 구조
@@ -14,6 +14,7 @@ public class PortalApprovalAuthorizer implements ApprovalAuthorizer {
return approval.getApprovers().stream()
.filter(this::isCurrentApprover)
.map(this::getApproverId)
.filter(java.util.Objects::nonNull)
.anyMatch(id -> id.equals(approverId));
}
@@ -22,6 +23,6 @@ public class PortalApprovalAuthorizer implements ApprovalAuthorizer {
}
private String getApproverId(Approver approver) {
return approver.getUser().getId();
return approver.getUser() != null ? approver.getUser().getId() : null;
}
}
@@ -13,10 +13,16 @@ 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.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Controller
@RequiredArgsConstructor
public class PortalApprovalManController extends BaseAnnotationController {
@@ -24,8 +30,8 @@ public class PortalApprovalManController extends BaseAnnotationController {
private final PortalApprovalManService portalApprovalManService;
@GetMapping(value = "/onl/apim/approval/portalApprovalMan.view")
public void view() {
// view
public void view(Model model) {
model.addAttribute("hardDeleteEnabled", portalApprovalManService.isHardDeleteEnabled());
}
@GetMapping(value = "/onl/apim/approval/portalApprovalMan.view", params = "cmd=DETAIL")
@@ -76,7 +82,49 @@ public class PortalApprovalManController extends BaseAnnotationController {
portalApprovalManService.redeploy(id);
return ResponseEntity.ok().build();
}
@PostMapping(value = "/onl/apim/approval/portalApprovalMan.json", params = "cmd=HARD_DELETE_MULTIPLE")
public ResponseEntity<Map<String, Object>> hardDeleteMultiple(String ids) {
Map<String, Object> resultMap = new HashMap<>();
try {
if (StringUtils.isBlank(ids)) {
resultMap.put("status", "fail");
resultMap.put("message", "삭제할 승인 요청을 선택해주세요.");
return ResponseEntity.ok(resultMap);
}
String[] idArray = ids.split(",");
int successCount = 0;
List<String> failMessages = new ArrayList<>();
for (String id : idArray) {
if (StringUtils.isNotBlank(id.trim())) {
try {
portalApprovalManService.hardDelete(id.trim());
successCount++;
} catch (BizException e) {
failMessages.add(e.getMessage());
} catch (Exception e) {
failMessages.add(id.trim() + ": " + e.getMessage());
}
}
}
if (failMessages.isEmpty()) {
resultMap.put("status", "success");
resultMap.put("message", successCount + "건의 승인 요청이 완전삭제되었습니다.");
} else {
resultMap.put("status", "fail");
resultMap.put("message", "완전삭제 실패: " + String.join(", ", failMessages)
+ (successCount > 0 ? " (" + successCount + "건 성공)" : ""));
}
} catch (Exception e) {
resultMap.put("status", "fail");
resultMap.put("message", "완전삭제 중 오류가 발생했습니다: " + e.getMessage());
}
return ResponseEntity.ok(resultMap);
}
@PostMapping(value = "/onl/apim/approval/portalApprovalMan.json", params = "cmd=UPDATE")
public ResponseEntity<Void> update(String id, String expectEndDate) {
portalApprovalManService.update(id, expectEndDate);
@@ -6,6 +6,7 @@ import com.eactive.apim.portal.apprequest.repository.AppRequestRepository;
import com.eactive.apim.portal.approval.entity.Approval;
import com.eactive.apim.portal.approval.entity.ApprovalType;
import com.eactive.apim.portal.approval.statemachine.*;
import com.eactive.apim.portal.portalproperty.service.PortalPropertyService;
import com.eactive.apim.portal.portaluser.entity.PortalUser;
import com.eactive.apim.portal.portaluser.repository.PortalUserRepository;
import com.eactive.eai.data.entity.onl.apim.apigroup.ApiGroup;
@@ -25,6 +26,7 @@ import com.eactive.eai.rms.onl.apim.portalorg.PortalOrgManService;
import com.eactive.eai.rms.onl.apim.portaluser.PortalUserManService;
import com.eactive.eai.rms.onl.apim.portaluser.PortalUserUI;
import com.eactive.eai.rms.common.acl.user.ui.UserUI;
import com.eactive.eai.rms.onl.common.exception.BizException;
import com.eactive.eai.rms.onl.transaction.apim.ApiInterfaceService;
import com.eactive.eai.rms.onl.transaction.apim.mapping.ApiSpecUIMapper;
import com.eactive.eai.rms.onl.transaction.apim.ui.ApiInterfaceUI;
@@ -36,6 +38,8 @@ import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.*;
@Service
@@ -43,6 +47,9 @@ import java.util.*;
@RequiredArgsConstructor
public class PortalApprovalManService extends BaseService {
private static final String PORTAL_PROPERTY_GROUP = "Portal";
private static final String HARD_DELETE_FLAG_KEY = "approval.hard-delete.enabled";
private final ApprovalService approvalService;
private final ApprovalStateMachine approvalStateMachine;
private final AppRequestRepository appRequestRepository;
@@ -59,6 +66,10 @@ public class PortalApprovalManService extends BaseService {
private final ApiGroupUIMapper apiGroupUIMapper;
private final PortalApprovalAuthorizer portalApprovalAuthorizer;
private final PortalOrgManService portalOrgManService;
private final PortalPropertyService portalPropertyService;
@PersistenceContext(unitName = "entityManagerFactoryForEMS")
private EntityManager entityManager;
public Page<PortalApprovalUI> selectList(Pageable pageable, PortalApprovalUISearch portalApprovalUISearch) {
Page<Approval> approval = approvalService.findAll(pageable, portalApprovalUISearch);
@@ -347,6 +358,28 @@ public class PortalApprovalManService extends BaseService {
}
public boolean isHardDeleteEnabled() {
Map<String, String> properties = portalPropertyService.getPortalPropertiesAsMap(PORTAL_PROPERTY_GROUP);
return Boolean.parseBoolean(properties.getOrDefault(HARD_DELETE_FLAG_KEY, "false"));
}
public void hardDelete(String id) {
if (!isHardDeleteEnabled()) {
throw new BizException("승인 요청 완전삭제 기능이 비활성화되어 있습니다. (PTL_PROPERTY: " + HARD_DELETE_FLAG_KEY + ")");
}
// Approval.approvers 가 EAGER + Approver.user 가 NotFound 처리 없음 → 사라진 UserInfo 로 인해
// findById/deleteById 가 hydration 단계에서 실패하는 row 가 존재. 네이티브 SQL 로 우회.
entityManager.createNativeQuery("DELETE FROM PTL_APPROVER WHERE APPROVAL_ID = :id")
.setParameter("id", id)
.executeUpdate();
int affected = entityManager.createNativeQuery("DELETE FROM PTL_APPROVAL WHERE ID = :id")
.setParameter("id", id)
.executeUpdate();
if (affected == 0) {
throw new BizException("승인 정보를 찾을 수 없습니다. ID: " + id);
}
}
public void sendEvent(String id, ApprovalEvent event, Map<String, Object> options) {
approvalService.findById(id).ifPresent(approval -> {
options.put("authorizer", portalApprovalAuthorizer);
@@ -6,7 +6,6 @@ import com.eactive.apim.portal.apprequest.repository.AppRequestRepository;
import com.eactive.apim.portal.approval.entity.Approval;
import com.eactive.apim.portal.approval.service.ApprovalDeployException;
import com.eactive.apim.portal.approval.statemachine.listener.ApprovalListener;
import com.eactive.apim.portal.portalproperty.service.PortalPropertyService;
import com.eactive.apim.portal.template.entity.MessageCode;
import com.eactive.apim.portal.template.service.MessageRecipient;
import com.eactive.apim.portal.template.service.MessageSendService;
@@ -19,10 +18,8 @@ import com.eactive.eai.rms.onl.apim.approval.RandomStringGenerator;
import com.eactive.eai.rms.onl.apim.approval.credential.CredentialManService;
import com.eactive.eai.rms.onl.apim.approval.credential.CredentialUI;
import com.eactive.eai.rms.onl.apim.approval.credential.CredentialUIMapper;
import com.eactive.eai.rms.onl.apim.portaluser.PortalUserUI;
import com.eactive.eai.rms.onl.common.service.AgentUtilService;
import com.eactive.eai.rms.onl.manage.authserver.client.ClientManService;
import com.eactive.eai.rms.onl.manage.authserver.client.ClientUI;
import com.eactive.eai.rms.onl.transaction.apim.ui.ApiSpecInfoUI;
import java.util.Arrays;
import java.util.Collections;
@@ -35,22 +32,12 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang.StringUtils;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.HttpClientBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;
@Service
@RequiredArgsConstructor
@@ -116,9 +103,15 @@ public class PortalAppApprovalListener implements ApprovalListener {
}
private void sendApprovalResult(AppRequest appRequest) {
com.eactive.apim.portal.portaluser.entity.PortalUser requester =
appRequest.getApproval() != null ? appRequest.getApproval().getRequester() : null;
if (requester == null) {
logger.warn("승인 결과 통지 생략 - 요청자 정보 없음. appRequestId: {}", appRequest.getId());
return;
}
MessageRecipient recipient = new MessageRecipient();
recipient.setPhone(appRequest.getApproval().getRequester().getMobileNumber());
recipient.setUserId(appRequest.getApproval().getRequester().getEmailAddr());
recipient.setPhone(requester.getMobileNumber());
recipient.setUserId(requester.getEmailAddr());
Map<String, String> params = new HashMap<>();
params.put("apiKey", appRequest.getClientName());
messageSendService.sendMessage(MessageCode.APP_REGISTER_APPROVED, recipient, params);
@@ -165,8 +158,8 @@ public class PortalAppApprovalListener implements ApprovalListener {
credentialUI.setClientname(appRequest.getClientName());
credentialUI.setScope("api");
credentialUI.setGranttypes("client_credentials");
credentialUI.setOrgid(appRequest.getOrg().getId());
credentialUI.setOrgname(appRequest.getOrg().getOrgName());
credentialUI.setOrgid(appRequest.getOrg() != null ? appRequest.getOrg().getId() : null);
credentialUI.setOrgname(appRequest.getOrg() != null ? appRequest.getOrg().getOrgName() : null);
credentialUI.setAllowedips(appRequest.getIpWhitelist());
credentialUI.setRedirecturi(appRequest.getCallbackUrl());
credentialUI.setAppIconFileId(appRequest.getAppIconFileId());
@@ -27,6 +27,8 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
@@ -48,6 +50,9 @@ public class PortalOrgManService extends BaseService {
private final PortalUserCorpManagerUIMapper portalUserCorpManagerUIMapper;
private final FileService fileService;
@PersistenceContext(unitName = "entityManagerFactoryForEMS")
private EntityManager entityManager;
public Page<PortalOrgUI> selectList(Pageable pageable, PortalOrgUISearch portalOrgUISearch) {
Page<PortalOrg> portalOrg = portalOrgService.findAll(pageable, portalOrgUISearch);
return portalOrg.map(entity -> convertToUIWithMaskOption(entity, true));
@@ -146,19 +151,37 @@ public class PortalOrgManService extends BaseService {
throw new BizException("삭제(탈퇴) 상태인 법인만 완전삭제할 수 있습니다.");
}
List<String> dependencies = new ArrayList<>();
// 사용자(PortalUser)는 별도 화면에서 정리해야 하므로 cascade 하지 않고 차단
long userCount = portalUserService.countByOrgId(id);
if (userCount > 0) dependencies.add("사용자 " + userCount + "");
long appRequestCount = portalOrgService.countAppRequestByOrgId(id);
if (appRequestCount > 0) dependencies.add("앱 신청 " + appRequestCount + "");
if (!dependencies.isEmpty()) {
throw new BizException("의존 데이터가 존재하여 완전삭제할 수 없습니다: " + String.join(", ", dependencies));
if (userCount > 0) {
throw new BizException("의존 데이터가 존재하여 완전삭제할 수 없습니다: 사용자 " + userCount + "");
}
// 앱 신청(AppRequest) + 연결된 승인(Approval/Approver) 은 cascade 삭제
cascadeDeleteAppRequestsByOrg(id);
portalOrgService.deleteById(id);
}
private void cascadeDeleteAppRequestsByOrg(String orgId) {
// 1) 해당 org 의 app_request 가 참조하는 approval 의 approver 제거
entityManager.createNativeQuery(
"DELETE FROM PTL_APPROVER WHERE APPROVAL_ID IN " +
"(SELECT APPROVAL_ID FROM PTL_APP_REQUEST WHERE ORG_ID = :orgId AND APPROVAL_ID IS NOT NULL)"
).setParameter("orgId", orgId).executeUpdate();
// 2) approval 제거
entityManager.createNativeQuery(
"DELETE FROM PTL_APPROVAL WHERE ID IN " +
"(SELECT APPROVAL_ID FROM PTL_APP_REQUEST WHERE ORG_ID = :orgId AND APPROVAL_ID IS NOT NULL)"
).setParameter("orgId", orgId).executeUpdate();
// 3) app_request 제거
entityManager.createNativeQuery(
"DELETE FROM PTL_APP_REQUEST WHERE ORG_ID = :orgId"
).setParameter("orgId", orgId).executeUpdate();
}
public Map<String, Object> selectDetailWithUsers(String id) {
return getDetailWithUsers(id, true);
}
@@ -1,79 +0,0 @@
package com.eactive.eai.rms.onl.common.util;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import com.eactive.eai.rms.onl.common.service.OnlAgentUtilServiceImpl;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.Configuration;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import io.kubernetes.client.openapi.models.V1EndpointAddress;
import io.kubernetes.client.openapi.models.V1EndpointSubset;
import io.kubernetes.client.openapi.models.V1Endpoints;
import io.kubernetes.client.util.ClientBuilder;
public class K8sUtil {
private static final Logger logger = Logger.getLogger(K8sUtil.class);
public static List<Map<String, String>> getServerInfoByK8sUrl(String namespace, String serviceName, int port, String extUrl) throws Exception {
List<Map<String, String>> resultList = getServerInfoByK8sApi(namespace,serviceName);
for (Map<String,String> map : resultList) {
String EAISEVRINSTNCNAME = map.get("EAISEVRINSTNCNAME");
String EAISEVRIP = map.get("EAISEVRIP");
Map<String, String> serverInfo = new HashMap<>();
serverInfo.put(OnlAgentUtilServiceImpl.FIELD_URL, new StringBuilder().append("http://" + EAISEVRIP + ":"+String.valueOf(port) + extUrl).toString());
serverInfo.put(OnlAgentUtilServiceImpl.FIELD_INST_NAME, EAISEVRINSTNCNAME);
}
return resultList;
}
public static List<Map<String, String>> getServerInfoByK8sApi(String namespace, String serviceName) throws Exception {
logger.info("K8sAgentUtilService getServerInfoByK8sApi start.");
List<Map<String, String>> resultList = new ArrayList<>();
try {
ApiClient client = ClientBuilder.cluster().build();
Configuration.setDefaultApiClient(client);
if (StringUtils.isBlank(namespace)) {
new RuntimeException("Can't find kubernetes_namespace in Engine Property");
}
if (StringUtils.isBlank(serviceName)) {
new RuntimeException("Can't find kubernetes_servicename in Engine Property");
}
logger.info("getServerInfoByK8sApi Codes [ namespace: " + namespace + ", serviceName:" + serviceName + " ]");
V1Endpoints endpoints = new CoreV1Api().readNamespacedEndpoints(serviceName, namespace, null);
for (V1EndpointSubset subset : endpoints.getSubsets()) {
for (V1EndpointAddress address : subset.getAddresses()) {
Map<String, String> serverInfo = new HashMap<>();
serverInfo.put("EAISEVRINSTNCNAME", address.getTargetRef().getName());
serverInfo.put("EAISEVRIP", address.getIp());
resultList.add(serverInfo);
}
}
logger.info("getServerInfoByK8sApi resultList [ " + resultList.toString() + " ]");
} catch (Exception e) {
logger.error(e);
throw new RuntimeException("K8S Mode로 AgentUtilService를 기동중 실패하였습니다.", e);
}
return resultList;
}
}
+10 -3
View File
@@ -5,13 +5,20 @@
# \uC790\uB3D9 \uD56B\uC2A4\uC651 \uD65C\uC131\uD654
autoHotswap=true
# \uB85C\uAE45 \uB808\uBCA8 (TRACE, DEBUG, INFO, WARNING, ERROR)
LOGGER.level=INFO
# \uB85C\uADF8 \uB808\uBCA8 (TRACE, DEBUG, INFO, WARNING, ERROR)
LOGGER=INFO
# \uB85C\uADF8 \uD30C\uC77C \uACBD\uB85C (\uC9C0\uC815 \uC2DC \uCF58\uC194 \uB300\uC2E0 \uD30C\uC77C\uB85C \uCD9C\uB825)
LOGFILE=/Users/rinjae/eactive/djb-eapim/djb-eapim/eapim-admin/logs/hotswap-agent.log
LOGFILE.append=true
# \uC2DC\uAC01 \uD3EC\uB9F7
LOGGER_DATETIME_FORMAT=yyyy-MM-dd HH:mm:ss.SSS
# ============================================================
# \uBA85\uC2DC\uC801 \uD50C\uB7EC\uADF8\uC778 \uBE44\uD65C\uC131\uD654 (\uC27C\uD45C \uAD6C\uBD84)
# ============================================================
disabledPlugins=org.hotswap.agent.plugin.ibatis.IBatisPlugin,org.hotswap.agent.plugin.mybatis.MyBatisPlugin
disabledPlugins=IBatis,MyBatis
# ============================================================
# Core Plugin \uD65C\uC131\uD654
+1 -1
View File
@@ -4,7 +4,7 @@
<property name="LOG_PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] \\(%F:%L\\) %-20M -%msg%n" />
<property name="PREFIX" value="${inst.Type:-ems}" />
<property name="LOG_HOME" value="/Log/App/eapim/${inst.Name:-devSvr00}" />
<property name="LOG_HOME" value="/logs/eapim/${inst.Name:-emsSvr00}" />
<property name="BACKUP_HOME" value="${LOG_HOME}/backup/" />
<property name="LOG_LEVEL" value="${LOGBACK_LOG_LEVEL:-DEBUG}" />
+1 -1
View File
@@ -4,7 +4,7 @@
<property name="LOG_PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] \\(%F:%L\\) %-20M -%msg%n" />
<property name="PREFIX" value="${inst.Type:-ems}" />
<property name="LOG_HOME" value="/Log/App/eapim/${inst.Name:-devSvr00}" />
<property name="LOG_HOME" value="/logs/eapim/${inst.Name:-devSvr00}" />
<property name="BACKUP_HOME" value="${LOG_HOME}/backup/" />
<property name="LOG_LEVEL" value="${LOGBACK_LOG_LEVEL:-DEBUG}" />
+1 -1
View File
@@ -4,7 +4,7 @@
<property name="LOG_PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] \\(%F:%L\\) %-20M -%msg%n" />
<property name="PREFIX" value="${inst.Type:-ems}" />
<property name="LOG_HOME" value="/Log/App/eapim/${inst.Name:-emsSvr00}" />
<property name="LOG_HOME" value="/logs/eapim/${inst.Name:-emsSvr00}" />
<property name="BACKUP_HOME" value="${LOG_HOME}/backup/" />
<property name="LOG_LEVEL" value="${LOGBACK_LOG_LEVEL:-INFO}" />
@@ -137,7 +137,7 @@ JSP `detail()` 함수에서 제네릭 루프 대신 명시적 필드 매핑으
## 미완료 / 다음 작업
- [ ] `kjb-gradle.sh compileJava` 실행 → `QCryptoModuleConfig` Q-class 생성 확인
- [ ] `./gradlew compileJava` 실행 → `QCryptoModuleConfig` Q-class 생성 확인
- [ ] 관리자 메뉴 테이블에 경로 등록 (`/onl/admin/security/cryptoModuleMan.view`)
- [ ] 화면 접근 권한(ACL) 등록
- [ ] 전체 파일 커밋 (eapim-admin)