diff --git a/.gitea/workflows/eapim-admin-ci.yml b/.gitea/workflows/eapim-admin-ci.yml new file mode 100644 index 0000000..eba94c0 --- /dev/null +++ b/.gitea/workflows/eapim-admin-ci.yml @@ -0,0 +1,132 @@ +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: 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 180s) + 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) + 180)) + 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 180s (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 diff --git a/.gitignore b/.gitignore index 41a9225..67a1cbc 100644 --- a/.gitignore +++ b/.gitignore @@ -246,3 +246,6 @@ eapim-admin-kjb/ *.report.md *.claude.md tmpclaude-*-cwd +logs +rinjae_ +tomcat-base \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index e51bbbb..7e15c4b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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`)은 다음을 포함합니다: diff --git a/WebContent/WEB-INF/applicationContext-datasource-DEV.xml b/WebContent/WEB-INF/applicationContext-datasource-DEV.xml index d3a8c40..1ee28a1 100644 --- a/WebContent/WEB-INF/applicationContext-datasource-DEV.xml +++ b/WebContent/WEB-INF/applicationContext-datasource-DEV.xml @@ -25,24 +25,24 @@ class="com.eactive.eai.rms.common.datasource.DataSourceType" p:name="APIGW" p:text="APIGW" - p:schema="AGWADM" + p:schema="AGWAPP" p:dynamic="true" p:online="true" p:jndiName="jdbc/dsOBP_AGW" p:render="ONL,COM" /> - + /> - - - - - - - - - - - + - + value-ref="BAP" /> /WEB-INF/sqlmap-config/${db.vendor}/sqlMapConfigOfOnl.xml - + /WEB-INF/sqlmap-config/${db.vendor}/sqlMapConfigOfBap.xml diff --git a/WebContent/WEB-INF/applicationContext-jdbc-LOCAL.xml b/WebContent/WEB-INF/applicationContext-jdbc-LOCAL.xml index 036af28..979b4aa 100644 --- a/WebContent/WEB-INF/applicationContext-jdbc-LOCAL.xml +++ b/WebContent/WEB-INF/applicationContext-jdbc-LOCAL.xml @@ -30,9 +30,9 @@ - - - + /WEB-INF/sqlmap-config/${db.vendor}/sqlMapConfigOfOnl.xml - + /WEB-INF/sqlmap-config/${db.vendor}/sqlMapConfigOfBap.xml diff --git a/WebContent/WEB-INF/applicationContext-jpa.xml b/WebContent/WEB-INF/applicationContext-jpa.xml index b1da502..95e9929 100644 --- a/WebContent/WEB-INF/applicationContext-jpa.xml +++ b/WebContent/WEB-INF/applicationContext-jpa.xml @@ -69,6 +69,7 @@ + - + diff --git a/WebContent/js/custom.js b/WebContent/js/custom.js index e6bb511..c5e7c5f 100644 --- a/WebContent/js/custom.js +++ b/WebContent/js/custom.js @@ -36,6 +36,14 @@ // save the returnValue in options so that it is available in the callback function optns.returnValue = $frame[0].contentWindow.window.returnValue; optns.onClose(); + // Remove iframe from DOM so window.frames doesn't accumulate stale entries; + // without this, a second showModal call leaves frames[0] pointing to the first + // (closed) iframe, causing the previous returnValue to bleed into the next callback. + var $dialogEl = $frame.closest('.ui-dialog'); + setTimeout(function() { + $frame.remove(); + if ($dialogEl.length) $dialogEl.remove(); + }, 0); if(optns.option && optns.option.fullScreenPopup){ $('iframe[name=topFrame]',window.parent.document).css('display', 'block'); diff --git a/WebContent/jsp/common/include/script.jsp b/WebContent/jsp/common/include/script.jsp index 1e96177..d4195f2 100644 --- a/WebContent/jsp/common/include/script.jsp +++ b/WebContent/jsp/common/include/script.jsp @@ -30,6 +30,10 @@ + <%-- --%> diff --git a/WebContent/jsp/common/scheduler/schedulerManDetail.jsp b/WebContent/jsp/common/scheduler/schedulerManDetail.jsp index 3851e26..82cca5b 100644 --- a/WebContent/jsp/common/scheduler/schedulerManDetail.jsp +++ b/WebContent/jsp/common/scheduler/schedulerManDetail.jsp @@ -465,21 +465,21 @@ $(document).ready(function() { - - - - - - + + + + + + - - - - - - + + + + + +
Default SchedulerOn Memory: Previous Fired: Next Fire Time: isConcurrentExectionDisallowed: PersistJobData: Durable: On Memory: Previous Fired: Next Fire Time: isConcurrentExectionDisallowed: PersistJobData: Durable:
Clustered SchedulerOn Memory: Previous Fired: Next Fire Time: isConcurrentExectionDisallowed: PersistJobData: Durable: On Memory: Previous Fired: Next Fire Time: isConcurrentExectionDisallowed: PersistJobData: Durable:
diff --git a/WebContent/jsp/common/screen/emergency.jsp b/WebContent/jsp/common/screen/emergency.jsp index b0d1252..b98632b 100644 --- a/WebContent/jsp/common/screen/emergency.jsp +++ b/WebContent/jsp/common/screen/emergency.jsp @@ -353,10 +353,10 @@ -
+

<%=resultMsg %>

diff --git a/WebContent/jsp/common/screen/main.jsp b/WebContent/jsp/common/screen/main.jsp index 0642401..8748ef8 100644 --- a/WebContent/jsp/common/screen/main.jsp +++ b/WebContent/jsp/common/screen/main.jsp @@ -99,6 +99,93 @@ - + + <%-- 자동 로그아웃 경고 모달 --%> + + + + diff --git a/WebContent/jsp/common/screen/top_04.jsp b/WebContent/jsp/common/screen/top_04.jsp index 1be448d..382e376 100644 --- a/WebContent/jsp/common/screen/top_04.jsp +++ b/WebContent/jsp/common/screen/top_04.jsp @@ -551,9 +551,9 @@
+
@@ -270,9 +357,9 @@ - 게시유형 - -
+ 게시유형 * + +
@@ -286,7 +373,7 @@ 사용 - 고정여부 + 상단고정 고정 @@ -297,14 +384,53 @@ - - 첨부파일 - -
- - + + + + 시작 * + + + + 종료 + + + + + + 상태 + +
+
-
+ + 이전 상태 + 없음 + + + 영향 API 선택 + +
+ + +
+ + + + + + + + + + + +
API IDAPI 명
선택된 영향 API가 없습니다.
@@ -312,4 +438,4 @@
- \ No newline at end of file + diff --git a/WebContent/jsp/onl/kjb/statistics/apiStatsAggregationMan.jsp b/WebContent/jsp/onl/kjb/statistics/apiStatsAggregationMan.jsp index 8322d71..5bc3e2f 100644 --- a/WebContent/jsp/onl/kjb/statistics/apiStatsAggregationMan.jsp +++ b/WebContent/jsp/onl/kjb/statistics/apiStatsAggregationMan.jsp @@ -82,6 +82,54 @@ + + + +
+
+ +
+
+
+ + +
+
API 사용현황
+ + + + + + + + + + + + + + + +
조회기간 + + ~ + + (최대 31일) +
조회구분 + + + + 제휴사명 + + API명 + +
+ +
+
+
+
+ + +
+
+ + diff --git a/WebContent/jsp/onl/transaction/apim/apiInterfaceManDetail.jsp b/WebContent/jsp/onl/transaction/apim/apiInterfaceManDetail.jsp index b0c6b3a..40cddba 100644 --- a/WebContent/jsp/onl/transaction/apim/apiInterfaceManDetail.jsp +++ b/WebContent/jsp/onl/transaction/apim/apiInterfaceManDetail.jsp @@ -1624,12 +1624,12 @@ x-effect="if (apiInterface.syncAsyncType !== 'async') apiInterface.requestType = 'S'"> + value="S" :disabled="$store.formState.isReqResDisabled"> + value="R" :disabled="$store.formState.isReqResDisabled">
@@ -1686,7 +1686,7 @@ - + diff --git a/build.gradle b/build.gradle index 2fb27e4..c712469 100644 --- a/build.gradle +++ b/build.gradle @@ -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' } } diff --git a/check_table.py b/check_table.py new file mode 100644 index 0000000..9e97111 --- /dev/null +++ b/check_table.py @@ -0,0 +1,11 @@ +import oracledb + +conn = oracledb.connect(user='AGWAPP', password='elink1234', dsn='192.168.240.177:1598/DAPM') +cursor = conn.cursor() + +cursor.execute("SELECT TABLE_NAME, STATUS FROM DBA_TABLES WHERE OWNER = 'AGWAPP' AND TABLE_NAME LIKE 'API_STATS%' ORDER BY TABLE_NAME") +for row in cursor.fetchall(): + print(f" {row[0]} ({row[1]})") + +cursor.close() +conn.close() diff --git a/deploy/systemd/eapim-admin.service b/deploy/systemd/eapim-admin.service new file mode 100644 index 0000000..b6da903 --- /dev/null +++ b/deploy/systemd/eapim-admin.service @@ -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 diff --git a/kjb-docs/API_HISTORY.sql b/kjb-docs/API_HISTORY.sql index 398af9d..82290a2 100644 --- a/kjb-docs/API_HISTORY.sql +++ b/kjb-docs/API_HISTORY.sql @@ -1,4 +1,4 @@ -CREATE TABLE EMSADM.API_HISTORY +CREATE TABLE EMSAPP.API_HISTORY ( API_ID VARCHAR2(30), SEQ NUMBER(22), @@ -9,23 +9,23 @@ CREATE TABLE EMSADM.API_HISTORY LOB (SAVEDDATA) STORE AS SECUREFILE (TABLESPACE TS_AGW_D01) TABLESPACE TS_EMS_D01; -COMMENT ON TABLE EMSADM.API_HISTORY IS 'API 변경이력 테이블'; -COMMENT ON COLUMN EMSADM.API_HISTORY.API_ID IS 'API ID(TSEAIHE01.EAISVCNAME)'; -COMMENT ON COLUMN EMSADM.API_HISTORY.SEQ IS 'Sequence 값'; -COMMENT ON COLUMN EMSADM.API_HISTORY.SAVEDDATA IS '변경저장 정보'; -COMMENT ON COLUMN EMSADM.API_HISTORY.USERID IS '사용자ID'; -COMMENT ON COLUMN EMSADM.API_HISTORY.SAVEDDT IS '저장일시'; +COMMENT ON TABLE EMSAPP.API_HISTORY IS 'API 변경이력 테이블'; +COMMENT ON COLUMN EMSAPP.API_HISTORY.API_ID IS 'API ID(TSEAIHE01.EAISVCNAME)'; +COMMENT ON COLUMN EMSAPP.API_HISTORY.SEQ IS 'Sequence 값'; +COMMENT ON COLUMN EMSAPP.API_HISTORY.SAVEDDATA IS '변경저장 정보'; +COMMENT ON COLUMN EMSAPP.API_HISTORY.USERID IS '사용자ID'; +COMMENT ON COLUMN EMSAPP.API_HISTORY.SAVEDDT IS '저장일시'; -CREATE UNIQUE INDEX EMSADM.PK_API_HISTORY ON EMSADM.API_HISTORY +CREATE UNIQUE INDEX EMSAPP.PK_API_HISTORY ON EMSAPP.API_HISTORY (API_ID, SEQ) TABLESPACE TS_EMS_I01; -ALTER TABLE EMSADM.API_HISTORY ADD ( +ALTER TABLE EMSAPP.API_HISTORY ADD ( CONSTRAINT PK_API_HISTORY PRIMARY KEY (API_ID, SEQ) - USING INDEX EMSADM.PK_API_HISTORY + USING INDEX EMSAPP.PK_API_HISTORY ENABLE VALIDATE); -GRANT DELETE, INSERT, SELECT, UPDATE ON EMSADM.API_HISTORY TO RL_EMS_ALL; -GRANT SELECT ON EMSADM.API_HISTORY TO RL_EMS_SEL; +GRANT DELETE, INSERT, SELECT, UPDATE ON EMSAPP.API_HISTORY TO RL_EMS_ALL; +GRANT SELECT ON EMSAPP.API_HISTORY TO RL_EMS_SEL; diff --git a/kjb-docs/Property 설정.md b/kjb-docs/Property 설정.md index daac5d2..557f557 100644 --- a/kjb-docs/Property 설정.md +++ b/kjb-docs/Property 설정.md @@ -39,37 +39,37 @@ ```sql -- 개발 서버용 -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai_batch.url', 'http://172.31.32.111:10230/BATAppWeb/EaiBatCall'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.connection_timeout', '5'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.response_timeout', '5'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.timeout', '10'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.charset', 'euc-kr'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.send_dir', '/Data/eapim/portal/sendmail/snd'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.backup_dir', '/Data/eapim/portal/sendmail/bak'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.error_dir', '/Data/eapim/portal/sendmail/bak'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.retention_date', '60'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.interface_id', 'UIEFF00001UAG'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.host_url', 'http://172.31.35.144:9021'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.api_key', '7cca6d58-e4f7-474d-9edd-525806bc99ff'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.connection_timeout', '5'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.response_timeout', '5'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.timeout', '10'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai_batch.url', 'http://172.31.32.111:10230/BATAppWeb/EaiBatCall'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.connection_timeout', '5'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.response_timeout', '5'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.timeout', '10'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.charset', 'euc-kr'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.send_dir', '/Data/eapim/portal/sendmail/snd'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.backup_dir', '/Data/eapim/portal/sendmail/bak'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.error_dir', '/Data/eapim/portal/sendmail/bak'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.retention_date', '60'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.interface_id', 'UIEFF00001UAG'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.host_url', 'http://172.31.35.144:9021'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.api_key', '7cca6d58-e4f7-474d-9edd-525806bc99ff'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.connection_timeout', '5'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.response_timeout', '5'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.timeout', '10'); -- autogen template -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.url', ''); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.connection_timeout', '5'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.response_timeout', '5'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.timeout', '10'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.charset', 'euc-kr'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.send_dir', ''); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.backup_dir', ''); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.error_dir', ''); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.interface_id', ''); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.retention_date', '60'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.host', ''); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.api_key', ''); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.connection_timeout', '5'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.response_timeout', '5'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.timeout', '10'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.url', ''); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.connection_timeout', '5'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.response_timeout', '5'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.timeout', '10'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.charset', 'euc-kr'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.send_dir', ''); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.backup_dir', ''); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.error_dir', ''); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.interface_id', ''); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.retention_date', '60'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.host', ''); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.api_key', ''); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.connection_timeout', '5'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.response_timeout', '5'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.timeout', '10'); ``` \ No newline at end of file diff --git a/kjb-docs/sms-auth-guide.md b/kjb-docs/sms-auth-guide.md index 08949f8..bb215e8 100644 --- a/kjb-docs/sms-auth-guide.md +++ b/kjb-docs/sms-auth-guide.md @@ -163,13 +163,13 @@ SMS 인증번호 발송 - authCode: 123456 ```sql -- SMS 인증 설정 초기화 -INSERT INTO EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) +INSERT INTO EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) VALUES ('Monitoring', 'kjb.sms_auth.enabled', 'true'); -INSERT INTO EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) +INSERT INTO EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) VALUES ('Monitoring', 'kjb.sms_auth.mode', 'real'); -INSERT INTO EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) +INSERT INTO EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) VALUES ('Monitoring', 'kjb.sms_auth.fixed_value', '654321'); ``` @@ -187,7 +187,7 @@ SMS 발송을 위해 UMS 관련 프로퍼티가 설정되어 있어야 합니다 ### Q: SMS 인증을 일시적으로 비활성화하려면? ```sql -UPDATE EMSADM.TSEAIRM24 +UPDATE EMSAPP.TSEAIRM24 SET PRPTY2VAL = 'false' WHERE PRPTYGROUPNAME = 'Monitoring' AND PRPTYNAME = 'kjb.sms_auth.enabled'; ``` diff --git a/readme.md b/readme.md index f6ee150..65ad55c 100644 --- a/readme.md +++ b/readme.md @@ -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 단독 실행 - ## 프로젝트 구조 ### 전체 프로젝트 구조 @@ -428,7 +238,7 @@ WebContent/ ``` -Deai.datasource.type=DEV -Dinst.Name=emsSvr11 --Deai.tableowner=EMSADM +-Deai.tableowner=EMSAPP -Deai.systemmode=D -Dfile.encoding=utf-8 -DLOGBACK_LOG_LEVEL=info @@ -438,7 +248,7 @@ WebContent/ ``` -Deai.datasource.type=DEV -Dinst.Name=emsSvr99 --Deai.tableowner=EMSADM +-Deai.tableowner=EMSAPP -Deai.systemmode=D -Dfile.encoding=utf-8 -Dlogin.mode=db @@ -577,82 +387,82 @@ gradle test --tests "com.eactive.eai.rms.*" ```sql -- 로컬 개발용 (외부) -- EAI, UMS 주소 미기입시 내부적으론 호출 시도 하지 않고 warning 로그만 남기며 정상 처리 ---insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai_batch.url', ''); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.connection_timeout', '5'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.response_timeout', '5'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.timeout', '10'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.charset', 'euc-kr'); +--insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai_batch.url', ''); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.connection_timeout', '5'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.response_timeout', '5'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.timeout', '10'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.charset', 'euc-kr'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.send_dir', '/Data/eapim/portal/sendmail/snd'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.backup_dir', '/Data/eapim/portal/sendmail/bak'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.error_dir', '/Data/eapim/portal/sendmail/bak'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.retention_date', '60'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.interface_id', 'UIEFF00001UAG'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.send_dir', '/Data/eapim/portal/sendmail/snd'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.backup_dir', '/Data/eapim/portal/sendmail/bak'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.error_dir', '/Data/eapim/portal/sendmail/bak'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.retention_date', '60'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.interface_id', 'UIEFF00001UAG'); --- insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.host_url', ''); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.api_key', '7cca6d58-e4f7-474d-9edd-525806bc99ff'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.connection_timeout', '5'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.response_timeout', '5'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.timeout', '10'); +-- insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.host_url', ''); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.api_key', '7cca6d58-e4f7-474d-9edd-525806bc99ff'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.connection_timeout', '5'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.response_timeout', '5'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.timeout', '10'); ---insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.host_url', ''); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.trust_system', 'APIMPT-0002-QVBJTVBULTAwMDI='); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.commission_uri', '/api/billing/findBillingForCondition'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.commission_print_uri', '/api/billing/billing/findBillingForCondition/print'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.query_partnercode_uri', '/api/customer/findCustomerByBusinessManRegistrationNo/%s'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.create_partnercode_uri', '/not-yet-implement/%s'); +--insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.host_url', ''); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.trust_system', 'APIMPT-0002-QVBJTVBULTAwMDI='); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.commission_uri', '/api/billing/findBillingForCondition'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.commission_print_uri', '/api/billing/billing/findBillingForCondition/print'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.query_partnercode_uri', '/api/customer/findCustomerByBusinessManRegistrationNo/%s'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.create_partnercode_uri', '/not-yet-implement/%s'); commit; -- 은행 개발 서버용 -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai_batch.url', 'http://172.31.32.111:10230/BATAppWeb/EaiBatCall'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.connection_timeout', '5'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.response_timeout', '5'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.timeout', '10'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.charset', 'euc-kr'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.send_dir', '/Data/eapim/portal/sendmail/snd'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.backup_dir', '/Data/eapim/portal/sendmail/bak'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.error_dir', '/Data/eapim/portal/sendmail/bak'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.retention_date', '60'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.interface_id', 'UIEFF00001UAG'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.host_url', 'http://172.31.35.144:9021'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.api_key', '7cca6d58-e4f7-474d-9edd-525806bc99ff'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.connection_timeout', '5'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.response_timeout', '5'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.timeout', '10'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai_batch.url', 'http://172.31.32.111:10230/BATAppWeb/EaiBatCall'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.connection_timeout', '5'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.response_timeout', '5'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.timeout', '10'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.charset', 'euc-kr'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.send_dir', '/Data/eapim/portal/sendmail/snd'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.backup_dir', '/Data/eapim/portal/sendmail/bak'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.error_dir', '/Data/eapim/portal/sendmail/bak'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.retention_date', '60'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.interface_id', 'UIEFF00001UAG'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.host_url', 'http://172.31.35.144:9021'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.api_key', '7cca6d58-e4f7-474d-9edd-525806bc99ff'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.connection_timeout', '5'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.response_timeout', '5'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.timeout', '10'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.host_url', 'http://192.168.246.15:8181'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.trust_system', 'APIMPT-0002-QVBJTVBULTAwMDI='); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.commission_uri', '/api/billing/findBillingForCondition'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.commission_print_uri', '/api/billing/billing/findBillingForCondition/print'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.query_partnercode_uri', '/api/customer/findCustomerByBusinessManRegistrationNo/%s'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.create_partnercode_uri', '/not-yet-implement/%s'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.host_url', 'http://192.168.246.15:8181'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.trust_system', 'APIMPT-0002-QVBJTVBULTAwMDI='); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.commission_uri', '/api/billing/findBillingForCondition'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.commission_print_uri', '/api/billing/billing/findBillingForCondition/print'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.query_partnercode_uri', '/api/customer/findCustomerByBusinessManRegistrationNo/%s'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.create_partnercode_uri', '/not-yet-implement/%s'); commit; -- 은행 운영 서버용 -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai_batch.url', 'http://172.21.1.40:10860/BATAppWeb/EaiBatCall'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.connection_timeout', '5'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.response_timeout', '5'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.timeout', '10'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.charset', 'euc-kr'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.send_dir', '/Data/eapim/portal/sendmail/snd'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.backup_dir', '/Data/eapim/portal/sendmail/bak'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.error_dir', '/Data/eapim/portal/sendmail/bak'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.retention_date', '60'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.interface_id', 'UIEFF00001UAG'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.host_url', 'http://172.24.128.66:9021'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.api_key', '7cca6d58-e4f7-474d-9edd-525806bc99ff'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.connection_timeout', '5'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.response_timeout', '5'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.timeout', '10'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai_batch.url', 'http://172.21.1.40:10860/BATAppWeb/EaiBatCall'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.connection_timeout', '5'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.response_timeout', '5'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.timeout', '10'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.eai.batch.charset', 'euc-kr'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.send_dir', '/Data/eapim/portal/sendmail/snd'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.backup_dir', '/Data/eapim/portal/sendmail/bak'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.error_dir', '/Data/eapim/portal/sendmail/bak'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.retention_date', '60'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.eai_batch.interface_id', 'UIEFF00001UAG'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.host_url', 'http://172.24.128.66:9021'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.api_key', '7cca6d58-e4f7-474d-9edd-525806bc99ff'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.connection_timeout', '5'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.response_timeout', '5'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.ums.timeout', '10'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.host_url', 'http://192.168.246.15:8181'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.trust_system', 'APIMPT-0002-QVBJTVBULTAwMDI='); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.commission_uri', '/api/billing/findBillingForCondition'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.commission_print_uri', '/api/billing/billing/findBillingForCondition/print'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.query_partnercode_uri', '/api/customer/findCustomerByBusinessManRegistrationNo/%s'); -insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.create_partnercode_uri', '/not-yet-implement/%s'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.host_url', 'http://192.168.246.15:8181'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.trust_system', 'APIMPT-0002-QVBJTVBULTAwMDI='); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.commission_uri', '/api/billing/findBillingForCondition'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.commission_print_uri', '/api/billing/billing/findBillingForCondition/print'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.query_partnercode_uri', '/api/customer/findCustomerByBusinessManRegistrationNo/%s'); +insert into EMSAPP.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', 'kjb.obp.create_partnercode_uri', '/not-yet-implement/%s'); commit; ``` ## 라이선스 diff --git a/src/main/java/com/eactive/eai/rms/common/context/MonitoringContext.java b/src/main/java/com/eactive/eai/rms/common/context/MonitoringContext.java index 61cf3c1..e9d6ed9 100644 --- a/src/main/java/com/eactive/eai/rms/common/context/MonitoringContext.java +++ b/src/main/java/com/eactive/eai/rms/common/context/MonitoringContext.java @@ -254,6 +254,13 @@ public interface MonitoringContext { // 이중 로그인 허용여부 public static final String RMS_DUAL_LOGIN_ENABLED = "rms.DUAL_LOGIN_ENABLED"; + + // 자동 로그아웃 타임아웃 (단위: 분, 기본값: 10) + public static final String RMS_AUTO_LOGOUT_TIMEOUT = "rms.auto.logout.timeout"; + + // 웹훅 재전송 설정 + public static final String API_WEBHOOK_RETRY_COUNT = "api.webhook.retry_count"; + public static final String API_WEBHOOK_RETRY_TIME = "api.webhook.retry_time"; diff --git a/src/main/java/com/eactive/eai/rms/common/login/MainController.java b/src/main/java/com/eactive/eai/rms/common/login/MainController.java index 1471ec8..3994a19 100644 --- a/src/main/java/com/eactive/eai/rms/common/login/MainController.java +++ b/src/main/java/com/eactive/eai/rms/common/login/MainController.java @@ -617,6 +617,8 @@ public class MainController implements InterceptorSkipController { menuId = menuId == null ? "" : menuId; model.addAttribute("mainPage", mainPage); model.addAttribute("menuId", menuId); + model.addAttribute("autoLogoutTimeout", + monitoringContext.getIntProperty(MonitoringContext.RMS_AUTO_LOGOUT_TIMEOUT, 10)); if (logger.isDebugEnabled()) { logger.debug("CURRENT SERVICE TYPE : " + service); diff --git a/src/main/java/com/eactive/eai/rms/common/property/MonitoringPropertyManService.java b/src/main/java/com/eactive/eai/rms/common/property/MonitoringPropertyManService.java index 1bf3869..3cde4d8 100644 --- a/src/main/java/com/eactive/eai/rms/common/property/MonitoringPropertyManService.java +++ b/src/main/java/com/eactive/eai/rms/common/property/MonitoringPropertyManService.java @@ -35,6 +35,7 @@ public class MonitoringPropertyManService extends BaseService { public MonitoringPropertyGroupUI selectDetail(String prptyGroupName) { MonitoringPropertyGroup propertyGroup = propertyGroupService.getById(prptyGroupName); + propertyGroup.getMonitoringProperties().sort(Comparator.comparing(p -> p.getId().getPrptyName())); return propertyGroupMapper.toVo(propertyGroup); } diff --git a/src/main/java/com/eactive/eai/rms/common/scheduler/SchedulerManService.java b/src/main/java/com/eactive/eai/rms/common/scheduler/SchedulerManService.java index 0b10e8f..35d6e24 100644 --- a/src/main/java/com/eactive/eai/rms/common/scheduler/SchedulerManService.java +++ b/src/main/java/com/eactive/eai/rms/common/scheduler/SchedulerManService.java @@ -1,13 +1,11 @@ package com.eactive.eai.rms.common.scheduler; -import com.eactive.eai.rms.common.base.BaseService; -import com.eactive.eai.rms.common.scheduler.ui.JobInfoMapper; -import com.eactive.eai.rms.common.scheduler.ui.JobInfoUI; -import com.eactive.eai.rms.common.scheduler.ui.ScheduleInfo; -import com.eactive.eai.rms.common.scheduler.ui.SchedulerUISearch; -import com.eactive.eai.rms.data.entity.man.monitoringCode.service.MonitoringCodeService; -import com.eactive.eai.rms.data.entity.man.scheduler.JobHistoryService; -import com.eactive.eai.rms.data.entity.man.scheduler.JobInfoService; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + import org.apache.commons.lang3.StringUtils; import org.quartz.JobDetail; import org.quartz.JobKey; @@ -20,9 +18,14 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; +import com.eactive.eai.rms.common.base.BaseService; +import com.eactive.eai.rms.common.scheduler.ui.JobInfoMapper; +import com.eactive.eai.rms.common.scheduler.ui.JobInfoUI; +import com.eactive.eai.rms.common.scheduler.ui.ScheduleInfo; +import com.eactive.eai.rms.common.scheduler.ui.SchedulerUISearch; +import com.eactive.eai.rms.data.entity.man.monitoringCode.service.MonitoringCodeService; +import com.eactive.eai.rms.data.entity.man.scheduler.JobHistoryService; +import com.eactive.eai.rms.data.entity.man.scheduler.JobInfoService; @Service("schedulerService") @Transactional(transactionManager = "transactionManagerForEMS") @@ -122,10 +125,10 @@ public class SchedulerManService extends BaseService { List triggersOfJob = scheduler.getTriggersOfJob(new JobKey(jobName, Scheduler.DEFAULT_GROUP)); if(triggersOfJob.size() > 0) { - result.setStartTime(String.valueOf(triggersOfJob.get(0).getStartTime())); - result.setEndTime(String.valueOf(triggersOfJob.get(0).getEndTime())); - result.setPreviousFireTime(String.valueOf(triggersOfJob.get(0).getPreviousFireTime())); - result.setNextFireTime(String.valueOf(triggersOfJob.get(0).getNextFireTime())); + result.setStartTime(dateFormat(triggersOfJob.get(0).getStartTime())); + result.setEndTime(dateFormat(triggersOfJob.get(0).getEndTime())); + result.setPreviousFireTime(dateFormat(triggersOfJob.get(0).getPreviousFireTime())); + result.setNextFireTime(dateFormat(triggersOfJob.get(0).getNextFireTime())); } } catch (Exception e) { @@ -150,5 +153,14 @@ public class SchedulerManService extends BaseService { public void delete(String jobName) { jobInfoService.deleteById(jobName); } + + private String dateFormat(Date date) { + if (date == null) { + return ""; + } else { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + return sdf.format(date); + } + } } diff --git a/src/main/java/com/eactive/eai/rms/common/util/StringUtils.java b/src/main/java/com/eactive/eai/rms/common/util/StringUtils.java index 2376dd1..a6f02c1 100644 --- a/src/main/java/com/eactive/eai/rms/common/util/StringUtils.java +++ b/src/main/java/com/eactive/eai/rms/common/util/StringUtils.java @@ -1,5 +1,6 @@ package com.eactive.eai.rms.common.util; +import java.math.BigDecimal; import java.util.List; import org.apache.log4j.Logger; @@ -191,6 +192,20 @@ public class StringUtils } return sb.toString(); } + + public static String toString(Object val) { + return val != null ? val.toString() : null; + } + + public static Long toLong(Object val) { + return val != null ? ((Number) val).longValue() : null; + } + + public static BigDecimal toDecimal(Object val) { + if (val == null) return null; + if (val instanceof BigDecimal) return (BigDecimal) val; + return new BigDecimal(val.toString()); + } // // /** // * - "" 나 Null 을 입력받아 SPACE 로 변환한다. diff --git a/src/main/java/com/eactive/eai/rms/data/entity/man/user/UserInfoService.java b/src/main/java/com/eactive/eai/rms/data/entity/man/user/UserInfoService.java index 6254ea8..b434546 100644 --- a/src/main/java/com/eactive/eai/rms/data/entity/man/user/UserInfoService.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/man/user/UserInfoService.java @@ -107,6 +107,17 @@ public class UserInfoService extends }).collect(Collectors.toList()); } + public List findAllByRoleId(String roleId) { + QUserInfo qUserInfo = QUserInfo.userInfo; + QUserRole qUserRole = QUserRole.userRole; + + return getJPAQueryFactory() + .selectFrom(qUserInfo) + .join(qUserRole).on(qUserInfo.userid.eq(qUserRole.id.userId)) + .where(qUserRole.id.roleId.eq(roleId)) + .fetch(); + } + public Page findByUsername(Pageable pageable, String username) { QUserInfo qUserInfo = QUserInfo.userInfo; BooleanExpression predicate = qUserInfo.isNotNull(); diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/PortalInquiryCommentRepository.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/PortalInquiryCommentRepository.java new file mode 100644 index 0000000..6fc58f4 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/PortalInquiryCommentRepository.java @@ -0,0 +1,28 @@ +package com.eactive.eai.rms.data.entity.onl.apim.portalinquiry; + +import com.eactive.apim.portal.qna.entity.InquiryComment; +import com.eactive.eai.data.jpa.BaseRepository; +import com.eactive.eai.rms.data.EMSDataSource; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +@EMSDataSource +public interface PortalInquiryCommentRepository extends BaseRepository { + + long countByInquiry_Id(String inquiryId); + + long countByInquiry_IdAndDelYn(String inquiryId, String delYn); + + List findByInquiry_IdAndDelYnOrderByCreatedDateAsc(String inquiryId, String delYn); + + Optional findTopByInquiry_IdAndDelYnOrderByCreatedDateDesc( + String inquiryId, String delYn); + + Optional findTopByInquiry_IdAndAdminYnAndDelYnOrderByCreatedDateDesc( + String inquiryId, String adminYn, String delYn); + + boolean existsByInquiry_IdAndAdminYnAndDelYnAndCreatedDateAfter( + String inquiryId, String adminYn, String delYn, LocalDateTime after); +} diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/PortalInquiryCommentService.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/PortalInquiryCommentService.java new file mode 100644 index 0000000..0ccbb30 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/PortalInquiryCommentService.java @@ -0,0 +1,31 @@ +package com.eactive.eai.rms.data.entity.onl.apim.portalinquiry; + +import com.eactive.apim.portal.qna.entity.InquiryComment; +import com.eactive.eai.rms.data.jpa.AbstractEMSDataSerivce; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@Transactional(transactionManager = "transactionManagerForEMS") +public class PortalInquiryCommentService extends AbstractEMSDataSerivce { + + // AbstractEMSDataSerivce의 repository 필드가 BaseRepository로 고정되어 + // 커스텀 메서드를 호출할 수 없으므로 별도 필드로 직접 주입 + @Autowired + private PortalInquiryCommentRepository inquiryCommentRepository; + + public long countByInquiryId(String inquiryId) { + return inquiryCommentRepository.countByInquiry_Id(inquiryId); + } + + public long countActiveByInquiryId(String inquiryId) { + return inquiryCommentRepository.countByInquiry_IdAndDelYn(inquiryId, "N"); + } + + public List findByInquiryId(String inquiryId) { + return inquiryCommentRepository.findByInquiry_IdAndDelYnOrderByCreatedDateAsc(inquiryId, "N"); + } +} diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/PortalInquiryRepository.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/PortalInquiryRepository.java index 3dc9f19..9dd631a 100644 --- a/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/PortalInquiryRepository.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/PortalInquiryRepository.java @@ -4,7 +4,10 @@ import com.eactive.apim.portal.qna.entity.Inquiry; import com.eactive.eai.data.jpa.BaseRepository; import com.eactive.eai.rms.data.EMSDataSource; +import java.util.List; + @EMSDataSource public interface PortalInquiryRepository extends BaseRepository { + List findByInquiryStatus(String inquiryStatus); } diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/PortalInquiryService.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/PortalInquiryService.java index bb20d6c..a9456d9 100644 --- a/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/PortalInquiryService.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/PortalInquiryService.java @@ -1,20 +1,28 @@ package com.eactive.eai.rms.data.entity.onl.apim.portalinquiry; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +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 com.eactive.apim.portal.qna.entity.Inquiry; import com.eactive.apim.portal.qna.entity.QInquiry; import com.eactive.eai.rms.data.jpa.AbstractEMSDataSerivce; import com.querydsl.core.BooleanBuilder; -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; @Service @Transactional(transactionManager = "transactionManagerForEMS") public class PortalInquiryService extends AbstractEMSDataSerivce { + // AbstractEMSDataSerivce의 repository 필드가 BaseRepository로 고정되어 + // 커스텀 메서드를 호출할 수 없으므로 별도 필드로 직접 주입 + @Autowired + private PortalInquiryRepository portalInquiryRepository; + public Page findAll(Pageable pageable, PortalInquirySearch portalInquirySearch) { QInquiry qInquiry = QInquiry.inquiry; @@ -58,4 +66,8 @@ public class PortalInquiryService extends AbstractEMSDataSerivce findByInquiryStatus(String inquiryStatus) { + return portalInquiryRepository.findByInquiryStatus(inquiryStatus); + } } \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/rms/data/ext/djb/apistatus/ApiStatus.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/apistatus/ApiStatus.java similarity index 95% rename from src/main/java/com/eactive/eai/rms/data/ext/djb/apistatus/ApiStatus.java rename to src/main/java/com/eactive/eai/rms/data/entity/onl/djb/apistatus/ApiStatus.java index c7f1e17..8c46dd5 100644 --- a/src/main/java/com/eactive/eai/rms/data/ext/djb/apistatus/ApiStatus.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/apistatus/ApiStatus.java @@ -1,4 +1,4 @@ -package com.eactive.eai.rms.data.ext.djb.apistatus; +package com.eactive.eai.rms.data.entity.onl.djb.apistatus; import java.time.LocalDateTime; diff --git a/src/main/java/com/eactive/eai/rms/data/ext/djb/apistatus/ApiStatusEvent.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/apistatus/ApiStatusEvent.java similarity index 54% rename from src/main/java/com/eactive/eai/rms/data/ext/djb/apistatus/ApiStatusEvent.java rename to src/main/java/com/eactive/eai/rms/data/entity/onl/djb/apistatus/ApiStatusEvent.java index 5f2049e..aba632a 100644 --- a/src/main/java/com/eactive/eai/rms/data/ext/djb/apistatus/ApiStatusEvent.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/apistatus/ApiStatusEvent.java @@ -1,7 +1,8 @@ -package com.eactive.eai.rms.data.ext.djb.apistatus; +package com.eactive.eai.rms.data.entity.onl.djb.apistatus; public interface ApiStatusEvent { String getEaisvcname(); String getEaisvcdesc(); + String getPrevStatusCode(); String getEvent(); } \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/rms/data/ext/djb/inflow/InflowTokenInsufficient.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/inflow/InflowTokenInsufficient.java similarity index 66% rename from src/main/java/com/eactive/eai/rms/data/ext/djb/inflow/InflowTokenInsufficient.java rename to src/main/java/com/eactive/eai/rms/data/entity/onl/djb/inflow/InflowTokenInsufficient.java index c3c6ffc..64108f8 100644 --- a/src/main/java/com/eactive/eai/rms/data/ext/djb/inflow/InflowTokenInsufficient.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/inflow/InflowTokenInsufficient.java @@ -1,4 +1,4 @@ -package com.eactive.eai.rms.data.ext.djb.inflow; +package com.eactive.eai.rms.data.entity.onl.djb.inflow; public interface InflowTokenInsufficient { String getEaisvcname(); diff --git a/src/main/java/com/eactive/eai/rms/data/ext/djb/inflow/InflowTokenInsufficientLog.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/inflow/InflowTokenInsufficientLog.java similarity index 95% rename from src/main/java/com/eactive/eai/rms/data/ext/djb/inflow/InflowTokenInsufficientLog.java rename to src/main/java/com/eactive/eai/rms/data/entity/onl/djb/inflow/InflowTokenInsufficientLog.java index ae0ba77..e3cd9eb 100644 --- a/src/main/java/com/eactive/eai/rms/data/ext/djb/inflow/InflowTokenInsufficientLog.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/inflow/InflowTokenInsufficientLog.java @@ -1,4 +1,4 @@ -package com.eactive.eai.rms.data.ext.djb.inflow; +package com.eactive.eai.rms.data.entity.onl.djb.inflow; import java.io.Serializable; diff --git a/src/main/java/com/eactive/eai/rms/data/ext/djb/inflow/InflowTokenInsufficientLogId.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/inflow/InflowTokenInsufficientLogId.java similarity index 90% rename from src/main/java/com/eactive/eai/rms/data/ext/djb/inflow/InflowTokenInsufficientLogId.java rename to src/main/java/com/eactive/eai/rms/data/entity/onl/djb/inflow/InflowTokenInsufficientLogId.java index 0a2bae6..1568a34 100644 --- a/src/main/java/com/eactive/eai/rms/data/ext/djb/inflow/InflowTokenInsufficientLogId.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/inflow/InflowTokenInsufficientLogId.java @@ -1,4 +1,4 @@ -package com.eactive.eai.rms.data.ext.djb.inflow; +package com.eactive.eai.rms.data.entity.onl.djb.inflow; import java.io.Serializable; import javax.persistence.Column; diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookReq.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookReq.java new file mode 100644 index 0000000..02aa430 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookReq.java @@ -0,0 +1,33 @@ +package com.eactive.eai.rms.data.entity.onl.djb.webhook; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.Comment; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name = "PTL_WEBHOOK_REQ") +@Getter +@NoArgsConstructor +public class WebhookReq { + + @Id + @Column(name = "ID", length = 36, nullable = false) + private String id; + + @Column(name = "ORG_ID", length = 100) + @Comment("법인 ID") + private String orgId; + + @Column(name = "TARGET_URL", length = 500, nullable = false) + @Comment("웹훅 수신 URL") + private String targetUrl; + + @Column(name = "SECRET", length = 200) + @Comment("HMAC 서명용 Secret Key") + private String secret; +} diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookReqApi.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookReqApi.java new file mode 100644 index 0000000..df94810 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookReqApi.java @@ -0,0 +1,19 @@ +package com.eactive.eai.rms.data.entity.onl.djb.webhook; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.Table; +import java.io.Serializable; + +@Entity +@Table(name = "PTL_WEBHOOK_REQ_API") +@Getter +@NoArgsConstructor +public class WebhookReqApi implements Serializable { + + @EmbeddedId + private WebhookReqApiId id; +} diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookReqApiId.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookReqApiId.java new file mode 100644 index 0000000..784ea4a --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookReqApiId.java @@ -0,0 +1,25 @@ +package com.eactive.eai.rms.data.entity.onl.djb.webhook; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.Comment; + +import javax.persistence.Column; +import javax.persistence.Embeddable; +import java.io.Serializable; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Embeddable +public class WebhookReqApiId implements Serializable { + + @Column(name = "WEBHOOK_REQ_ID", nullable = false, length = 36) + @Comment("웹훅 요청 ID") + private String webhookReqId; + + @Column(name = "API_ID", nullable = false, length = 100) + @Comment("API ID") + private String apiId; +} diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookReqEvent.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookReqEvent.java new file mode 100644 index 0000000..5cc9b4b --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookReqEvent.java @@ -0,0 +1,19 @@ +package com.eactive.eai.rms.data.entity.onl.djb.webhook; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.Table; +import java.io.Serializable; + +@Entity +@Table(name = "PTL_WEBHOOK_REQ_EVENT") +@Getter +@NoArgsConstructor +public class WebhookReqEvent implements Serializable { + + @EmbeddedId + private WebhookReqEventId id; +} diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookReqEventId.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookReqEventId.java new file mode 100644 index 0000000..1af090e --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookReqEventId.java @@ -0,0 +1,25 @@ +package com.eactive.eai.rms.data.entity.onl.djb.webhook; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.Comment; + +import javax.persistence.Column; +import javax.persistence.Embeddable; +import java.io.Serializable; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Embeddable +public class WebhookReqEventId implements Serializable { + + @Column(name = "WEBHOOK_REQ_ID", nullable = false, length = 36) + @Comment("웹훅 요청 ID") + private String webhookReqId; + + @Column(name = "EVENT_TYPE", nullable = false, length = 50) + @Comment("이벤트 유형 (CONTROL_START, ERROR_START 등)") + private String eventType; +} diff --git a/src/main/java/com/eactive/eai/rms/data/ext/djb/webhook/WebhookSendLog.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookSendLog.java similarity index 94% rename from src/main/java/com/eactive/eai/rms/data/ext/djb/webhook/WebhookSendLog.java rename to src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookSendLog.java index f2bcdf2..5c82e6e 100644 --- a/src/main/java/com/eactive/eai/rms/data/ext/djb/webhook/WebhookSendLog.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookSendLog.java @@ -1,4 +1,4 @@ -package com.eactive.eai.rms.data.ext.djb.webhook; +package com.eactive.eai.rms.data.entity.onl.djb.webhook; import lombok.Getter; import lombok.NoArgsConstructor; @@ -42,6 +42,9 @@ public class WebhookSendLog { @Column(name = "STATUS_CODE") private Integer statusCode; + @Column(name = "ORG_ID") + private String orgId; + @Lob @Column(name = "RESPONSE_BODY") private String responseBody; diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/inflow/InflowControlService.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/inflow/InflowControlService.java index 9c1284f..d68c379 100644 --- a/src/main/java/com/eactive/eai/rms/data/entity/onl/inflow/InflowControlService.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/inflow/InflowControlService.java @@ -1,9 +1,6 @@ package com.eactive.eai.rms.data.entity.onl.inflow; -import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.stream.Collectors; import javax.persistence.EntityManager; @@ -18,6 +15,7 @@ import org.springframework.stereotype.Service; import com.eactive.apim.portal.app.entity.QCredential; import com.eactive.eai.data.entity.onl.adapter.QAdapterGroup; +import com.eactive.eai.data.entity.onl.authserver.QClientEntity; import com.eactive.eai.data.entity.onl.inflow.InflowControl; import com.eactive.eai.data.entity.onl.inflow.InflowControlId; import com.eactive.eai.data.entity.onl.inflow.QInflowControl; @@ -29,7 +27,6 @@ import com.eactive.eai.rms.onl.manage.inflow.history.InflowControlHistoryManUISe import com.eactive.eai.rms.onl.manage.inflow.inflow.InflowControlManMapper; import com.querydsl.core.BooleanBuilder; import com.querydsl.core.Tuple; -import com.querydsl.core.types.OrderSpecifier; import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; @@ -186,101 +183,67 @@ public class InflowControlService extends AbstractDataService findAllForClient(Pageable pageable, String searchName) { - QCredential qCredential = QCredential.credential; + + JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(entityManager); QInflowControl qInflowControl = QInflowControl.inflowControl; + QClientEntity qClientEntity = QClientEntity.clientEntity; - // EMSADM: Credential 목록 및 건수 조회 - JPAQueryFactory emsFactory = new JPAQueryFactory(entityManagerForEMS); - BooleanBuilder credPredicate = new BooleanBuilder(); - if (!StringUtils.isEmpty(searchName)) { - credPredicate.and(qCredential.clientname.contains(searchName)); - } - - long totalCount = emsFactory - .select(qCredential.clientid.count()) - .from(qCredential) - .where(credPredicate) - .fetchOne(); - - List credList = emsFactory - .select(qCredential.clientid, qCredential.clientname) - .from(qCredential) - .where(credPredicate) - .orderBy(qCredential.clientname.asc()) + List tupleList = jpaQueryFactory + .select(qInflowControl, qClientEntity.clientid, qClientEntity.clientname) + .from(qClientEntity) + .leftJoin(qInflowControl) + .on(qInflowControl.id.type.eq(CLIENT_TYPE_INFLOW) + .and(qInflowControl.id.name.eq(qClientEntity.clientid))) + .where(qClientEntity.clientname.contains(searchName)) + .orderBy(qClientEntity.clientname.asc()) .offset(pageable.getOffset()) .limit(pageable.getPageSize()) .fetch(); - if (credList.isEmpty()) { - return new PageImpl<>(Collections.emptyList(), pageable, totalCount); - } - - // AGWADM: 해당 clientId의 InflowControl 일괄 조회 - List clientIds = credList.stream() - .map(t -> t.get(qCredential.clientid)) - .collect(Collectors.toList()); - - Map inflowMap = new JPAQueryFactory(entityManager) - .selectFrom(qInflowControl) - .where(qInflowControl.id.type.eq(CLIENT_TYPE_INFLOW) - .and(qInflowControl.id.name.in(clientIds))) - .fetch() + List dtoList = tupleList .stream() - .collect(Collectors.toMap(ic -> ic.getId().getName(), ic -> ic)); - - // Java에서 병합 - List dtoList = credList.stream() - .map(tuple -> { - String clientId = tuple.get(qCredential.clientid); - InflowControl inflowControl = inflowMap.get(clientId); - InflowControlServiceDto dto; - if (inflowControl != null) { - dto = mapper.toDto(inflowControl); - } else { - dto = new InflowControlServiceDto(); - dto.setName(clientId); - dto.setType(CLIENT_TYPE_INFLOW); - } - dto.setDesc(tuple.get(qCredential.clientname)); - return dto; - }) + .map(tuple -> toDto(qClientEntity, qInflowControl, tuple)) .collect(Collectors.toList()); + long totalCount = jpaQueryFactory + .select(qClientEntity.clientname.count()) + .from(qClientEntity) + .where(qClientEntity.clientname.contains(searchName)) + .fetchOne(); return new PageImpl<>(dtoList, pageable, totalCount); } + private InflowControlServiceDto toDto(QClientEntity qClientEntity, QInflowControl qInflowControl, + Tuple tuple) { + InflowControlServiceDto dto = null; + if (tuple.get(qInflowControl) != null) + dto = mapper.toDto(tuple.get(qInflowControl)); + else { + dto = new InflowControlServiceDto(); + dto.setName(tuple.get(qClientEntity.clientid)); + dto.setType(CLIENT_TYPE_INFLOW); + } + dto.setDesc(tuple.get(qClientEntity.clientname)); + return dto; + } + public Page selectLogList(Pageable pageable, InflowControlHistoryManUISearch uiSearch) { QInflowControlLog qlog = QInflowControlLog.inflowControlLog; QInflowControlGroup qgroup = QInflowControlGroup.inflowControlGroup; diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsDayService.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsDayService.java index ad7fc2e..efe1566 100644 --- a/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsDayService.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsDayService.java @@ -120,11 +120,11 @@ public class ApiStatsDayService q.seq900BizErrCnt.sum().as("seq900BizErrCnt"), q.minRespTime.min().castToNum(BigDecimal.class).as("minRespTime"), q.maxRespTime.max().castToNum(BigDecimal.class).as("maxRespTime"), - // 가중 평균: SUM(avg * successCnt) / SUM(successCnt) + // 가중 평균: SUM(avg * totalCnt) / SUM(totalCnt) Expressions.cases() - .when(q.successCnt.sum().gt(0)) - .then(q.avgRespTime.multiply(q.successCnt).sum() - .divide(q.successCnt.sum()).castToNum(BigDecimal.class)) + .when(q.totalCnt.sum().gt(0)) + .then(q.avgRespTime.multiply(q.totalCnt).sum() + .divide(q.totalCnt.sum()).castToNum(BigDecimal.class)) .otherwise((BigDecimal) null) .as("avgRespTime"))) .from(q) diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsHourService.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsHourService.java index 91524a7..6d01415 100644 --- a/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsHourService.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsHourService.java @@ -211,11 +211,10 @@ public class ApiStatsHourService q.seq900BizErrCnt.sum().as("seq900BizErrCnt"), q.minRespTime.min().castToNum(BigDecimal.class).as("minRespTime"), q.maxRespTime.max().castToNum(BigDecimal.class).as("maxRespTime"), - // 가중 평균: SUM(avg * successCnt) / SUM(successCnt) Expressions.cases() - .when(q.successCnt.sum().gt(0)) - .then(q.avgRespTime.multiply(q.successCnt).sum() - .divide(q.successCnt.sum()).castToNum(BigDecimal.class)) + .when(q.totalCnt.sum().gt(0)) + .then(q.avgRespTime.multiply(q.totalCnt).sum() + .divide(q.totalCnt.sum()).castToNum(BigDecimal.class)) .otherwise((BigDecimal) null) .as("avgRespTime"))) .from(q) @@ -293,9 +292,9 @@ public class ApiStatsHourService q.minRespTime.min().castToNum(BigDecimal.class).as("minRespTime"), q.maxRespTime.max().castToNum(BigDecimal.class).as("maxRespTime"), Expressions.cases() - .when(q.successCnt.sum().gt(0)) - .then(q.avgRespTime.multiply(q.successCnt).sum() - .divide(q.successCnt.sum()).castToNum(BigDecimal.class)) + .when(q.totalCnt.sum().gt(0)) + .then(q.avgRespTime.multiply(q.totalCnt).sum() + .divide(q.totalCnt.sum()).castToNum(BigDecimal.class)) .otherwise((BigDecimal) null) .as("avgRespTime"))) .from(q) diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsMinuteService.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsMinuteService.java index 7466422..964fef6 100644 --- a/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsMinuteService.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsMinuteService.java @@ -214,11 +214,11 @@ public class ApiStatsMinuteService q.seq900BizErrCnt.sum().as("seq900BizErrCnt"), q.minRespTime.min().castToNum(BigDecimal.class).as("minRespTime"), q.maxRespTime.max().castToNum(BigDecimal.class).as("maxRespTime"), - // 가중 평균: SUM(avg * successCnt) / SUM(successCnt) + // 가중 평균: SUM(avg * totalCnt) / SUM(totalCnt) Expressions.cases() - .when(q.successCnt.sum().gt(0)) - .then(q.avgRespTime.multiply(q.successCnt).sum() - .divide(q.successCnt.sum()).castToNum(BigDecimal.class)) + .when(q.totalCnt.sum().gt(0)) + .then(q.avgRespTime.multiply(q.totalCnt).sum() + .divide(q.totalCnt.sum()).castToNum(BigDecimal.class)) .otherwise((BigDecimal) null) .as("avgRespTime"))) .from(q) diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsMonthService.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsMonthService.java index 4545eca..6bb2e3a 100644 --- a/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsMonthService.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsMonthService.java @@ -120,11 +120,11 @@ public class ApiStatsMonthService q.seq900BizErrCnt.sum().as("seq900BizErrCnt"), q.minRespTime.min().castToNum(BigDecimal.class).as("minRespTime"), q.maxRespTime.max().castToNum(BigDecimal.class).as("maxRespTime"), - // 가중 평균: SUM(avg * successCnt) / SUM(successCnt) + // 가중 평균: SUM(avg * totalCnt) / SUM(totalCnt) Expressions.cases() - .when(q.successCnt.sum().gt(0)) - .then(q.avgRespTime.multiply(q.successCnt).sum() - .divide(q.successCnt.sum()).castToNum(BigDecimal.class)) + .when(q.totalCnt.sum().gt(0)) + .then(q.avgRespTime.multiply(q.totalCnt).sum() + .divide(q.totalCnt.sum()).castToNum(BigDecimal.class)) .otherwise((BigDecimal) null) .as("avgRespTime"))) .from(q) @@ -172,9 +172,9 @@ public class ApiStatsMonthService q.minRespTime.min().castToNum(BigDecimal.class).as("minRespTime"), q.maxRespTime.max().castToNum(BigDecimal.class).as("maxRespTime"), Expressions.cases() - .when(q.successCnt.sum().gt(0)) - .then(q.avgRespTime.multiply(q.successCnt).sum() - .divide(q.successCnt.sum()).castToNum(BigDecimal.class)) + .when(q.totalCnt.sum().gt(0)) + .then(q.avgRespTime.multiply(q.totalCnt).sum() + .divide(q.totalCnt.sum()).castToNum(BigDecimal.class)) .otherwise((BigDecimal) null) .as("avgRespTime"))) .from(q) diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsYearService.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsYearService.java index 65842b0..8d2f080 100644 --- a/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsYearService.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/kjb/statistics/ApiStatsYearService.java @@ -117,11 +117,11 @@ public class ApiStatsYearService q.seq900BizErrCnt.sum().as("seq900BizErrCnt"), q.minRespTime.min().castToNum(BigDecimal.class).as("minRespTime"), q.maxRespTime.max().castToNum(BigDecimal.class).as("maxRespTime"), - // 가중 평균: SUM(avg * successCnt) / SUM(successCnt) + // 가중 평균: SUM(avg * totalCnt) / SUM(totalCnt) Expressions.cases() - .when(q.successCnt.sum().gt(0)) - .then(q.avgRespTime.multiply(q.successCnt).sum() - .divide(q.successCnt.sum()).castToNum(BigDecimal.class)) + .when(q.totalCnt.sum().gt(0)) + .then(q.avgRespTime.multiply(q.totalCnt).sum() + .divide(q.totalCnt.sum()).castToNum(BigDecimal.class)) .otherwise((BigDecimal) null) .as("avgRespTime"))) .from(q) @@ -169,9 +169,9 @@ public class ApiStatsYearService q.minRespTime.min().castToNum(BigDecimal.class).as("minRespTime"), q.maxRespTime.max().castToNum(BigDecimal.class).as("maxRespTime"), Expressions.cases() - .when(q.successCnt.sum().gt(0)) - .then(q.avgRespTime.multiply(q.successCnt).sum() - .divide(q.successCnt.sum()).castToNum(BigDecimal.class)) + .when(q.totalCnt.sum().gt(0)) + .then(q.avgRespTime.multiply(q.totalCnt).sum() + .divide(q.totalCnt.sum()).castToNum(BigDecimal.class)) .otherwise((BigDecimal) null) .as("avgRespTime"))) .from(q) diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/stdmessage/ExtendedColumnDefinitionService.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/stdmessage/ExtendedColumnDefinitionService.java index 5567d62..2b44c59 100644 --- a/src/main/java/com/eactive/eai/rms/data/entity/onl/stdmessage/ExtendedColumnDefinitionService.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/stdmessage/ExtendedColumnDefinitionService.java @@ -5,7 +5,9 @@ import java.util.List; import org.apache.commons.lang3.StringUtils; 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; @@ -42,7 +44,12 @@ public class ExtendedColumnDefinitionService predicate = predicate.and(qExtendedColumnDefinition.columnDesc.containsIgnoreCase(searchColumnDesc)); } - return repository.findAll(predicate, pageable); + Pageable orderedPageable = PageRequest.of( + pageable.getPageNumber(), + pageable.getPageSize(), + Sort.by(Sort.Order.asc("orderSeq"), Sort.Order.asc("columnName"))); + + return repository.findAll(predicate, orderedPageable); } } diff --git a/src/main/java/com/eactive/eai/rms/data/ext/djb/apistatus/ApiStatusService.java b/src/main/java/com/eactive/eai/rms/data/ext/djb/apistatus/ApiStatusService.java deleted file mode 100644 index ac35442..0000000 --- a/src/main/java/com/eactive/eai/rms/data/ext/djb/apistatus/ApiStatusService.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.eactive.eai.rms.data.ext.djb.apistatus; - -import java.util.HashMap; -import java.util.List; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import com.eactive.eai.rms.ext.djb.event.ApiStatusChangedEvent; - -@Service -@Transactional -public class ApiStatusService { - - private static final Logger log = LoggerFactory.getLogger(ApiStatusService.class); - - @Autowired - private ApiStatusRepository apiStatusRepository; - - @Autowired - private ApplicationEventPublisher eventPublisher; - - - - public void updateApiStatus(HashMap param) { - - List list = apiStatusRepository.findApiStatusEvents( - Integer.parseInt(param.get("errorRate")), - Integer.parseInt(param.get("errorRangeMinute")), - Integer.parseInt(param.get("delayRangeMinute")), - Integer.parseInt(param.get("delayAvgRespTime")) - ); - - for (ApiStatusEvent event : list) { - String newStatusCode = resolveStatusCode(event.getEvent()); - if (newStatusCode == null) continue; - - ApiStatus apiStatus = apiStatusRepository.findById(event.getEaisvcname()).orElse(new ApiStatus()); - - apiStatus.setEaisvcname(event.getEaisvcname()); - apiStatus.setStatusCode(newStatusCode); - - apiStatusRepository.save(apiStatus); // PK 있으면 UPDATE, 없으면 INSERT - log.debug("API 상태 변경: {}-{} {} → {}", event.getEaisvcname(), event.getEaisvcdesc(), event.getEvent(), newStatusCode); - - eventPublisher.publishEvent(ApiStatusChangedEvent.from(event)); // 트랜잭션 커밋 후 리스너 실행 - log.debug("이벤트 전파"); - } - } - - - private String resolveStatusCode(String event) { - switch (event) { - case "CONTROL_START": return "C"; //점검 - case "CONTROL_END": return "N"; //정상 - case "ERROR_START": return "E"; //장애 - case "ERROR_END": return "N"; //정상 - case "DELAY_START": return "D"; //지연 - case "DELAY_END": return "N"; //정상 - default: - log.warn("알 수 없는 이벤트: {}", event); - return null; - } - } -} diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusDetectionService.java b/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusDetectionService.java new file mode 100644 index 0000000..f8687ef --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusDetectionService.java @@ -0,0 +1,41 @@ +package com.eactive.eai.rms.ext.djb.apistatus; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional +public class ApiStatusDetectionService { + + private static final Logger log = LoggerFactory.getLogger(ApiStatusDetectionService.class); + + /** + * 이벤트 감지 + * @param event "CONTROL_START" : 점검시작 + "CONTROL_END" : 점검종료 + "ERROR_START" : 장애시작 + "ERROR_END" : 장애종료 + "DELAY_START" : 지연시작 + "DELAY_END" : 지연종료 + * @param apiIds API IDs + */ + public void detect(String event, List apiIds) { + log.debug("이벤트 감지: {} {}", event, apiIds); + } + + // API FSM 내 아직 장애로 남아있는 API 목록 + public void remainDownApiIds() { + + } + + + // 정상 동작 - detect()에서 일괄 처리 (복구 = 점검종료, 장애종료, 지연종료) +// public void recovered(String event, String[] apiIds) { +// +// } + +} \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/rms/data/ext/djb/apistatus/ApiStatusRepository.java b/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusRepository.java similarity index 80% rename from src/main/java/com/eactive/eai/rms/data/ext/djb/apistatus/ApiStatusRepository.java rename to src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusRepository.java index f716bb0..750d3f7 100644 --- a/src/main/java/com/eactive/eai/rms/data/ext/djb/apistatus/ApiStatusRepository.java +++ b/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusRepository.java @@ -1,10 +1,12 @@ -package com.eactive.eai.rms.data.ext.djb.apistatus; +package com.eactive.eai.rms.ext.djb.apistatus; import java.util.List; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import com.eactive.eai.data.jpa.BaseRepository; +import com.eactive.eai.rms.data.entity.onl.djb.apistatus.ApiStatus; +import com.eactive.eai.rms.data.entity.onl.djb.apistatus.ApiStatusEvent; public interface ApiStatusRepository extends BaseRepository { @@ -22,9 +24,11 @@ public interface ApiStatusRepository extends BaseRepository { @Query(nativeQuery = true, value = " SELECT EAISVCNAME" + " , (SELECT EAISVCDESC FROM TSEAIHE01 WHERE A.EAISVCNAME = EAISVCNAME) AS EAISVCDESC" + + " , PREV_STATUS_CODE " + " , EVENT" + " FROM (" + " SELECT EAISVCNAME" + + " , STATUS_CODE AS PREV_STATUS_CODE " + " , CASE WHEN STATUS_CODE != 'C' AND CTRL_YN = 'Y' THEN 'CONTROL_START'" + " WHEN STATUS_CODE = 'C' AND CTRL_YN = 'N' THEN 'CONTROL_END'" + " WHEN STATUS_CODE IN ('N','D') AND ERR_YN = 'Y' THEN 'ERROR_START'" @@ -34,14 +38,14 @@ public interface ApiStatusRepository extends BaseRepository { + " ELSE 'STAY' END AS EVENT" + " FROM (" + " SELECT A.EAISVCNAME" - + " , NVL(B.STATUS_CODE, '1') AS STATUS_CODE" - + " , NVL((SELECT 'Y' FROM TSEAITI01" - + " WHERE (TO_CHAR(SYSDATE,'HH24MI') BETWEEN SUBSTR(EAICTRLDSTICCTNT,16,4) AND SUBSTR(EAICTRLDSTICCTNT,21,4)" - + " OR SUBSTR(EAICTRLDSTICCTNT,16,9) = '0000|0000')" - + " AND A.EAISVCNAME = EAICTRLNAME),'N') AS CTRL_YN" + + " , 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 " + " , (SELECT CASE WHEN TOTAL = 0 THEN 'X'" - + " WHEN (TOTAL - SUCCESS) * 100 / TOTAL > :errorRate THEN 'Y'" - + " ELSE 'N' END" + + " WHEN (TOTAL - SUCCESS) * 100 / TOTAL > :errorRate THEN 'Y'" + + " ELSE 'N' END" + " FROM (SELECT SUM(SUCCESS_CNT + TIMEOUT_CNT + SYSTEM_ERR_CNT + BIZ_ERR_CNT) AS TOTAL" + " , SUM(SUCCESS_CNT) AS SUCCESS" + " FROM API_STATS_MINUTE" diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusService.java b/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusService.java new file mode 100644 index 0000000..55615ad --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusService.java @@ -0,0 +1,133 @@ +package com.eactive.eai.rms.ext.djb.apistatus; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +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.apistatus.ApiStatus; +import com.eactive.eai.rms.data.entity.onl.djb.apistatus.ApiStatusEvent; +import com.eactive.eai.rms.ext.djb.ums.UmsManager; + + +@Service +@Transactional +public class ApiStatusService { + + private static final Logger log = LoggerFactory.getLogger(ApiStatusService.class); + + @Autowired + private ApiStatusRepository apiStatusRepository; + + @Autowired + private UmsManager ums; + + @Autowired + private ApiStatusDetectionService apiStatusDectionService; + + + public void updateApiStatus(HashMap param) { + + List list = apiStatusRepository.findApiStatusEvents( + Integer.parseInt(param.get("errorRate")), + Integer.parseInt(param.get("errorRangeMinute")), + Integer.parseInt(param.get("delayRangeMinute")), + Integer.parseInt(param.get("delayAvgRespTime")) + ); + + HashMap> eventMap = new HashMap<>(); + + + for (ApiStatusEvent event : list) { + String newStatusCode = resolveStatusCode(event.getEvent()); + if (newStatusCode == null) continue; + + ApiStatus apiStatus = apiStatusRepository.findById(event.getEaisvcname()).orElse(new ApiStatus()); + + apiStatus.setEaisvcname(event.getEaisvcname()); + apiStatus.setStatusCode(newStatusCode); + + apiStatusRepository.save(apiStatus); // PK 있으면 UPDATE, 없으면 INSERT + log.debug("API 상태 변경: {}-{} {} → {}", event.getEaisvcname(), event.getEaisvcdesc(), event.getEvent(), newStatusCode); + + + //이벤트별로 api분리 저장 + List apis = (List)eventMap.get(event.getEvent()); + if (apis == null) { + apis = new ArrayList(); + apis.add(event.getEaisvcname()); + eventMap.put(event.getEvent(), apis); + } else { + apis.add(event.getEaisvcname()); + } + } + + //이벤트별로 ums, webhook을 발송한다 + for (Map.Entry> entry : eventMap.entrySet()) { + String event = entry.getKey(); + List apiIds = entry.getValue(); + + String message = getSwingChatMessage(event, apiIds); + ums.sendMessenger("api-monitor", MessageCode.API_STATUS_CHANGED, message); + + //제휴사 웹훅 발송 + ums.sendWebhook(event, apiIds); + + //개발자포탈 API상태모니터링 화면을 위한 처리 + apiStatusDectionService.detect(event, apiIds); + } + } + + + private String resolveStatusCode(String event) { + switch (event) { + case "CONTROL_START": return "C"; //점검 + case "CONTROL_END": return "N"; //정상 + case "ERROR_START": return "E"; //장애 + case "ERROR_END": return "N"; //정상 + case "DELAY_START": return "D"; //지연 + case "DELAY_END": return "N"; //정상 + default: + log.warn("알 수 없는 이벤트: {}", event); + return null; + } + } + + + private String getSwingChatMessage(String event, List apiIds) { + String message = ""; + switch (event) { + case "CONTROL_START": + message = "API 서비스 점검이 시작되었습니다"; //점검 + break; + case "CONTROL_END": + message = "API 서비스 점검이 완료되었습니다"; //정상 + break; + case "ERROR_START": + message = "API 서비스 장애가 발생하였습니다"; //장애 + break; + case "ERROR_END": + message = "API 서비스 장애가 복구되었습니다"; //정상 + break; + case "DELAY_START": + message = "API 서비스 지연이 발생하였습니다"; //지연 + break; + case "DELAY_END": + message = "API 서비스 지연이 복구되었습니다"; //정상 + break; + default: + message = ""; + } + + return message + "\n- " + String.join(",", apiIds); + } +} + diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/async/AsyncConfig.java b/src/main/java/com/eactive/eai/rms/ext/djb/async/AsyncConfig.java deleted file mode 100644 index b62f619..0000000 --- a/src/main/java/com/eactive/eai/rms/ext/djb/async/AsyncConfig.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.eactive.eai.rms.ext.djb.async; - -import java.util.concurrent.Executor; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.scheduling.annotation.EnableAsync; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; - -@Configuration -@EnableAsync -public class AsyncConfig { - - @Bean("asyncExecutor") - public Executor asyncExecutor() { - ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); - executor.setCorePoolSize(2); - executor.setMaxPoolSize(5); - executor.setQueueCapacity(20); - executor.setThreadNamePrefix("async-"); - executor.initialize(); - return executor; - } - -} \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/async/AsyncEventListener.java b/src/main/java/com/eactive/eai/rms/ext/djb/async/AsyncEventListener.java deleted file mode 100644 index 63c5089..0000000 --- a/src/main/java/com/eactive/eai/rms/ext/djb/async/AsyncEventListener.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.eactive.eai.rms.ext.djb.async; - -import org.springframework.context.event.EventListener; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Component; -import org.springframework.transaction.event.TransactionPhase; -import org.springframework.transaction.event.TransactionalEventListener; - -import com.eactive.eai.rms.ext.djb.event.ApiStatusChangedEvent; -import com.eactive.eai.rms.ext.djb.event.InflowTokenInsufficientEvent; -import com.eactive.eai.rms.ext.djb.swing.service.SwingSendManager; -import com.eactive.eai.rms.ext.djb.webhook.service.WebhookSendManager; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -@Component -@RequiredArgsConstructor -public class AsyncEventListener { - - private final WebhookSendManager webhookSendManager; - - private final SwingSendManager swingSendManager; - - @Async("asyncExecutor") - @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) - public void onApiStatusChanged(ApiStatusChangedEvent event) { - log.debug("API 상태 변경 이벤트 수신: {} {}", event.getEaisvcname(), event.getEvent()); - webhookSendManager.send(event.getEaisvcname(), event.getEvent()); - swingSendManager.send(null, 0, 0); - } - - @Async("asyncExecutor") - @EventListener // 트랜잭션 write 없이 read만 하므로 TransactionalEventListener 불필요 - public void onInflowTokenFail(InflowTokenInsufficientEvent event) { - log.debug("유량제어 토큰획득 실패 이벤트 수신: {} {}건", event.getEaisvcname(), event.getCnt()); - swingSendManager.send(event.getEaisvcname(), event.getCnt(), event.getRangeMinute()); - } -} \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/event/ApiStatusChangedEvent.java b/src/main/java/com/eactive/eai/rms/ext/djb/event/ApiStatusChangedEvent.java deleted file mode 100644 index 6f6dc95..0000000 --- a/src/main/java/com/eactive/eai/rms/ext/djb/event/ApiStatusChangedEvent.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.eactive.eai.rms.ext.djb.event; - -import com.eactive.eai.rms.data.ext.djb.apistatus.ApiStatusEvent; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor -public class ApiStatusChangedEvent { - private final String eaisvcname; - private final String eaisvcdesc; - private final String event; - - public static ApiStatusChangedEvent from(ApiStatusEvent source) { - return new ApiStatusChangedEvent(source.getEaisvcname(), source.getEaisvcdesc(), source.getEvent()); - } -} \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/event/InflowTokenInsufficientEvent.java b/src/main/java/com/eactive/eai/rms/ext/djb/event/InflowTokenInsufficientEvent.java deleted file mode 100644 index b4b010e..0000000 --- a/src/main/java/com/eactive/eai/rms/ext/djb/event/InflowTokenInsufficientEvent.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.eactive.eai.rms.ext.djb.event; - -import com.eactive.eai.rms.data.ext.djb.inflow.InflowTokenInsufficient; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor -public class InflowTokenInsufficientEvent { - private final String eaisvcname; - private final String eaisvcdesc; - private final long cnt; - private final long rangeMinute; - - public static InflowTokenInsufficientEvent from(InflowTokenInsufficient summary, long rangeMinute) { - return new InflowTokenInsufficientEvent(summary.getEaisvcname(), summary.getEaisvcdesc(), summary.getCnt(), rangeMinute); - } -} \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/rms/data/ext/djb/inflow/InflowTokenRepository.java b/src/main/java/com/eactive/eai/rms/ext/djb/inflow/InflowTokenRepository.java similarity index 73% rename from src/main/java/com/eactive/eai/rms/data/ext/djb/inflow/InflowTokenRepository.java rename to src/main/java/com/eactive/eai/rms/ext/djb/inflow/InflowTokenRepository.java index 8dadc25..d98c99c 100644 --- a/src/main/java/com/eactive/eai/rms/data/ext/djb/inflow/InflowTokenRepository.java +++ b/src/main/java/com/eactive/eai/rms/ext/djb/inflow/InflowTokenRepository.java @@ -1,9 +1,12 @@ -package com.eactive.eai.rms.data.ext.djb.inflow; +package com.eactive.eai.rms.ext.djb.inflow; import java.util.List; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import com.eactive.eai.data.jpa.BaseRepository; +import com.eactive.eai.rms.data.entity.onl.djb.inflow.InflowTokenInsufficient; +import com.eactive.eai.rms.data.entity.onl.djb.inflow.InflowTokenInsufficientLog; +import com.eactive.eai.rms.data.entity.onl.djb.inflow.InflowTokenInsufficientLogId; public interface InflowTokenRepository extends BaseRepository { diff --git a/src/main/java/com/eactive/eai/rms/data/ext/djb/inflow/InflowTokenService.java b/src/main/java/com/eactive/eai/rms/ext/djb/inflow/InflowTokenService.java similarity index 62% rename from src/main/java/com/eactive/eai/rms/data/ext/djb/inflow/InflowTokenService.java rename to src/main/java/com/eactive/eai/rms/ext/djb/inflow/InflowTokenService.java index e4e91fa..5b00c96 100644 --- a/src/main/java/com/eactive/eai/rms/data/ext/djb/inflow/InflowTokenService.java +++ b/src/main/java/com/eactive/eai/rms/ext/djb/inflow/InflowTokenService.java @@ -1,15 +1,17 @@ -package com.eactive.eai.rms.data.ext.djb.inflow; +package com.eactive.eai.rms.ext.djb.inflow; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.eactive.eai.rms.ext.djb.event.InflowTokenInsufficientEvent; +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; @Service @@ -22,16 +24,16 @@ public class InflowTokenService { private InflowTokenRepository inflowTokenRepository; @Autowired - private ApplicationEventPublisher eventPublisher; + private UmsManager ums; public void checkRecentFails(long rangeMinute) { List rows = inflowTokenRepository.countTokenInsufficient(rangeMinute); for (InflowTokenInsufficient info : rows) { log.debug("유량제어 토큰 획득 실패: {}-{} 최근 {}분 동안 {}건", info.getEaisvcname(), info.getEaisvcdesc(), rangeMinute, info.getCnt()); - - //내부직원 알림 발송 - eventPublisher.publishEvent(InflowTokenInsufficientEvent.from(info, rangeMinute)); + String message = "유량제어 토큰 획득 실패하였습니다 \n" + info.getEaisvcname(); + + ums.sendMessenger("api-monitor", MessageCode.INFLOW_TOKEN_FAILED, message); } } } diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/job/ApiStatsHourlyAggregationJob.java b/src/main/java/com/eactive/eai/rms/ext/djb/job/ApiStatsHourlyAggregationJob.java new file mode 100644 index 0000000..6b840c3 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/ext/djb/job/ApiStatsHourlyAggregationJob.java @@ -0,0 +1,143 @@ +package com.eactive.eai.rms.ext.djb.job; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; + +import org.quartz.Job; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.quartz.SchedulerException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import com.eactive.eai.rms.common.datasource.DataSourceContextHolder; +import com.eactive.eai.rms.common.datasource.DataSourceType; +import com.eactive.eai.rms.common.datasource.DataSourceTypeManager; +import com.eactive.eai.rms.common.util.CommonUtil; +import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsHourService; +import com.eactive.eai.rms.data.entity.onl.kjb.statistics.QApiStatsHour; + +/** + * API 로그 테이블을 시간별 통계로 집계하는 배치 작업 + * TSEAILGXX → API_STATS_HOUR + * 실행 주기: 매시 00:10 (당일 데이터 집계) + */ +@Component +public class ApiStatsHourlyAggregationJob implements Job { + private static final Logger log = LoggerFactory.getLogger(ApiStatsHourlyAggregationJob.class); + + @PersistenceContext + private EntityManager entityManager; + + private ApiStatsHourService apiStatsHourService; + + @Autowired + public void setApiStatsHourService(ApiStatsHourService apiStatsHourService) { + this.apiStatsHourService = apiStatsHourService; + } + + @Override + public void execute(JobExecutionContext context) throws JobExecutionException { + ApplicationContext appContext = null; + final ApiStatsHourlyAggregationJob selfJob; + try { + appContext = (ApplicationContext) context.getScheduler().getContext().get("applicationContext"); + selfJob = appContext.getBean(ApiStatsHourlyAggregationJob.class); + } catch (SchedulerException e) { + log.error("applicationContext get module error", e); + return; + } + + DataSourceType dataType = DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.APIGW); + DataSourceContextHolder.setDataSourceType(dataType); + try { + LocalDate targetDate = LocalDate.now(); + if (LocalTime.now().isBefore(LocalTime.of(1, 0))) { + targetDate = targetDate.minusDays(1); + } + selfJob.executeManual(targetDate); + } catch (Exception e) { + throw new JobExecutionException(e); + } finally { + DataSourceContextHolder.clearDataSourceType(); + } + } + + /** + * 수동 실행 메서드 (날짜 지정) + */ + @Transactional + public int executeManual(LocalDate targetDate) { + long startTime = System.currentTimeMillis(); + log.info("=== 시간별 통계 집계 작업 시작 === 대상: {}", targetDate); + + String searchDate = targetDate.format(DateTimeFormatter.ofPattern("yyyyMMdd")); + String logTableName = CommonUtil.getLogTable(searchDate, true); + + // 1. 기존 데이터 삭제 + QApiStatsHour q = QApiStatsHour.apiStatsHour; + long deletedCount = apiStatsHourService.getJPAQueryFactory() + .delete(q) + .where(q.statTime.goe(targetDate.atStartOfDay()) + .and(q.statTime.lt(targetDate.plusDays(1).atStartOfDay()))) + .execute(); + log.info("기존 데이터 삭제: {} 건", deletedCount); + + // 2. 집계 및 데이터 입력 + /** + * EAISVCSERNO로 그룹핑하여 1행으로 집계를 한 다음 (한거래 안에서 adapter가 여러개 있을 경우 seq=100의 adapter를 기준) + * 총건수 : EAISVCSERNO로 그룹핑한 총 건수 + * 성공건수 : seq = 400 and EAIERRCD = null 인 건수 + * Timeout : EAIERRCD in (공통코드 CODEGROUP = 'ERRCODE_TIMEOUT') 인 건수 + * 시스템오류 : (seq400 = null or EAIERRCD is not null) and 공통코드(ERRCODE_TIMEOUT)에 정의되지 않은 errorcode 인 건수 + */ + String sql = + "INSERT INTO API_STATS_HOUR" + + " (STAT_TIME, API_NAME, GW_INSTANCE_ID, BIZ_DIV_CODE, CLIENT_ID" + + " , INBOUND_ADAPTER, OUTBOUND_ADAPTER" + + ", TOTAL_CNT, SUCCESS_CNT, TIMEOUT_CNT, SYSTEM_ERR_CNT, BIZ_ERR_CNT" + + ", SEQ900_TIMEOUT_CNT, SEQ900_SYSTEM_ERR_CNT, SEQ900_BIZ_ERR_CNT" + + ", AVG_RESP_TIME, MIN_RESP_TIME, MAX_RESP_TIME, P50_RESP_TIME, P95_RESP_TIME)" + + " SELECT TO_TIMESTAMP(SUBSTR(DT,1,10) || '0000', 'YYYYMMDDHH24MISS')" + + " , API_NAME, GW_INSTANCE_ID, BIZ_DIV_CODE, NVL(A.CLIENT_ID, 'NONE')" + + " , INBOUND_ADAPTER, OUTBOUND_ADAPTER" + + " , COUNT(EAISVCSERNO)" + + " , SUM(CASE WHEN A.ERROR_CODE IS NULL AND E400 IS NOT NULL THEN 1 ELSE 0 END)" + + " , SUM(CASE WHEN B.CODE IS NOT NULL THEN 1 ELSE 0 END)" + + " , SUM(CASE WHEN (A.E400 IS NULL OR A.ERROR_CODE IS NOT NULL) AND B.CODE IS NULL THEN 1 ELSE 0 END)" + + " , 0, 0, 0, 0" + + " , TRUNC(AVG(RESP_TIME)), MIN(RESP_TIME), MAX(RESP_TIME), 0, 0" + + " FROM (" + + " SELECT EAISVCSERNO" + + " , MAX(EAISVCNAME) AS API_NAME, MAX(EAISEVRINSTNCNAME) AS GW_INSTANCE_ID" + + " , MAX(EAIBZWKDSTCD) AS BIZ_DIV_CODE, MAX(CLIENTID) AS CLIENT_ID" + + " , MAX(CASE WHEN LOGPRCSSSERNO = '100' THEN GSTATSYSADPTRBZWKGROUPNAME ELSE '' END) AS INBOUND_ADAPTER" + + " , MAX(CASE WHEN LOGPRCSSSERNO = '100' THEN PSVSYSADPTRBZWKGROUPNAME ELSE '' END) AS OUTBOUND_ADAPTER" + + " , MIN(MSGDPSTYMS) AS DT" + + " , MAX(MSGPRCSSYMS) - MIN(MSGDPSTYMS) AS RESP_TIME" + + " , MAX(CASE WHEN LOGPRCSSSERNO = '400' THEN LOGPRCSSSERNO ELSE '' END) AS E400" + + " , MAX(EAIERRCD) AS ERROR_CODE" + + " FROM " + logTableName + + " WHERE MSGDPSTYMS LIKE :searchDate || '%'" + + " GROUP BY EAISVCSERNO" + + " ) A LEFT OUTER JOIN TSEAICM20 B ON A.ERROR_CODE = B.CODE AND B.CODEGROUP = 'ERRCODE_TIMEOUT' AND B.USEYN = 'Y' " + + " GROUP BY SUBSTR(DT,1,10), API_NAME, GW_INSTANCE_ID, BIZ_DIV_CODE, NVL(A.CLIENT_ID, 'NONE')" + + " , INBOUND_ADAPTER, OUTBOUND_ADAPTER"; + + int savedCount = entityManager.createNativeQuery(sql) + .setParameter("searchDate", searchDate) + .executeUpdate(); + + long elapsedTime = System.currentTimeMillis() - startTime; + log.info("=== 집계 완료 === (처리 건수: {}, 소요 시간: {}ms)", savedCount, elapsedTime); + return savedCount; + } +} diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/job/ApiStatusMonitorJob.java b/src/main/java/com/eactive/eai/rms/ext/djb/job/ApiStatusMonitorJob.java index dbd3b66..6d0dd5c 100644 --- a/src/main/java/com/eactive/eai/rms/ext/djb/job/ApiStatusMonitorJob.java +++ b/src/main/java/com/eactive/eai/rms/ext/djb/job/ApiStatusMonitorJob.java @@ -17,7 +17,7 @@ import com.eactive.eai.rms.common.context.MonitoringContext; import com.eactive.eai.rms.common.datasource.DataSourceContextHolder; import com.eactive.eai.rms.common.datasource.DataSourceTypeManager; import com.eactive.eai.rms.common.util.CommonUtil; -import com.eactive.eai.rms.data.ext.djb.apistatus.ApiStatusService; +import com.eactive.eai.rms.ext.djb.apistatus.ApiStatusService; import com.eactive.eai.rms.onl.common.util.DateUtil; /** @@ -88,11 +88,11 @@ public class ApiStatusMonitorJob implements Job { monitoringContext = (MonitoringContext) appContext.getBean("monitoringContext"); ApiStatusService apiStatusUpdateService = appContext.getBean(ApiStatusService.class); - DataSourceContextHolder.setDataSourceType( DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.APIGW)); try { apiStatusUpdateService.updateApiStatus(param); + } catch (Exception e) { log.error("ApiStatusUpdateJob execution failed", e); throw new JobExecutionException(e); diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/job/InflowTokenMonitorJob.java b/src/main/java/com/eactive/eai/rms/ext/djb/job/InflowTokenMonitorJob.java index f9dc9b7..bcb1a5f 100644 --- a/src/main/java/com/eactive/eai/rms/ext/djb/job/InflowTokenMonitorJob.java +++ b/src/main/java/com/eactive/eai/rms/ext/djb/job/InflowTokenMonitorJob.java @@ -18,7 +18,7 @@ import com.eactive.eai.rms.common.context.MonitoringContext; import com.eactive.eai.rms.common.datasource.DataSourceContextHolder; import com.eactive.eai.rms.common.datasource.DataSourceTypeManager; import com.eactive.eai.rms.common.util.CommonUtil; -import com.eactive.eai.rms.data.ext.djb.inflow.InflowTokenService; +import com.eactive.eai.rms.ext.djb.inflow.InflowTokenService; import com.eactive.eai.rms.onl.common.util.DateUtil; /** diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/job/PortalInquiryClosingJob.java b/src/main/java/com/eactive/eai/rms/ext/djb/job/PortalInquiryClosingJob.java new file mode 100644 index 0000000..2c976cd --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/ext/djb/job/PortalInquiryClosingJob.java @@ -0,0 +1,73 @@ +package com.eactive.eai.rms.ext.djb.job; + +import com.eactive.eai.rms.common.datasource.DataSourceContextHolder; +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.quartz.DisallowConcurrentExecution; +import org.quartz.Job; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * 매일 00:05 실행 - RESPONDED 상태 문의글 자동 종료 배치 + * + *

종료 조건 (기본 7일):

+ *
    + *
  • 답변완료 후 댓글이 7일간 없는 경우
  • + *
  • 관리자 댓글 등록 후 7일간 포탈사용자 댓글이 없는 경우
  • + *
+ * + *

Cron: 0 5 0 * * ?

+ * + *

Job 파라미터 (JobDataMap):

+ *
    + *
  • {@value #KEY_INQUIRY_COMMENT_CLOSING_DAY}: 종료 기준 일수 (기본값: 7)
  • + *
+ */ +@DisallowConcurrentExecution +public class PortalInquiryClosingJob implements Job { + + private static final Logger log = LoggerFactory.getLogger(PortalInquiryClosingJob.class); + + /** Job 파라미터 키: 댓글종료 기간(일) */ + public static final String KEY_INQUIRY_COMMENT_CLOSING_DAY = "inquiry.comment.closing_day"; + + private static final int DEFAULT_CLOSING_DAYS = 7; + + @Autowired + private PortalInquiryClosingService inquiryCommentClosingService; + + @Override + public void execute(JobExecutionContext context) throws JobExecutionException { + log.info("*** START PortalInquiryCommentClosingService run({})", CommonUtil.getToday("yyyy-MM-dd HH:mm")); + + int closingDays = DEFAULT_CLOSING_DAYS; + String closingDayParam = context.getMergedJobDataMap().getString(KEY_INQUIRY_COMMENT_CLOSING_DAY); + if (closingDayParam != null) { + try { + closingDays = Integer.parseInt(closingDayParam); + } catch (NumberFormatException e) { + log.warn("잘못된 {} 파라미터 값: '{}', 기본값 {}일 사용", KEY_INQUIRY_COMMENT_CLOSING_DAY, closingDayParam, DEFAULT_CLOSING_DAYS); + } + } + + DataSourceContextHolder.setDataSourceType( + DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.APIGW)); + try { + int closedCount = inquiryCommentClosingService.closeExpiredInquiries(closingDays); + log.info("PortalInquiryCommentClosingService 완료: {}건 CLOSED 처리", closedCount); + } catch (Exception e) { + log.error("PortalInquiryCommentClosingService execution failed", e); + throw new JobExecutionException(e); + } finally { + DataSourceContextHolder.clearDataSourceType(); + } + + log.info("*** END PortalInquiryCommentClosingService run({})", DateUtil.getDateTime("yyyy-MM-dd HH:mm")); + } +} diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/statistics/ApiUseStatsController.java b/src/main/java/com/eactive/eai/rms/ext/djb/statistics/ApiUseStatsController.java index 3b79e3c..90278a1 100644 --- a/src/main/java/com/eactive/eai/rms/ext/djb/statistics/ApiUseStatsController.java +++ b/src/main/java/com/eactive/eai/rms/ext/djb/statistics/ApiUseStatsController.java @@ -1,29 +1,20 @@ package com.eactive.eai.rms.ext.djb.statistics; import java.io.IOException; -import java.time.LocalDate; +import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.List; -import java.util.stream.Collectors; import javax.servlet.http.HttpServletResponse; -import org.apache.commons.lang3.StringUtils; 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.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import com.eactive.eai.rms.common.vo.GridResponse; -import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsDay; -import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsDayService; -import com.eactive.ext.kjb.statistics.mapping.ApiStatsUIMapper; -import com.eactive.ext.kjb.statistics.service.ApiStatsExcelExportService; -import com.eactive.ext.kjb.statistics.ui.ApiStatsChartUI; import com.eactive.ext.kjb.statistics.ui.ApiStatsSearch; import com.eactive.ext.kjb.statistics.ui.ApiStatsUI; @@ -35,37 +26,29 @@ import lombok.extern.slf4j.Slf4j; @RequiredArgsConstructor public class ApiUseStatsController { - private final ApiStatsDayService service; - private final ApiStatsUIMapper mapper; - private final ApiStatsExcelExportService excelExportService; + private final ApiUseStatsService service; + private final ApiUseStatsExcelExportService excelExportService; - @GetMapping(value = "/onl/djb/statistics/apiUseStatsMan.view") + @GetMapping(value = "/onl/kjb/statistics/apiUseStatsMan.view") public String view() { - return "/onl/djb/statistics/apiUseStatsMan"; + return "/onl/kjb/statistics/apiUseStatsMan"; } - @PostMapping(value = "/onl/djb/statistics/apiUseStatsMan.json", params = "cmd=LIST") + @PostMapping(value = "/onl/kjb/statistics/apiUseStatsMan.json", params = "cmd=LIST") public ResponseEntity> selectList(ApiStatsSearch search, Pageable pageable) { - Pageable sortedPageable = PageRequest.of( - pageable.getPageNumber(), - pageable.getPageSize(), - Sort.by(Sort.Order.desc("statTime"))); - - Page page = service.selectList(search, sortedPageable); - Page uiPage = page.map(mapper::toVo); - - return ResponseEntity.ok(new GridResponse<>(uiPage)); + Page page = service.selectList(search, pageable); + return ResponseEntity.ok(new GridResponse<>(page)); } - @PostMapping(value = "/onl/djb/statistics/apiStatsDayMan.json", params = "cmd=EXCEL_EXPORT") + @PostMapping(value = "/onl/kjb/statistics/apiUseStatsMan.json", params = "cmd=EXCEL_EXPORT") public void exportToExcel(ApiStatsSearch search, HttpServletResponse response) throws IOException { log.info("Excel export started - search: {}", search); try { - List dataList = service.selectListForExcel(search); - log.info("Data retrieved - count: {}", dataList.size()); + List uiList = service.selectList(search); + log.info("Data retrieved - count: {}", uiList.size()); - if (dataList.isEmpty()) { + if (uiList.isEmpty()) { log.warn("No data found for export"); response.setStatus(HttpServletResponse.SC_NO_CONTENT); response.setContentType("application/json; charset=UTF-8"); @@ -74,11 +57,6 @@ public class ApiUseStatsController { return; } - List uiList = dataList.stream() - .map(mapper::toVo) - .collect(Collectors.toList()); - log.info("Data converted to UI list - count: {}", uiList.size()); - String fileName = generateFileName(search); log.info("Generating Excel file: {}", fileName); @@ -101,10 +79,7 @@ public class ApiUseStatsController { } private String generateFileName(ApiStatsSearch search) { - String timeRange = StringUtils.isNotBlank(search.getSearchStartDateTime()) - ? search.getSearchStartDateTime() - : LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")); - return "API사용현황_" + timeRange + ".xlsx"; + return "API사용현황_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + ".xlsx"; } } diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/statistics/ApiUseStatsExcelExportService.java b/src/main/java/com/eactive/eai/rms/ext/djb/statistics/ApiUseStatsExcelExportService.java new file mode 100644 index 0000000..1bf691d --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/ext/djb/statistics/ApiUseStatsExcelExportService.java @@ -0,0 +1,256 @@ +package com.eactive.eai.rms.ext.djb.statistics; + +import java.io.IOException; +import java.math.BigDecimal; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import javax.servlet.http.HttpServletResponse; + +import org.apache.poi.ss.usermodel.BorderStyle; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.FillPatternType; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.IndexedColors; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.stereotype.Service; + +import com.eactive.ext.kjb.statistics.ui.ApiStatsUI; + +import lombok.extern.slf4j.Slf4j; + +/** + * API 통계 Excel Export 서비스 + * Hour/Minute 통계 데이터를 Excel 파일로 생성 + */ +@Slf4j +@Service +public class ApiUseStatsExcelExportService { + + private static final String[] COLUMN_HEADERS = { + "구분", "총건수", "성공", "성공율(%)", "실패율(%)", "Timeout", "시스템오류", + "평균응답(ms)", "최소응답(ms)", "최대응답(ms)" + }; + + /** + * API 통계 데이터를 Excel 파일로 생성하여 HTTP 응답으로 전송 + * + * @param dataList API 통계 데이터 리스트 + * @param fileName 다운로드 파일명 + * @param response HTTP 응답 객체 + * @throws IOException 파일 생성 실패 시 + */ + public void exportApiStats(List dataList, String fileName, HttpServletResponse response) + throws IOException { + + log.info("Starting Excel export - rows: {}, filename: {}", dataList.size(), fileName); + + try (Workbook workbook = new XSSFWorkbook()) { + Sheet sheet = workbook.createSheet("API통계"); + log.debug("Excel sheet created"); + + // 스타일 생성 + CellStyle headerStyle = createHeaderStyle(workbook); + CellStyle stringStyle = createStringStyle(workbook); + CellStyle numberStyle = createNumberStyle(workbook); + CellStyle decimalStyle = createDecimalStyle(workbook); + log.debug("Excel styles created"); + + // 헤더 행 생성 + createHeaderRow(sheet, headerStyle); + log.debug("Header row created"); + + // 데이터 행 생성 + int rowNum = 1; + for (ApiStatsUI data : dataList) { + createDataRow(sheet, rowNum++, data, stringStyle, numberStyle, decimalStyle); + } + log.info("Data rows created - count: {}", dataList.size()); + + // 컬럼 너비 자동 조정 + autoSizeColumns(sheet); + log.debug("Column widths adjusted"); + + // HTTP 응답 설정 + setHttpResponse(response, fileName, workbook); + log.info("Excel file sent to response"); + + } catch (Exception e) { + log.error("Error creating Excel file", e); + throw new IOException("Excel file creation failed: " + e.getMessage(), e); + } + } + + /** + * 헤더 스타일 생성 (회색 배경, 볼드, 테두리) + */ + private CellStyle createHeaderStyle(Workbook workbook) { + CellStyle style = workbook.createCellStyle(); + + // 배경색 + style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + + // 테두리 + style.setBorderTop(BorderStyle.THIN); + style.setBorderBottom(BorderStyle.THIN); + style.setBorderLeft(BorderStyle.THIN); + style.setBorderRight(BorderStyle.THIN); + + // 정렬 + style.setAlignment(HorizontalAlignment.CENTER); + + // 폰트 (볼드) + Font font = workbook.createFont(); + font.setBold(true); + style.setFont(font); + + return style; + } + + /** + * 문자열 데이터 스타일 생성 (기본 테두리) + */ + private CellStyle createStringStyle(Workbook workbook) { + CellStyle style = workbook.createCellStyle(); + style.setBorderTop(BorderStyle.THIN); + style.setBorderBottom(BorderStyle.THIN); + style.setBorderLeft(BorderStyle.THIN); + style.setBorderRight(BorderStyle.THIN); + return style; + } + + /** + * 숫자 데이터 스타일 생성 (우측정렬 + 쉼표 구분) + */ + private CellStyle createNumberStyle(Workbook workbook) { + CellStyle style = workbook.createCellStyle(); + style.setBorderTop(BorderStyle.THIN); + style.setBorderBottom(BorderStyle.THIN); + style.setBorderLeft(BorderStyle.THIN); + style.setBorderRight(BorderStyle.THIN); + style.setAlignment(HorizontalAlignment.RIGHT); + style.setDataFormat(workbook.createDataFormat().getFormat("#,##0")); + return style; + } + + /** + * 소수점 데이터 스타일 생성 (우측정렬 + 소수점 2자리) + */ + private CellStyle createDecimalStyle(Workbook workbook) { + CellStyle style = workbook.createCellStyle(); + style.setBorderTop(BorderStyle.THIN); + style.setBorderBottom(BorderStyle.THIN); + style.setBorderLeft(BorderStyle.THIN); + style.setBorderRight(BorderStyle.THIN); + style.setAlignment(HorizontalAlignment.RIGHT); + style.setDataFormat(workbook.createDataFormat().getFormat("0.00")); + return style; + } + + /** + * 헤더 행 생성 + */ + private void createHeaderRow(Sheet sheet, CellStyle headerStyle) { + Row headerRow = sheet.createRow(0); + + for (int i = 0; i < COLUMN_HEADERS.length; i++) { + Cell cell = headerRow.createCell(i); + cell.setCellValue(COLUMN_HEADERS[i]); + cell.setCellStyle(headerStyle); + } + } + + /** + * 데이터 행 생성 + */ + private void createDataRow(Sheet sheet, int rowNum, ApiStatsUI data, + CellStyle stringStyle, CellStyle numberStyle, CellStyle decimalStyle) { + + Row row = sheet.createRow(rowNum); + int colNum = 0; + + createStringCell(row, colNum++, data.getOrgName(), stringStyle); + createLongCell(row, colNum++, data.getTotalCnt(), numberStyle); + createLongCell(row, colNum++, data.getSuccessCnt(), numberStyle); + createDecimalCell(row, colNum++, data.getSuccessRate(), decimalStyle); + createDecimalCell(row, colNum++, data.getFailRate(), decimalStyle); + createLongCell(row, colNum++, data.getTimeoutCnt(), numberStyle); + createLongCell(row, colNum++, data.getSystemErrCnt(), numberStyle); + createDecimalCell(row, colNum++, data.getAvgRespTime(), numberStyle); + createDecimalCell(row, colNum++, data.getMinRespTime(), numberStyle); + createDecimalCell(row, colNum++, data.getMaxRespTime(), numberStyle); + } + + /** + * 문자열 셀 생성 + */ + private void createStringCell(Row row, int colNum, String value, CellStyle style) { + Cell cell = row.createCell(colNum); + cell.setCellValue(value != null ? value : ""); + cell.setCellStyle(style); + } + + /** + * Long 타입 숫자 셀 생성 + */ + private void createLongCell(Row row, int colNum, Long value, CellStyle style) { + Cell cell = row.createCell(colNum); + if (value != null) { + cell.setCellValue(value.doubleValue()); + } else { + cell.setCellValue(0); + } + cell.setCellStyle(style); + } + + /** + * BigDecimal 타입 소수점 셀 생성 + */ + private void createDecimalCell(Row row, int colNum, BigDecimal value, CellStyle style) { + Cell cell = row.createCell(colNum); + if (value != null) { + cell.setCellValue(value.doubleValue()); + } else { + cell.setCellValue(0.0); + } + cell.setCellStyle(style); + } + + /** + * 컬럼 너비 자동 조정 + */ + private void autoSizeColumns(Sheet sheet) { + for (int i = 0; i < COLUMN_HEADERS.length; i++) { + sheet.autoSizeColumn(i); + // 한글 문자 고려하여 약간 여유 공간 추가 + sheet.setColumnWidth(i, sheet.getColumnWidth(i) + 512); + } + } + + /** + * HTTP 응답 설정 및 파일 전송 + */ + private void setHttpResponse(HttpServletResponse response, String fileName, Workbook workbook) + throws IOException { + + // Content-Type 설정 + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + + // Content-Disposition 설정 (파일명 UTF-8 인코딩) + String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString()) + .replaceAll("\\+", "%20"); + response.setHeader("Content-Disposition", + "attachment; filename=\"" + encodedFileName + "\"; filename*=UTF-8''" + encodedFileName); + + // Workbook을 응답 스트림으로 전송 + workbook.write(response.getOutputStream()); + response.getOutputStream().flush(); + } +} diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/statistics/ApiUseStatsRepository.java b/src/main/java/com/eactive/eai/rms/ext/djb/statistics/ApiUseStatsRepository.java new file mode 100644 index 0000000..3b5ee06 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/ext/djb/statistics/ApiUseStatsRepository.java @@ -0,0 +1,12 @@ +package com.eactive.eai.rms.ext.djb.statistics; + +import com.eactive.eai.data.jpa.BaseRepository; +import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsDay; +import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsDayId; + +/** + * API 사용현황 Repository + */ +interface ApiUseStatsRepository extends BaseRepository { + +} diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/statistics/ApiUseStatsService.java b/src/main/java/com/eactive/eai/rms/ext/djb/statistics/ApiUseStatsService.java new file mode 100644 index 0000000..067b71f --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/ext/djb/statistics/ApiUseStatsService.java @@ -0,0 +1,209 @@ +package com.eactive.eai.rms.ext.djb.statistics; + +import java.math.RoundingMode; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.stream.Collectors; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.Query; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.eactive.eai.data.jpa.AbstractDataService; +import com.eactive.eai.rms.common.util.StringUtils; +import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsDay; +import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsDayId; +import com.eactive.ext.kjb.statistics.ui.ApiStatsSearch; +import com.eactive.ext.kjb.statistics.ui.ApiStatsUI; + +@Service +@Transactional +public class ApiUseStatsService + extends AbstractDataService { + + private static final int MAX_DAYS = 31; + + @PersistenceContext + private EntityManager entityManager; + + @SuppressWarnings("unchecked") + public Page selectList(ApiStatsSearch search, Pageable pageable) { + Query dataQuery = getDataQuery(search); + dataQuery.setFirstResult((int) pageable.getOffset()); + dataQuery.setMaxResults(pageable.getPageSize()); + + List rows = dataQuery.getResultList(); + List results = rows.stream() + .map(this::toVO) + .collect(Collectors.toList()); + + long records = rows.size() > 0 ? StringUtils.toLong(rows.get(0)[0]) : 0; + + return new PageImpl<>(results, pageable, records); + } + + + @SuppressWarnings("unchecked") + public List selectList(ApiStatsSearch search) { + Query dataQuery = getDataQuery(search); + List rows = dataQuery.getResultList(); + return rows.stream() + .map(this::toVO) + .collect(Collectors.toList()); + } + + + private Query getDataQuery(ApiStatsSearch search) { + + StringBuilder where = buildNativeWhere(search); + + String dataSql = ""; + if ("ORG".equals(search.getSearchType())) { + dataSql = "SELECT COUNT(*) OVER () AS RECORDS " + + ", B.ORGNAME" + + ", 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" + + ", CASE WHEN SUM(A.TOTAL_CNT) > 0 THEN (SUM(A.TOTAL_CNT) - SUM(A.SUCCESS_CNT)) * 100 / SUM(A.TOTAL_CNT) ELSE NULL END" + + ", SUM(A.TIMEOUT_CNT)" + + ", SUM(A.SYSTEM_ERR_CNT)" + + ", CASE WHEN SUM(A.TOTAL_CNT) > 0 THEN TRUNC(SUM(A.TOTAL_CNT * A.AVG_RESP_TIME) / SUM(A.TOTAL_CNT)) ELSE NULL END" + + ", MIN(A.MIN_RESP_TIME)" + + ", MAX(A.MAX_RESP_TIME)" + + " FROM API_STATS_DAY A" + + " 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 B.ORGNAME" + + " ORDER BY ORGNAME"; + } else if ("API".equals(search.getSearchType())) { + dataSql = "SELECT COUNT(*) OVER () AS RECORDS " + + ", 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" + + ", CASE WHEN SUM(A.TOTAL_CNT) > 0 THEN (SUM(A.TOTAL_CNT) - SUM(A.SUCCESS_CNT)) * 100 / SUM(A.TOTAL_CNT) ELSE NULL END" + + ", SUM(A.TIMEOUT_CNT)" + + ", SUM(A.SYSTEM_ERR_CNT)" + + ", CASE WHEN SUM(A.TOTAL_CNT) > 0 THEN TRUNC(SUM(A.TOTAL_CNT * A.AVG_RESP_TIME) / SUM(A.TOTAL_CNT)) ELSE NULL END" + + ", MIN(A.MIN_RESP_TIME)" + + ", MAX(A.MAX_RESP_TIME)" + + " FROM API_STATS_DAY A " + + " LEFT OUTER JOIN TSEAIAU01 B ON A.CLIENT_ID = B.CLIENTID" + + " LEFT OUTER JOIN TSEAIHE01 C ON A.API_NAME = C.EAISVCNAME " + + where + + " GROUP BY C.EAISVCDESC" + + " ORDER BY EAISVCDESC"; + } else if ("DATE".equals(search.getSearchType())) { + dataSql = "SELECT COUNT(*) OVER () AS RECORDS " + + ", TO_CHAR(STAT_TIME,'YYYY-MM-DD') AS STAT_TIME " + + ", 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" + + ", CASE WHEN SUM(A.TOTAL_CNT) > 0 THEN (SUM(A.TOTAL_CNT) - SUM(A.SUCCESS_CNT)) * 100 / SUM(A.TOTAL_CNT) ELSE NULL END" + + ", SUM(A.TIMEOUT_CNT)" + + ", SUM(A.SYSTEM_ERR_CNT)" + + ", CASE WHEN SUM(A.TOTAL_CNT) > 0 THEN TRUNC(SUM(A.TOTAL_CNT * A.AVG_RESP_TIME) / SUM(A.TOTAL_CNT)) ELSE NULL END" + + ", MIN(A.MIN_RESP_TIME)" + + ", MAX(A.MAX_RESP_TIME)" + + " FROM API_STATS_DAY A " + + " 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 TO_CHAR(STAT_TIME,'YYYY-MM-DD')" + + " ORDER BY STAT_TIME"; + } + + Query dataQuery = entityManager.createNativeQuery(dataSql); + if (search.getParsedStartDate() != null) { + dataQuery.setParameter("searchStartDate", search.getParsedStartDate()); + dataQuery.setParameter("searchEndDate", search.getParsedEndDate()); + } + if (StringUtils.isNotBlank(search.getSearchOrgName())) { + dataQuery.setParameter("searchOrgName", "%" + search.getSearchOrgName().toUpperCase() + "%"); + } + if (StringUtils.isNotBlank(search.getSearchApiName())) { + dataQuery.setParameter("searchApiName", "%" + search.getSearchApiName().toUpperCase() + "%"); + } + return dataQuery; + } + + + /** + * Native Query 결과를 ApiStatsUI로 변환 + */ + private ApiStatsUI toVO(Object[] row) { + ApiStatsUI vo = new ApiStatsUI(); + vo.setOrgName(StringUtils.toString(row[1])); + vo.setTotalCnt(StringUtils.toLong(row[2])); + vo.setSuccessCnt(StringUtils.toLong(row[3])); + vo.setSuccessRate(StringUtils.toDecimal(row[4]).setScale(2, RoundingMode.HALF_UP)); + vo.setFailRate(StringUtils.toDecimal(row[5]).setScale(2, RoundingMode.HALF_UP)); + vo.setTimeoutCnt(StringUtils.toLong(row[6])); + vo.setSystemErrCnt(StringUtils.toLong(row[7])); + vo.setAvgRespTime(StringUtils.toDecimal(row[8])); + vo.setMinRespTime(StringUtils.toDecimal(row[9])); + vo.setMaxRespTime(StringUtils.toDecimal(row[10])); + return vo; + } + + + /** + * native SQL용 동적 WHERE 절 생성 + */ + private StringBuilder buildNativeWhere(ApiStatsSearch search) { + StringBuilder where = new StringBuilder(" WHERE 1=1"); + + calculateDateRange(search); + + if (search.getParsedStartDate() != null) { + where.append(" AND A.STAT_TIME >= :searchStartDate "); + where.append(" AND A.STAT_TIME <= :searchEndDate "); + } + + if (StringUtils.isNotBlank(search.getSearchOrgName())) { + where.append(" AND UPPER(B.ORGNAME) LIKE :searchOrgName "); + } + + if (StringUtils.isNotBlank(search.getSearchApiName())) { + where.append(" AND UPPER(C.EAISVCDESC) LIKE :searchApiName "); + } + + return where; + } + + + /** + * 조회 기간 계산 (최대 31일 제한) + */ + private void calculateDateRange(ApiStatsSearch search) { + if (StringUtils.isBlank(search.getSearchStartDateTime())) { + return; + } + + LocalDate startDate = LocalDate.parse(search.getSearchStartDateTime(), + DateTimeFormatter.ofPattern("yyyyMMdd")); + LocalDate endDate; + + if (StringUtils.isNotBlank(search.getSearchEndDateTime())) { + endDate = LocalDate.parse(search.getSearchEndDateTime(), + DateTimeFormatter.ofPattern("yyyyMMdd")); + if (java.time.Period.between(startDate, endDate).getDays() > MAX_DAYS) { + endDate = startDate.plusDays(MAX_DAYS); + } + } else { + endDate = startDate.plusDays(MAX_DAYS); + } + + search.setParsedStartDate(startDate); + search.setParsedEndDate(endDate); + } + +} diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/swing/service/SwingSendManager.java b/src/main/java/com/eactive/eai/rms/ext/djb/swing/service/SwingSendManager.java deleted file mode 100644 index 34f61a2..0000000 --- a/src/main/java/com/eactive/eai/rms/ext/djb/swing/service/SwingSendManager.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.eactive.eai.rms.ext.djb.swing.service; - -import org.springframework.stereotype.Service; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -@Service -@RequiredArgsConstructor -public class SwingSendManager { - - public void send(String eaisvcname, long cnt, long rangeMinute) { - log.info("[Swing] 발송 준비: {} 최근 {}분 {}건", eaisvcname, rangeMinute, cnt); - // TODO: 알림 발송 로직 - } -} \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/ums/UmsManager.java b/src/main/java/com/eactive/eai/rms/ext/djb/ums/UmsManager.java new file mode 100644 index 0000000..67c238e --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/ext/djb/ums/UmsManager.java @@ -0,0 +1,89 @@ +package com.eactive.eai.rms.ext.djb.ums; + +import java.util.HashMap; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +import com.eactive.apim.portal.template.entity.MessageCode; +import com.eactive.apim.portal.template.service.MessageHandlerService; +import com.eactive.apim.portal.template.service.MessageRecipient; +import com.eactive.apim.portal.user.entity.UserInfo; +import com.eactive.eai.rms.data.entity.man.user.UserInfoService; +import com.eactive.eai.rms.ext.djb.webhook.dto.WebhookSendRequest; +import com.eactive.eai.rms.ext.djb.webhook.service.WebhookService; + +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class UmsManager { + + private static final Logger log = LoggerFactory.getLogger(UmsManager.class); + + private final MessageHandlerService messageHandlerService; + private final UserInfoService userInfoService; + private final WebhookService webhookService; + + /** + * role 역할을 가진 내부직원에게 메신저(Swing chat) 발송 + * @param role + * @param messageCode + * @param message + * @return + */ + public void sendMessenger(String roleId, MessageCode messageCode, String message) { + + List users = userInfoService.findAllByRoleId(roleId); + if (users.isEmpty()) { + log.info("sendMessenger: no users found for role '{}'", roleId); + return; + } + + for (UserInfo user : users) { + MessageRecipient recipient = MessageRecipient.builder() + .userId(user.getUserid()) + .username(user.getUsername()) + .build(); + + HashMap params = new HashMap<>(); + params.put("message", message); + + messageHandlerService.publishEvent(messageCode, recipient, params); + } + } + + /** + * 내부직원에게 메신저 발송 + * @param user + * @param messageCode + * @param message + */ + public void sendMessenger(UserInfo user, MessageCode messageCode, String message) { + MessageRecipient recipient = MessageRecipient.builder() + .userId(user.getUserid()) + .username(user.getUsername()) + .build(); + + HashMap params = new HashMap<>(); + params.put("message", message); + + messageHandlerService.publishEvent(messageCode, recipient, params); + } + + /** + * event를 구독중인 제휴사에게 웹훅 발송 (비동기) + * @param event + * @param apiIds + */ + @Async("webhookExecutor") + public void sendWebhook(String event, List apiIds) { + List list = webhookService.findSendList(event, apiIds); + for (WebhookSendRequest req : list) { + webhookService.send(req); + } + } +} diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/ums/event/ApiStatusChangedEventHandler.java b/src/main/java/com/eactive/eai/rms/ext/djb/ums/event/ApiStatusChangedEventHandler.java new file mode 100644 index 0000000..fd059a5 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/ext/djb/ums/event/ApiStatusChangedEventHandler.java @@ -0,0 +1,43 @@ +package com.eactive.eai.rms.ext.djb.ums.event; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.stereotype.Component; + +import com.eactive.apim.portal.template.entity.MessageCode; +import com.eactive.apim.portal.template.service.MessageEventHandler; +import com.eactive.apim.portal.template.service.MessageRecipient; +import com.eactive.apim.portal.template.service.MessageSendEvent; + +@Component +public class ApiStatusChangedEventHandler implements MessageEventHandler { + + @Override + public MessageCode getKey() { + return MessageCode.API_STATUS_CHANGED; + } + + @Override + public boolean allowAdditionalRecipients() { + return true; + } + + @Override + public String getDisplayName() { + return "API 상태 변화"; + } + + @Override + public String getDescription() { + return "
사용 가능 변수:

" + + "
- message 알림 메시지

"; + } + + @Override + public MessageSendEvent createEvent(Object source, MessageRecipient recipient, Map params) { + Map eventParams = new HashMap<>(); + eventParams.put("message", (String) params.get("message")); + return new MessageSendEvent(source, MessageCode.API_STATUS_CHANGED, recipient, eventParams); + } +} diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/ums/event/InflowTokenFailedEventHandler.java b/src/main/java/com/eactive/eai/rms/ext/djb/ums/event/InflowTokenFailedEventHandler.java new file mode 100644 index 0000000..fb059e0 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/ext/djb/ums/event/InflowTokenFailedEventHandler.java @@ -0,0 +1,43 @@ +package com.eactive.eai.rms.ext.djb.ums.event; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.stereotype.Component; + +import com.eactive.apim.portal.template.entity.MessageCode; +import com.eactive.apim.portal.template.service.MessageEventHandler; +import com.eactive.apim.portal.template.service.MessageRecipient; +import com.eactive.apim.portal.template.service.MessageSendEvent; + +@Component +public class InflowTokenFailedEventHandler implements MessageEventHandler { + + @Override + public MessageCode getKey() { + return MessageCode.INFLOW_TOKEN_FAILED; + } + + @Override + public boolean allowAdditionalRecipients() { + return true; + } + + @Override + public String getDisplayName() { + return "유량제어 토큰 획득 실패"; + } + + @Override + public String getDescription() { + return "
사용 가능 변수:

" + + "
- message 알림 메시지

"; + } + + @Override + public MessageSendEvent createEvent(Object source, MessageRecipient recipient, Map params) { + Map eventParams = new HashMap<>(); + eventParams.put("message", (String) params.get("message")); + return new MessageSendEvent(source, MessageCode.INFLOW_TOKEN_FAILED, recipient, eventParams); + } +} diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/ums/service/UmsService.java b/src/main/java/com/eactive/eai/rms/ext/djb/ums/service/UmsService.java new file mode 100644 index 0000000..08a0129 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/ext/djb/ums/service/UmsService.java @@ -0,0 +1,344 @@ +package com.eactive.eai.rms.ext.djb.ums.service; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.Charset; +import java.security.SecureRandom; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.springframework.stereotype.Service; + +import com.eactive.apim.portal.portalproperty.service.PortalPropertyService; +import com.eactive.apim.portal.template.entity.MessageRequest; +import com.eactive.eai.common.util.SystemUtil; +import com.eactive.eai.rms.data.entity.man.monitoringProperty.service.MonitoringPropertyService; +import com.eactive.eai.rms.ext.djb.ums.vo.EmailDataVO; +import com.eactive.eai.rms.ext.djb.ums.vo.SmsDataVO; +import com.eactive.eai.rms.ext.djb.ums.vo.StdHeadVO; +import com.eactive.eai.rms.ext.djb.ums.vo.SwingCommonVO; +import com.eactive.eai.rms.ext.djb.ums.vo.SwingDataVO; +import com.eactive.eai.rms.ext.djb.ums.vo.SwingReceiverDataVO; +import com.google.gson.Gson; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +public class UmsService { + + private static final Gson gson = new Gson(); + + private static final String SYS_DVCD = "OPA"; + private final MonitoringPropertyService propertyService; + + public UmsService(MonitoringPropertyService propertyService) { + this.propertyService = propertyService; + } + + public boolean send(MessageRequest messageRequest) throws Exception { + if (messageRequest == null) { + throw new IllegalArgumentException("messageRequest is null"); + } + + this.httpConnection(messageRequest); + + return true; + } + + + private void httpConnection(MessageRequest messageRequest) throws Exception { + HttpURLConnection conn = null; + BufferedReader br = null; + log.debug("<<>>:" + messageRequest.toString()); + try { + String host = propertyService.getPropertyValue("Monitoring", "djb.ums."+messageRequest.getMessageType().toLowerCase()+".url", ""); + + URL url = new URL(host); + int timeoutValue = 5 * 1000; // 타임아웃 설정을 위한 값 (단위: ms) + // Charset charset = Charset.forName("UTF-8"); + Charset charset = Charset.forName("MS949"); + + StringBuffer sb = null; + String bodyData = this.makeBodyData(messageRequest); + + String responseData = ""; + String method = bodyData.isEmpty() ? "GET" : "POST"; + conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod(method); // 전송방식 + // conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); + conn.setRequestProperty("Content-Type", "application/json; charset=MS949"); + conn.setConnectTimeout(timeoutValue); // 연결 타임아웃 설정(5초) + conn.setReadTimeout(timeoutValue); // 읽기 타임아웃 설정(5초) + conn.setDoOutput(true); + + // POst 방식인 경우에만 + if (method.equals("POST")) { + OutputStream os = conn.getOutputStream(); + byte requestData[] = bodyData.getBytes(charset); + os.write(requestData); + os.close(); + } + + if (log.isDebugEnabled()) { + log.debug("getContentType();" + conn.getContentType()); // 응답 콘텐츠 유형 구하기 + log.debug("getResponsecode();" + conn.getResponseCode()); // 응답 코드 구하기 + log.debug("getResponseMessage():" + conn.getResponseMessage()); // 응답 메세지 구하기 + } + + // http 요청 후 응답 받은 데이타를 버퍼에 쌓는다 + if (conn.getResponseCode() >= 200 && conn.getResponseCode() <= 300) { + br = new BufferedReader(new InputStreamReader(conn.getInputStream(), charset)); + } else { + br = new BufferedReader(new InputStreamReader(conn.getErrorStream(), charset)); + } + + String inputLine; + sb = new StringBuffer(); + while ((inputLine = br.readLine()) != null) { + sb.append(inputLine); + } + + responseData = sb.toString(); + } finally { + // http 요청 및 응답 완료 후 BufferedReader를 닫는다 + if (br != null) { + br.close(); + } + if (conn != null) { + conn.disconnect(); + } + } + } + + + + private String makeBodyData(MessageRequest messageRequest) { + // 리턴값 + String rtnVal = ""; + + // guID 및 날짜 시간 정보 + String jsonData[] = this.makeStdGuidInfo(); + + // 표준 헤더 + StdHeadVO stdHeadVO = new StdHeadVO(); + stdHeadVO.setNxgn_stnd_idfr("JERA"); + stdHeadVO.setGuid(jsonData[2]); + stdHeadVO.setGuid_prgs_no("1"); + stdHeadVO.setStnd_mesg_ver("R10"); + stdHeadVO.setOrtr_guid(jsonData[2]); + stdHeadVO.setSect_ecrpt_yn("N"); + stdHeadVO.setFrst_trnm_ipad(propertyService.getPropertyValue("Monitoring", "djb.ums.was_ip_address", "")); + stdHeadVO.setFrst_trnm_ipv4_addr(propertyService.getPropertyValue("Monitoring", "djb.ums.was_ip_address", "")); + stdHeadVO.setFrst_trnm_mac(propertyService.getPropertyValue("Monitoring", "djb.ums.was_mac_address", "")); + stdHeadVO.setFrst_mesg_dman_dt(jsonData[0]); + stdHeadVO.setFrst_mesg_dman_time(jsonData[1]); + stdHeadVO.setFrst_trnm_sys_dvcd(SYS_DVCD); + stdHeadVO.setTrnm_sys_dvcd(SYS_DVCD); + stdHeadVO.setDman_rspn_dvcd("S"); // 요청응답구분코드 S:요청 R:응답 + stdHeadVO.setSys_env_dvcd(SystemUtil.getSysOperEvirnDstcd()); // D:개발 T:테스트 P:운영 + stdHeadVO.setTx_tycd("O"); // O:온라인 B:배치 + stdHeadVO.setSynz_dvcd("S"); // S:동기 A:비동기 + stdHeadVO.setChnl_tycd(""); // 채널유형코드 + stdHeadVO.setHmab_dvcd("1"); // 내외구분코드 1:내부 2:외부(타발) + stdHeadVO.setIf_id(messageRequest.getEaiInterfaceId()); // 인터페이스ID + stdHeadVO.setTx_id(messageRequest.getServiceId()); // 거래ID + + if ("MESSENGER".equals(messageRequest.getMessageType()) ) { + + // 스윙챗 알림 공용 + SwingCommonVO swiComVO = new SwingCommonVO(); + swiComVO.setClientld(propertyService.getPropertyValue("Monitoring", "djb.ums.messenger.client_id", "")); + swiComVO.setClientSecret(propertyService.getPropertyValue("Monitoring", "djb.ums.messenger.client_secret", "")); + swiComVO.setCompanyCode("CB"); + swiComVO.setRequestUniqueKey(this.genrateRandomStringNumber26()); + + // 스윙챗 알림 데이터 + SwingDataVO swiDataVO = new SwingDataVO(); + swiDataVO.setNotiCode("CNO_FEP_HMP_0002"); + swiDataVO.setNotiTitle("알림"); + swiDataVO.setNotiContent(messageRequest.getMessage()); + + // 스윙챗 알림 데이터 + List swiReDataVOList = new ArrayList(); + SwingReceiverDataVO receiver = new SwingReceiverDataVO(); + receiver.setCode(messageRequest.getMessengerId()); + swiReDataVOList.add(receiver); + + swiDataVO.setReceiverInfo(swiReDataVOList); + + String header = gson.toJson(stdHeadVO); + String common = gson.toJson(swiComVO); + String data = gson.toJson(swiDataVO); + + // 헤더 + 데이터 + rtnVal = "{\"HEAD\":" + header + " ,\r\n" + "\"DATA\":\r\n" + " [" + "{\r\n" + + " \"data_dvcd\":\"IO\", \r\n" + " \"BIZ_DATA\":\r\n" + " {\r\n" + + " \"common\":\r\n" + common + ",\r\n" + " \"data\":" + data + + " }\r\n" + " }\r\n" + " ]\r\n" + "}"; + + } + else if ("SMS".equals(messageRequest.getMessageType()) + || "KAKAO".equals(messageRequest.getMessageType())) { + + // Sms 데이터 + SmsDataVO dataVO = new SmsDataVO(); + dataVO.setSnd_dman_dt(jsonData[0]); + dataVO.setSnd_dman_id(""); + dataVO.setSnd_dvcd("EAI"); + dataVO.setMsg_titl(""); + dataVO.setMsg_ctnt(messageRequest.getMessage()); + dataVO.setRecvr_no(messageRequest.getPhone()); + dataVO.setSndn_no("15880079"); //발신번호 + dataVO.setSnd_empno(""); //요청자 + dataVO.setSnd_emp_dprm_cd(""); //요청부서코드 + if ("SMS".equals(messageRequest.getMessageType())) { + dataVO.setLtrs_snd_tycd("1"); // 1:SMS 2:LMS/MMS + dataVO.setMsg_snd_tycd("0000"); // 알림톡 메세지 종류 0000:SMS, 1000:LMS, 1008:알림톡, .... + } else if ("KAKAO".equals(messageRequest.getMessageType())) { + dataVO.setLtrs_snd_tycd("2"); // 1:SMS 2:LMS/MMS + dataVO.setMsg_snd_tycd("1008"); // 알림톡 메세지 종류 0000:SMS, 1000:LMS, 1008:알림톡, .... + dataVO.setAlmtk_snd_prfl_key(""); // 플러스친구아이디 + dataVO.setAlmtk_tmplt_cd(""); // 템플릿코드 + dataVO.setAlmtk_err_ltrs_snd_yn("Y"); // 오류자문자발송여부 + dataVO.setAlmtk_btn_trnm_tycd("2"); // 카카오버튼전송방식 1-format string 2-JSON 3-XML + } + dataVO.setSms_trnm_bzwk_dvcd(""); //업무구분코드 + dataVO.setSms_bzwk_dcls_dvcd(""); //업무세분코드 + dataVO.setSms_sys_dvcd("83"); //시스템코드 + dataVO.setTx_dt(jsonData[0]); //거래일자 + dataVO.setTx_tktm(jsonData[1].substring(0,6)); //거래시간 + + List dataList = new ArrayList(); + dataList.add(dataVO); + + String header = gson.toJson(stdHeadVO); + String data = gson.toJson(dataList); + + // 헤더 + 데이터 + rtnVal = "{\"HEAD\":" + header + " ,\r\n" + "\"DATA\":\r\n" + " [" + "{\r\n" + + " \"data_dvcd\":\"IO\", \r\n" + " \"BIZ_DATA\":\r\n" + " {\r\n" + + " \"ums_if_data_ncnt\":1, \r\n" + + " \"ums_if_data\":" + data + + " }\r\n" + " }\r\n" + " ]\r\n" + "}"; + + } + else if ("EMAIL".equals(messageRequest.getMessageType())) { + + // 이메일 데이터 + EmailDataVO dataVO = new EmailDataVO(); + dataVO.setUms_evnt_id(""); // UMS이벤트ID(필수) + dataVO.setSnd_dman_dt(jsonData[0]); + dataVO.setSnd_dman_id(""); + dataVO.setSnd_dman_sno("1"); + dataVO.setEmail_titl(messageRequest.getSubject()); // 메일제목 + dataVO.setCstno(""); // 고객번호(필수) + dataVO.setSendr_emaddr(""); // 발신자 이메일 + dataVO.setSendr_nm(""); // 발신자 이름 + dataVO.setSndbk_emaddr(""); // 리턴 이메일 + dataVO.setRecvr_emaddr(messageRequest.getEmail()); // 수신자 이메일(필수) + dataVO.setRecvr_nm(messageRequest.getUsername()); // 수신자명(필수) + dataVO.setTmplt_mpng_info(messageRequest.getMessage()); // 탬플릿매핑정보 + dataVO.setTmplt_mpng_rptt_info(""); // 탬플릿매핑반복정보 + dataVO.setSnd_empno(""); // 발신자ID(필수) + dataVO.setSnd_emp_dprm_cd(""); // 발신자부서코드(필수) + dataVO.setBzwk_cd(""); // 업무구분코드 + dataVO.setSub_bzwk_cd(""); // 업무세부코드 + dataVO.setSys_dvcd(SYS_DVCD); + dataVO.setTx_dt(jsonData[0]); //거래일자 + dataVO.setTx_tktm(jsonData[1].substring(0,6)); //거래시간 + + List dataList = new ArrayList(); + dataList.add(dataVO); + + String header = gson.toJson(stdHeadVO); + String data = gson.toJson(dataList); + + // 헤더 + 데이터 + rtnVal = "{\"HEAD\":" + header + " ,\r\n" + "\"DATA\":\r\n" + " [" + "{\r\n" + + " \"data_dvcd\":\"IO\", \r\n" + " \"BIZ_DATA\":\r\n" + " {\r\n" + + " \"ums_if_data_ncnt\":1, \r\n" + + " \"ums_if_data\":" + data + + " }\r\n" + " }\r\n" + " ]\r\n" + "}"; + } + + log.debug(rtnVal); + + return rtnVal; + } + + + private String[] makeStdGuidInfo() { + // 리턴값 + String rtnVal[] = new String[3]; + + // 현재시간 + Date currentDate = new Date(); + + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS"); + String formattedDate = sdf.format(currentDate); + + rtnVal[0] = formattedDate.substring(0,8); + rtnVal[1] = formattedDate.substring(8); + + // 랜던값 + String random20 = this.genrateRandomStringNumber20(); + + rtnVal[2] = formattedDate + SYS_DVCD + random20; + + return rtnVal; + } + + + private String genrateRandomStringNumber20() { + String rtnVal= ""; + + StringBuilder buffer= new StringBuilder(10); + + // 랜덤값 + String characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + + // 랜덤객체 생성 + SecureRandom random = new SecureRandom(); + + // 반복문 + for (int i= 0;i< 10;i++) { + // 랜덤수 + int randomIndex = random.nextInt(characters.length()); + + // 생성된 랜덤값 + char randomChar = characters.charAt(randomIndex); + + // 랜덤값 저장 + buffer.append(randomChar); + } + + // 랜덤객체 재생성 + random = new SecureRandom(); + + // 난수 10개 생성 + int random10 = (int) (Math.pow(10, 9) + random.nextDouble() * Math.pow(10, 9)); + + // 문자 숫자 조합(10) + 숫자(10) + rtnVal = buffer.toString() + Integer.toString(random10); + + //리턴 + return rtnVal; + } + + private String genrateRandomStringNumber26() { + StringBuilder buffer = new StringBuilder(26); + String characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ0123456789"; + SecureRandom random = new SecureRandom(); + for (int i = 0; i < 26; i++) { + buffer.append(characters.charAt(random.nextInt(characters.length()))); + } + return buffer.toString(); + } +} diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/ums/vo/EmailDataVO.java b/src/main/java/com/eactive/eai/rms/ext/djb/ums/vo/EmailDataVO.java new file mode 100644 index 0000000..defecc5 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/ext/djb/ums/vo/EmailDataVO.java @@ -0,0 +1,67 @@ +package com.eactive.eai.rms.ext.djb.ums.vo; + +import lombok.Data; + +@Data +public class EmailDataVO { + + /** UMS이벤트ID */ + private String ums_evnt_id; + + /** 발송요청일자 */ + private String snd_dman_dt; + + /** UUID */ + private String snd_dman_id; + + /** 발송요청순번 */ + private String snd_dman_sno; + + /** 메일제목 */ + private String email_titl; + + /** 고객번호 */ + private String cstno; + + /** 발신자 이메일 */ + private String sendr_emaddr; + + /** 발신자 이름 */ + private String sendr_nm; + + /** 리턴 이메일 */ + private String sndbk_emaddr; + + /** 수신자 이메일 */ + private String recvr_emaddr; + + /** 수신자명 */ + private String recvr_nm; + + /** 탬플릿매핑정보 */ + private String tmplt_mpng_info; + + /** 탬플릿매핑반복정보 */ + private String tmplt_mpng_rptt_info; + + /** 발신자ID */ + private String snd_empno; + + /** 발신자부서코드 */ + private String snd_emp_dprm_cd; + + /** 업무구분코드 */ + private String bzwk_cd; + + /** 업무세부코드 */ + private String sub_bzwk_cd; + + /** 시스템코드 */ + private String sys_dvcd; + + /** 거래일자 */ + private String tx_dt; + + /** 거래시간 */ + private String tx_tktm; +} diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/ums/vo/SmsDataVO.java b/src/main/java/com/eactive/eai/rms/ext/djb/ums/vo/SmsDataVO.java new file mode 100644 index 0000000..a2f822c --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/ext/djb/ums/vo/SmsDataVO.java @@ -0,0 +1,67 @@ +package com.eactive.eai.rms.ext.djb.ums.vo; + +import lombok.Data; + +@Data +public class SmsDataVO { + + /** 발송요청일자 */ + private String snd_dman_dt; + + /** UUID */ + private String snd_dman_id; + + /** 발송구분 */ + private String snd_dvcd; + + /** 발송타입 */ + private String ltrs_snd_tycd; + + /** LMS 제목 */ + private String msg_titl; + + /** 메세지 */ + private String msg_ctnt; + + /** 수신자번호 */ + private String recvr_no; + + /** 발신번호 */ + private String sndn_no; + + /** 요청자(직원번호) */ + private String snd_empno; + + /** 요청부서코드 */ + private String snd_emp_dprm_cd; + + /** 알림톡 메시지 종류 */ + private String msg_snd_tycd; + + /** 플러스친구아이디 */ + private String almtk_snd_prfl_key; + + /** 템플릿코드 */ + private String almtk_tmplt_cd; + + /** 오류자문자발송여부 */ + private String almtk_err_ltrs_snd_yn; + + /** 카카오톡전송방식 */ + private String almtk_btn_trnm_tycd; + + /** 업무구분 코드 */ + private String sms_trnm_bzwk_dvcd; + + /** 업무세부코드 */ + private String sms_bzwk_dcls_dvcd; + + /** 시스템코드 */ + private String sms_sys_dvcd; + + /** 거래일자 */ + private String tx_dt; + + /** 거래시간 */ + private String tx_tktm; +} diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/ums/vo/StdHeadVO.java b/src/main/java/com/eactive/eai/rms/ext/djb/ums/vo/StdHeadVO.java new file mode 100644 index 0000000..0f6675a --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/ext/djb/ums/vo/StdHeadVO.java @@ -0,0 +1,61 @@ +package com.eactive.eai.rms.ext.djb.ums.vo; + +import lombok.Data; + +@Data +public class StdHeadVO { + + private String nxgn_stnd_idfr; + + private String guid; + + private String guid_prgs_no; + + private String stnd_mesg_ver; + + private String ortr_guid; + + private String sect_ecrpt_yn; + + private String frst_trnm_ipad; + + private String frst_trnm_ipv4_addr; + + private String frst_trnm_mac; + + private String frst_mesg_dman_dt; + + private String frst_mesg_dman_time; + + private String frst_trnm_sys_dvcd; + + private String trnm_sys_dvcd; + + private String dman_rspn_dvcd; + + private String sys_env_dvcd; + + private String tx_tycd; + + private String synz_dvcd; + + private String tx_id; + + private String osdch_dvcd; + + private String chnl_tycd; + + private String if_id; + + private String hmab_dvcd; + + private String tx_brcd; + + private String tlrno; + + private String insl_brcd; + + private String blng_brcd; + + private String mgmt_brcd; +} diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/ums/vo/SwingCommonVO.java b/src/main/java/com/eactive/eai/rms/ext/djb/ums/vo/SwingCommonVO.java new file mode 100644 index 0000000..cbb9b36 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/ext/djb/ums/vo/SwingCommonVO.java @@ -0,0 +1,19 @@ +package com.eactive.eai.rms.ext.djb.ums.vo; + +import lombok.Data; + +@Data +public class SwingCommonVO { + + /** 클라이업트 D */ + private String clientld; + + /** 클라이언트 시크릿 */ + private String clientSecret; + + /** 회사코드 */ + private String companyCode; + + /** 요청고유키 */ + private String requestUniqueKey; +} diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/ums/vo/SwingDataVO.java b/src/main/java/com/eactive/eai/rms/ext/djb/ums/vo/SwingDataVO.java new file mode 100644 index 0000000..60bd2b6 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/ext/djb/ums/vo/SwingDataVO.java @@ -0,0 +1,21 @@ +package com.eactive.eai.rms.ext.djb.ums.vo; + +import java.util.List; + +import lombok.Data; + +@Data +public class SwingDataVO { + + /** 알림코드 */ + private String notiCode; + + /** 알림제목 */ + private String notiTitle; + + /** 알림내용 */ + private String notiContent; + + /** 수신자정보 */ + private List receiverInfo; +} diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/ums/vo/SwingReceiverDataVO.java b/src/main/java/com/eactive/eai/rms/ext/djb/ums/vo/SwingReceiverDataVO.java new file mode 100644 index 0000000..2d2a25d --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/ext/djb/ums/vo/SwingReceiverDataVO.java @@ -0,0 +1,22 @@ +package com.eactive.eai.rms.ext.djb.ums.vo; + +import lombok.Data; + +@Data +public class SwingReceiverDataVO { + + /** 알림코드 */ + private String name; + + /** 알림제목 */ + private String type; + + /** 알림내용 */ + private String companyCode; + + /** 수신자정보 */ + private String code; + + /** 상품명 */ + private String prdNm; +} diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/config/WebhookAsyncConfig.java b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/config/WebhookAsyncConfig.java new file mode 100644 index 0000000..ec7fc83 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/config/WebhookAsyncConfig.java @@ -0,0 +1,26 @@ +package com.eactive.eai.rms.ext.djb.webhook.config; + +import java.util.concurrent.Executor; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +@Configuration +@EnableAsync +public class WebhookAsyncConfig { + + @Bean(name = "webhookExecutor") + public Executor webhookExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(2); + executor.setMaxPoolSize(10); + executor.setQueueCapacity(100); + executor.setThreadNamePrefix("webhook-"); + executor.setWaitForTasksToCompleteOnShutdown(true); + executor.setAwaitTerminationSeconds(30); + executor.initialize(); + return executor; + } +} diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/controller/WebhookSendController.java b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/controller/WebhookSendController.java index 3f2a1a0..f338fbe 100644 --- a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/controller/WebhookSendController.java +++ b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/controller/WebhookSendController.java @@ -11,7 +11,7 @@ import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import com.eactive.eai.rms.data.ext.djb.webhook.WebhookSendLog; +import com.eactive.eai.rms.data.entity.onl.djb.webhook.WebhookSendLog; import com.eactive.eai.rms.ext.djb.webhook.dto.WebhookSendRequest; import com.eactive.eai.rms.ext.djb.webhook.service.WebhookReceiveService; import com.eactive.eai.rms.ext.djb.webhook.service.WebhookService; @@ -38,16 +38,12 @@ public class WebhookSendController { public ResponseEntity> sendWebhook( @RequestBody WebhookSendRequest request) { - WebhookSendLog result = webhookService.send( - request.getTargetUrl(), - request.getEventType(), - request.getData() - ); + webhookService.send(request); Map response = new HashMap<>(); - response.put("logId", result.getId()); - response.put("success", result.getSuccess()); - response.put("statusCode", result.getStatusCode()); +// response.put("logId", result.getId()); +// response.put("success", result.getSuccess()); +// response.put("statusCode", result.getStatusCode()); return ResponseEntity.ok(response); } diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/dto/WebhookSendRequest.java b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/dto/WebhookSendRequest.java index 6250bc0..264338e 100644 --- a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/dto/WebhookSendRequest.java +++ b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/dto/WebhookSendRequest.java @@ -6,8 +6,9 @@ import lombok.Setter; @Getter @Setter public class WebhookSendRequest { - + private String orgId; private String targetUrl; private String eventType; + private String secret; private Object data; } \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/repository/WebhookReqRepository.java b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/repository/WebhookReqRepository.java new file mode 100644 index 0000000..7abc722 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/repository/WebhookReqRepository.java @@ -0,0 +1,11 @@ +package com.eactive.eai.rms.ext.djb.webhook.repository; + +import com.eactive.eai.rms.data.EMSDataSource; +import com.eactive.eai.rms.data.entity.onl.djb.webhook.WebhookReq; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +@EMSDataSource +public interface WebhookReqRepository extends JpaRepository { +} diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/repository/WebhookSendLogRepository.java b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/repository/WebhookSendLogRepository.java index 08141d6..2233a9c 100644 --- a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/repository/WebhookSendLogRepository.java +++ b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/repository/WebhookSendLogRepository.java @@ -10,7 +10,7 @@ import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import com.eactive.eai.rms.data.EMSDataSource; -import com.eactive.eai.rms.data.ext.djb.webhook.WebhookSendLog; +import com.eactive.eai.rms.data.entity.onl.djb.webhook.WebhookSendLog; @Repository @EMSDataSource diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/service/WebhookSendManager.java b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/service/WebhookSendManager.java deleted file mode 100644 index d2f5f59..0000000 --- a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/service/WebhookSendManager.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.eactive.eai.rms.ext.djb.webhook.service; - -import org.springframework.stereotype.Service; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -@Slf4j - @Service - @RequiredArgsConstructor - public class WebhookSendManager { - - public void send(String eaisvcname, String eventType) { - log.info("[Webhook] 발송 준비: {} {}", eaisvcname, eventType); - // TODO: PTL_WEBHOOK_REQ 조인 조회 후 WebhookService.send() 호출 - } - } \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/service/WebhookService.java b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/service/WebhookService.java index a7f1f5d..6a2ffc1 100644 --- a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/service/WebhookService.java +++ b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/service/WebhookService.java @@ -2,12 +2,16 @@ package com.eactive.eai.rms.ext.djb.webhook.service; import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; -import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -18,10 +22,17 @@ import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.HttpServerErrorException; import org.springframework.web.client.RestTemplate; -import com.eactive.eai.rms.data.ext.djb.webhook.WebhookSendLog; +import com.eactive.eai.rms.common.context.MonitoringContext; +import com.eactive.eai.rms.data.entity.onl.djb.webhook.QWebhookReq; +import com.eactive.eai.rms.data.entity.onl.djb.webhook.QWebhookReqApi; +import com.eactive.eai.rms.data.entity.onl.djb.webhook.QWebhookReqEvent; +import com.eactive.eai.rms.data.entity.onl.djb.webhook.WebhookSendLog; import com.eactive.eai.rms.ext.djb.webhook.dto.WebhookPayload; +import com.eactive.eai.rms.ext.djb.webhook.dto.WebhookSendRequest; import com.eactive.eai.rms.ext.djb.webhook.repository.WebhookSendLogRepository; import com.fasterxml.jackson.databind.ObjectMapper; +import com.querydsl.core.Tuple; +import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -34,19 +45,63 @@ public class WebhookService { private final WebhookSendLogRepository sendLogRepository; private final ObjectMapper objectMapper; private final RestTemplate restTemplate; + private final MonitoringContext monitoringContext; - - private String secretKey = "HW8JtFpkPmQqsVmr0Rb81P4qDypaNIVbGf3ZNMMPfyerhOHshoKABLaMBo1HA4BldTxhw1NjYmVnoPu9IIV7cyRXjecL3b2UctyR7DnVaJausltZLLr8Qm4Bzs4wRmgf"; + @PersistenceContext(unitName = "entityManagerFactoryForEMS") + private EntityManager entityManager; private static final String HMAC_ALGORITHM = "HmacSHA256"; - private static final int MAX_RETRY = 3; + private static final int DEFAULT_RETRY_COUNT = 3; + private static final int DEFAULT_RETRY_TIME = 10000; + + + @SuppressWarnings("unchecked") + public List findSendList(String event, List apiIds) { + QWebhookReq req = QWebhookReq.webhookReq; + QWebhookReqEvent evt = QWebhookReqEvent.webhookReqEvent; + QWebhookReqApi api = QWebhookReqApi.webhookReqApi; + + List tuples = new JPAQueryFactory(entityManager) + .select(req.orgId, req.targetUrl, req.secret, api.id.apiId) + .from(req) + .join(evt).on(req.id.eq(evt.id.webhookReqId)) + .join(api).on(req.id.eq(api.id.webhookReqId)) + .where(evt.id.eventType.eq(event) + .and(api.id.apiId.in(apiIds))) + .fetch(); + + // orgId + targetUrl + secret 기준으로 그룹핑, apiId를 data(List)로 집계 + Map resultMap = new LinkedHashMap<>(); + for (Tuple t : tuples) { + String key = t.get(req.orgId) + "|" + t.get(req.targetUrl); + resultMap.computeIfAbsent(key, k -> { + WebhookSendRequest wr = new WebhookSendRequest(); + wr.setOrgId(t.get(req.orgId)); + wr.setTargetUrl(t.get(req.targetUrl)); + wr.setSecret(t.get(req.secret)); + wr.setEventType(event); + wr.setData(new ArrayList()); + return wr; + }); + ((List) resultMap.get(key).getData()).add(t.get(api.id.apiId)); + } + + return new ArrayList<>(resultMap.values()); + } /** * 웹훅 발송 메인 메서드 */ @Transactional - public WebhookSendLog send(String targetUrl, String eventType, Object data) { + public void send(WebhookSendRequest req) { + + String targetUrl = req.getTargetUrl(); + String secret = req.getSecret(); + String eventType = req.getEventType(); + Object data = req.getData(); + WebhookSendLog sendLog = new WebhookSendLog(); + sendLog.setOrgId(req.getOrgId()); sendLog.setTargetUrl(targetUrl); sendLog.setEventType(eventType); @@ -57,7 +112,7 @@ public class WebhookService { sendLog.setPayload(payloadJson); // 2. Secret Key로 HMAC-SHA256 서명 생성 - String signature = generateSignature(payloadJson); + String signature = generateSignature(secret, payloadJson); sendLog.setSignature(signature); // 3. HTTP 요청 헤더 구성 @@ -67,18 +122,20 @@ public class WebhookService { headers.set("X-Webhook-Event", eventType); headers.set("X-Webhook-Timestamp", String.valueOf(webhookPayload.getTimestamp())); - // 4. 발송 + // 4. 발송 (재시도 포함) + int retryCount = monitoringContext.getIntProperty(MonitoringContext.API_WEBHOOK_RETRY_COUNT, DEFAULT_RETRY_COUNT); + int retryTime = monitoringContext.getIntProperty(MonitoringContext.API_WEBHOOK_RETRY_TIME, DEFAULT_RETRY_TIME); sendLog.setSentAt(LocalDateTime.now()); - HttpEntity request = new HttpEntity<>(payloadJson, headers); - ResponseEntity response = restTemplate.postForEntity(targetUrl, request, String.class); + HttpEntity httpRequest = new HttpEntity<>(payloadJson, headers); + ResponseEntity response = sendWithRetry(targetUrl, httpRequest, sendLog, retryCount, retryTime); // 5. 성공 로그 sendLog.setStatusCode(response.getStatusCode().value()); sendLog.setResponseBody(response.getBody()); sendLog.setSuccess(response.getStatusCode().is2xxSuccessful()); - log.info("[Webhook] 발송 성공 - eventType: {}, url: {}, status: {}", - eventType, targetUrl, response.getStatusCode()); + log.info("[Webhook] 발송 성공 - eventType: {}, url: {}, status: {}, retryCount: {}", + eventType, targetUrl, response.getStatusCode(), sendLog.getRetryCount()); } catch (HttpClientErrorException | HttpServerErrorException e) { sendLog.setStatusCode(e.getStatusCode().value()); @@ -95,40 +152,46 @@ public class WebhookService { eventType, targetUrl, e.getMessage()); } - return sendLogRepository.save(sendLog); + sendLogRepository.save(sendLog); + } + + /** - * 실패 건 재시도 + * 재시도 포함 HTTP 발송 + * - 4xx: 클라이언트 오류이므로 즉시 throw (재시도 불필요) + * - 5xx / 네트워크 오류: 최대 RETRY_COUNT회까지 exponential backoff 재시도 (1s → 2s → 4s) */ - @Transactional - public void retryFailedWebhooks() { - List failedLogs = - sendLogRepository.findFailedLogs(MAX_RETRY); - - for (WebhookSendLog failedLog : failedLogs) { + private ResponseEntity sendWithRetry(String targetUrl, HttpEntity request, WebhookSendLog sendLog, int retryCount, int retryTime) throws Exception { + Exception lastException = null; + for (int attempt = 0; attempt <= retryCount; attempt++) { try { - log.info("[Webhook] 재시도 - id: {}, retry: {}", failedLog.getId(), failedLog.getRetryCount()); - failedLog.setRetryCount(failedLog.getRetryCount() + 1); - sendLogRepository.save(failedLog); - - send(failedLog.getTargetUrl(), failedLog.getEventType(), - objectMapper.readValue(failedLog.getPayload(), Object.class)); - + if (attempt > 0) { + long delayMs = retryTime * (1L << (attempt - 1)); + log.info("[Webhook] 재시도 {}/{} - url: {}, delay: {}ms", attempt, retryCount, targetUrl, delayMs); + Thread.sleep(delayMs); + sendLog.setRetryCount(attempt); + } + return restTemplate.postForEntity(targetUrl, request, String.class); + } catch (HttpClientErrorException e) { + throw e; // 4xx는 재시도 불필요 } catch (Exception e) { - log.error("[Webhook] 재시도 실패 - id: {}", failedLog.getId(), e); + lastException = e; + log.warn("[Webhook] 발송 실패 (attempt {}/{}) - url: {}, error: {}", attempt, retryCount, targetUrl, e.getMessage()); } } + throw lastException; } /** * HMAC-SHA256 서명 생성 * Java 17 미만은 HexFormat 미지원이므로 직접 변환 */ - private String generateSignature(String payload) throws Exception { + private String generateSignature(String secret, String payload) throws Exception { Mac mac = Mac.getInstance(HMAC_ALGORITHM); SecretKeySpec keySpec = new SecretKeySpec( - secretKey.getBytes(StandardCharsets.UTF_8), HMAC_ALGORITHM); + secret.getBytes(StandardCharsets.UTF_8), HMAC_ALGORITHM); mac.init(keySpec); byte[] hash = mac.doFinal(payload.getBytes(StandardCharsets.UTF_8)); return bytesToHex(hash); diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/approval/PortalApprovalAuthorizer.java b/src/main/java/com/eactive/eai/rms/onl/apim/approval/PortalApprovalAuthorizer.java index 1995392..4d6f2f3 100644 --- a/src/main/java/com/eactive/eai/rms/onl/apim/approval/PortalApprovalAuthorizer.java +++ b/src/main/java/com/eactive/eai/rms/onl/apim/approval/PortalApprovalAuthorizer.java @@ -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; } } diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/approval/PortalApprovalManController.java b/src/main/java/com/eactive/eai/rms/onl/apim/approval/PortalApprovalManController.java index 8a72d2f..a8a0500 100644 --- a/src/main/java/com/eactive/eai/rms/onl/apim/approval/PortalApprovalManController.java +++ b/src/main/java/com/eactive/eai/rms/onl/apim/approval/PortalApprovalManController.java @@ -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,4 +82,52 @@ 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> hardDeleteMultiple(String ids) { + Map 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 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 update(String id, String expectEndDate) { + portalApprovalManService.update(id, expectEndDate); + return ResponseEntity.ok().build(); + } } diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/approval/PortalApprovalManService.java b/src/main/java/com/eactive/eai/rms/onl/apim/approval/PortalApprovalManService.java index 99cf1b2..8ef8fbe 100644 --- a/src/main/java/com/eactive/eai/rms/onl/apim/approval/PortalApprovalManService.java +++ b/src/main/java/com/eactive/eai/rms/onl/apim/approval/PortalApprovalManService.java @@ -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 selectList(Pageable pageable, PortalApprovalUISearch portalApprovalUISearch) { Page approval = approvalService.findAll(pageable, portalApprovalUISearch); @@ -76,6 +87,16 @@ public class PortalApprovalManService extends BaseService { * 마스킹된 상세 정보 조회 */ public PortalApprovalUI selectDetail(String id) { + //2026.06.05 관리자가 승인요청을 조회시 심사중으로 상태 변경 + //APP 유형이고 REQUESTED 상태인 경우 관리자 조회 시 PROCESSING으로 변경 + approvalService.findById(id).ifPresent(approval -> { + if (approval.getApprovalType().equals(ApprovalType.APP) + && approval.getApprovalStatus() instanceof RequestedState) { + approval.setApprovalStatus(new ProcessingState()); + approvalService.save(approval); + } + }); + PortalApprovalUI result = getDetailWithMaskOption(id, false); if (result == null) { throw new IllegalArgumentException("승인 정보를 찾을 수 없습니다. ID: " + id); @@ -320,6 +341,13 @@ public class PortalApprovalManService extends BaseService { sendEvent(id, ApprovalEvent.DENY, options); } + public void update(String id, String expectEndDate) { + Approval approval = approvalService.findById(id) + .orElseThrow(() -> new IllegalArgumentException("승인 정보를 찾을 수 없습니다. ID: " + id)); + approval.setExpectEndDate(expectEndDate); + approvalService.save(approval); + } + public void redeploy(String id) { Map options = new HashMap<>(); Approval approval = approvalService.findById(id).orElseThrow(() -> new IllegalArgumentException("승인 정보를 찾을 수 없습니다. ID: " + id)); @@ -330,6 +358,28 @@ public class PortalApprovalManService extends BaseService { } + public boolean isHardDeleteEnabled() { + Map 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 options) { approvalService.findById(id).ifPresent(approval -> { options.put("authorizer", portalApprovalAuthorizer); diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/approval/PortalApprovalUI.java b/src/main/java/com/eactive/eai/rms/onl/apim/approval/PortalApprovalUI.java index 5df32b4..029d3f6 100644 --- a/src/main/java/com/eactive/eai/rms/onl/apim/approval/PortalApprovalUI.java +++ b/src/main/java/com/eactive/eai/rms/onl/apim/approval/PortalApprovalUI.java @@ -38,6 +38,8 @@ public class PortalApprovalUI { @DateTimeFormat(pattern = "yyyyMMddHHmmssSSS") private String approvalDate; + private String expectEndDate; + public String getApprovalTypeDescription() { return approvalType != null ? approvalType.getDescription() : ""; } diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/approval/app/PortalAppApprovalListener.java b/src/main/java/com/eactive/eai/rms/onl/apim/approval/app/PortalAppApprovalListener.java index 6eef6fb..7c95b34 100644 --- a/src/main/java/com/eactive/eai/rms/onl/apim/approval/app/PortalAppApprovalListener.java +++ b/src/main/java/com/eactive/eai/rms/onl/apim/approval/app/PortalAppApprovalListener.java @@ -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 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()); diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryClosingService.java b/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryClosingService.java new file mode 100644 index 0000000..48b6dae --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryClosingService.java @@ -0,0 +1,78 @@ +package com.eactive.eai.rms.onl.apim.portalInquiry; + +import com.eactive.apim.portal.qna.entity.Inquiry; +import com.eactive.apim.portal.qna.entity.InquiryComment; +import com.eactive.eai.rms.data.entity.onl.apim.portalinquiry.PortalInquiryCommentRepository; +import com.eactive.eai.rms.data.entity.onl.apim.portalinquiry.PortalInquiryCommentService; +import com.eactive.eai.rms.data.entity.onl.apim.portalinquiry.PortalInquiryService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +/** + * RESPONDED 상태 문의글 중 아래 조건에 해당하는 경우 CLOSED로 변경한다. + * - 조건 A: 활성 댓글이 없고 답변완료일(responseDate)로부터 N일 경과 + * - 조건 B: 마지막 관리자 댓글(adminYn=Y) 이후 N일간 포탈사용자(adminYn=N) 댓글 없음 + */ +@Service +@Transactional(transactionManager = "transactionManagerForEMS") +@Slf4j +@RequiredArgsConstructor +public class PortalInquiryClosingService { + + private static final String RESPONDED = "RESPONDED"; + private static final String CLOSED = "CLOSED"; + + private final PortalInquiryService portalInquiryService; + private final PortalInquiryCommentService portalInquiryCommentService; + private final PortalInquiryCommentRepository inquiryCommentRepository; + + public int closeExpiredInquiries(int closingDays) { + LocalDateTime threshold = LocalDateTime.now().minusDays(closingDays); + List respondedInquiries = portalInquiryService.findByInquiryStatus(RESPONDED); + + int closedCount = 0; + for (Inquiry inquiry : respondedInquiries) { + if (shouldClose(inquiry, threshold)) { + inquiry.setInquiryStatus(CLOSED); + portalInquiryService.save(inquiry); + closedCount++; + log.info("문의글 CLOSED 처리: id={}", inquiry.getId()); + } + } + log.info("InquiryCommentClosingService: 총 {}건 CLOSED 처리 (기준={}일)", closedCount, closingDays); + return closedCount; + } + + private boolean shouldClose(Inquiry inquiry, LocalDateTime threshold) { + long activeCommentCount = portalInquiryCommentService.countActiveByInquiryId(inquiry.getId()); + + // 댓글 없음 → 답변완료일 기준 N일 경과 여부만 확인 + if (activeCommentCount == 0) { + LocalDateTime responseDate = inquiry.getResponseDate(); + return responseDate != null && !responseDate.isAfter(threshold); + } + + // 가장 최근 활성 댓글 확인 + Optional lastComment = inquiryCommentRepository + .findTopByInquiry_IdAndDelYnOrderByCreatedDateDesc(inquiry.getId(), "N"); + + if (!lastComment.isPresent()) { + return false; + } + + // 포탈사용자(adminYn != 'Y')가 마지막 댓글 작성자 → CLOSED 불가 + if (!"Y".equals(lastComment.get().getAdminYn())) { + return false; + } + + // 관리자가 마지막 댓글 작성자 → N일 경과 여부 확인 + LocalDateTime lastAdminDate = lastComment.get().getCreatedDate(); + return lastAdminDate != null && !lastAdminDate.isAfter(threshold); + } +} diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryCommentUI.java b/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryCommentUI.java new file mode 100644 index 0000000..d609ca4 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryCommentUI.java @@ -0,0 +1,40 @@ +package com.eactive.eai.rms.onl.apim.portalInquiry; + +import com.eactive.apim.portal.file.entity.FileInfo; +import com.eactive.eai.data.converter.LocalDateTimeFormatters; +import com.eactive.eai.rms.onl.apim.portaluser.PortalUserUI; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.web.multipart.MultipartFile; + +import java.time.LocalDateTime; +import java.util.List; + + +@Data +public class PortalInquiryCommentUI { + + private String id; + + private String inquiryId; + + private String commentDetail; + + private String delYn; + + private String createdBy; + + private String writerName; + + private String lastModifiedBy; + + @JsonFormat(pattern = LocalDateTimeFormatters.FORMAT_YYYYMMDDHHMMSS_14) + @DateTimeFormat(pattern = "yyyyMMddHHmmss") + private LocalDateTime createdDate; + + @JsonFormat(pattern = LocalDateTimeFormatters.FORMAT_YYYYMMDDHHMMSS_14) + @DateTimeFormat(pattern = "yyyyMMddHHmmss") + private LocalDateTime lastModifiedDate; + +} diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryCommentUIMapper.java b/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryCommentUIMapper.java new file mode 100644 index 0000000..8a5e3dd --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryCommentUIMapper.java @@ -0,0 +1,17 @@ +package com.eactive.eai.rms.onl.apim.portalInquiry; + +import com.eactive.apim.portal.qna.entity.InquiryComment; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; + +import java.util.List; + +@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface PortalInquiryCommentUIMapper { + + @Mapping(target = "inquiryId", source = "inquiry.id") + PortalInquiryCommentUI toVo(InquiryComment entity); + + List toVoList(List entities); +} diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryManController.java b/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryManController.java index a87c6ee..5fe6a76 100644 --- a/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryManController.java +++ b/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryManController.java @@ -73,5 +73,23 @@ public class PortalInquiryManController extends BaseAnnotationController { portalInquiryManService.deleteBulk(ids); return ResponseEntity.ok().build(); } + + @PostMapping(value = "/onl/apim/portalinquiry/portalInquiryMan.json", params = "cmd=LIST_COMMENT") + public ResponseEntity> selectCommentList(String id) { + return ResponseEntity.ok(portalInquiryManService.selectCommentList(id)); + } + + @PostMapping(value = "/onl/apim/portalinquiry/portalInquiryMan.json", params = "cmd=INSERT_COMMENT") + public ResponseEntity insertComment(HttpServletRequest request, PortalInquiryCommentUI portalInquiryCommentUI) { + String userid = SessionManager.getUserId(request); + portalInquiryManService.insertComment(portalInquiryCommentUI, userid); + return ResponseEntity.ok().build(); + } + + @PostMapping(value = "/onl/apim/portalinquiry/portalInquiryMan.json", params = "cmd=DELETE_COMMENT") + public ResponseEntity deleteComment(String id) { + portalInquiryManService.deleteComment(id); + return ResponseEntity.ok().build(); + } } diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryManService.java b/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryManService.java index f8549d1..fff483c 100644 --- a/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryManService.java +++ b/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryManService.java @@ -4,10 +4,14 @@ import com.eactive.apim.portal.file.entity.FileInfo; import com.eactive.apim.portal.file.service.FileService; import com.eactive.apim.portal.portaluser.entity.PortalUser; import com.eactive.apim.portal.portaluser.entity.PortalUserEnums; +import com.eactive.apim.portal.portaluser.repository.PortalUserRepository; import com.eactive.apim.portal.qna.entity.Inquiry; +import com.eactive.apim.portal.qna.entity.InquiryComment; import com.eactive.apim.portal.user.entity.UserInfo; import com.eactive.eai.rms.common.base.BaseService; +import com.eactive.eai.rms.common.exception.IntegrationException; import com.eactive.eai.rms.data.entity.man.user.UserInfoService; +import com.eactive.eai.rms.data.entity.onl.apim.portalinquiry.PortalInquiryCommentService; import com.eactive.eai.rms.data.entity.onl.apim.portalinquiry.PortalInquirySearch; import com.eactive.eai.rms.data.entity.onl.apim.portalinquiry.PortalInquiryService; import com.eactive.eai.rms.onl.apim.masking.MaskingUtils; @@ -22,27 +26,38 @@ import lombok.extern.slf4j.Slf4j; import java.time.LocalDateTime; import java.util.Arrays; import java.util.List; +import java.util.Optional; @Service @Transactional(transactionManager = "transactionManagerForEMS") @Slf4j public class PortalInquiryManService extends BaseService { private final PortalInquiryService portalInquiryService; + private final PortalInquiryCommentService portalInquiryCommentService; private final PortalInquiryUIMapper portalInquiryUIMapper; + private final PortalInquiryCommentUIMapper portalInquiryCommentUIMapper; private final UserInfoService userInfoService; + private final PortalUserRepository portalUserRepository; private final FileService fileService; public static final String PENDING = "PENDING"; public static final String RESPONDED = "RESPONDED"; + public static final String CLOSED = "CLOSED"; public PortalInquiryManService(PortalInquiryService portalInquiryService, + PortalInquiryCommentService portalInquiryCommentService, PortalInquiryUIMapper portalInquiryUIMapper, + PortalInquiryCommentUIMapper portalInquiryCommentUIMapper, UserInfoService userInfoService, + PortalUserRepository portalUserRepository, FileService fileService){ this.portalInquiryService = portalInquiryService; + this.portalInquiryCommentService = portalInquiryCommentService; this.portalInquiryUIMapper = portalInquiryUIMapper; + this.portalInquiryCommentUIMapper = portalInquiryCommentUIMapper; this.userInfoService = userInfoService; + this.portalUserRepository = portalUserRepository; this.fileService = fileService; } @@ -55,6 +70,7 @@ public class PortalInquiryManService extends BaseService { if (ui.getInquirer() != null) { maskPortalInquiryUI(ui); } + ui.setCommentCount((int) portalInquiryCommentService.countActiveByInquiryId(inquiry.getId())); return setInquiryAttachmentStatus(ui); }); } @@ -141,6 +157,7 @@ public class PortalInquiryManService extends BaseService { UserInfo userinfo = userInfoService.getById(userId); Inquiry inquiry = portalInquiryService.getById(portalInquiryUI.getId()); + validateNotClosed(inquiry); // 답변 대기중 상태일 경우만 답변완료로 상태 변경 if(inquiry.getInquiryStatus().equals(PENDING)) { inquiry.setInquiryStatus(RESPONDED); @@ -161,5 +178,53 @@ public class PortalInquiryManService extends BaseService { List idList = Arrays.asList(ids.split(",")); portalInquiryService.deleteByIds(idList); } + + public List selectCommentList(String inquiryId) { + List comments = portalInquiryCommentService.findByInquiryId(inquiryId); + List result = portalInquiryCommentUIMapper.toVoList(comments); + result.forEach(ui -> ui.setWriterName(resolveWriterName(ui.getCreatedBy()))); + return result; + } + + private String resolveWriterName(String userId) { + if (StringUtils.isBlank(userId)) return ""; + // 직원(내부 사용자) 여부 먼저 확인 → 이름 그대로 반환 + Optional staff = userInfoService.findById(userId); + if (staff.isPresent()) { + return staff.get().getUsername(); + } + // 포털 회원이면 이름 마스킹 처리 (김수민 → 김수*) + Optional portalUser = portalUserRepository.findById(userId); + if (portalUser.isPresent()) { + return MaskingUtils.maskName(portalUser.get().getUserName()); + } + return userId; + } + + public void insertComment(PortalInquiryCommentUI commentUI, String userId) { + Inquiry inquiry = portalInquiryService.getById(commentUI.getInquiryId()); + validateNotClosed(inquiry); + + InquiryComment comment = new InquiryComment(); + comment.setInquiry(inquiry); + comment.setCommentDetail(commentUI.getCommentDetail()); + comment.setDelYn("N"); + comment.setAdminYn("Y"); + + portalInquiryCommentService.save(comment); + } + + public void deleteComment(String commentId) { + InquiryComment comment = portalInquiryCommentService.getById(commentId); + validateNotClosed(comment.getInquiry()); + comment.setDelYn("Y"); + portalInquiryCommentService.save(comment); + } + + private void validateNotClosed(Inquiry inquiry) { + if (CLOSED.equals(inquiry.getInquiryStatus())) { + throw new IntegrationException("답변종료되어 수정할 수 없습니다."); + } + } } diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryUI.java b/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryUI.java index fd48292..df3669c 100644 --- a/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryUI.java +++ b/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryUI.java @@ -55,4 +55,6 @@ public class PortalInquiryUI { private LocalDateTime lastModifiedDate; private String orgName; + + private int commentCount; } diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/portalnotice/IncidentAffectedApiUI.java b/src/main/java/com/eactive/eai/rms/onl/apim/portalnotice/IncidentAffectedApiUI.java new file mode 100644 index 0000000..d38dcc7 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/onl/apim/portalnotice/IncidentAffectedApiUI.java @@ -0,0 +1,11 @@ +package com.eactive.eai.rms.onl.apim.portalnotice; + +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class IncidentAffectedApiUI { + private String apiId; + private String apiName; +} diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/portalnotice/PortalNoticeManService.java b/src/main/java/com/eactive/eai/rms/onl/apim/portalnotice/PortalNoticeManService.java index a668864..e432b14 100644 --- a/src/main/java/com/eactive/eai/rms/onl/apim/portalnotice/PortalNoticeManService.java +++ b/src/main/java/com/eactive/eai/rms/onl/apim/portalnotice/PortalNoticeManService.java @@ -1,5 +1,13 @@ package com.eactive.eai.rms.onl.apim.portalnotice; +import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncident; +import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncidentApi; +import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncidentApiId; +import com.eactive.apim.portal.djb.apistatus.incident.entity.IncidentKind; +import com.eactive.apim.portal.djb.apistatus.incident.entity.IncidentState; +import com.eactive.apim.portal.djb.apistatus.incident.repository.DjbApistatusIncidentApiRepository; +import com.eactive.apim.portal.djb.apistatus.incident.repository.DjbApistatusIncidentRepository; +import com.eactive.apim.portal.djb.apistatus.incident.repository.DjbApistatusIncidentTimelineRepository; import com.eactive.apim.portal.file.entity.FileDetail; import com.eactive.apim.portal.file.entity.FileInfo; import com.eactive.apim.portal.file.service.FileService; @@ -23,9 +31,13 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; 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; +import java.util.stream.Collectors; @Service @Transactional(transactionManager = "transactionManagerForEMS") @@ -34,6 +46,9 @@ public class PortalNoticeManService extends BaseService { private final PortalNoticeService portalNoticeService; private final PortalNoticeUIMapper portalNoticeUIMapper; private final FileService fileService; + private final DjbApistatusIncidentRepository incidentRepository; + private final DjbApistatusIncidentApiRepository incidentApiRepository; + private final DjbApistatusIncidentTimelineRepository incidentTimelineRepository; private String decodeString(String value) { if (ContainerUtil.get() == ContainerUtil.TOMCAT) { @@ -50,10 +65,27 @@ public class PortalNoticeManService extends BaseService { @Autowired public PortalNoticeManService(PortalNoticeService portalNoticeService, PortalNoticeUIMapper portalNoticeUIMapper, - FileService fileService){ + FileService fileService, + DjbApistatusIncidentRepository incidentRepository, + DjbApistatusIncidentApiRepository incidentApiRepository, + DjbApistatusIncidentTimelineRepository incidentTimelineRepository){ this.portalNoticeService = portalNoticeService; this.portalNoticeUIMapper = portalNoticeUIMapper; this.fileService = fileService; + this.incidentRepository = incidentRepository; + this.incidentApiRepository = incidentApiRepository; + this.incidentTimelineRepository = incidentTimelineRepository; + } + + private static boolean isIncidentKind(String noticeType) { + return PortalNoticeUI.NOTICE_TYPE_INCIDENT.equals(noticeType) + || PortalNoticeUI.NOTICE_TYPE_MAINTENANCE.equals(noticeType); + } + + private static IncidentKind toKind(String noticeType) { + if (PortalNoticeUI.NOTICE_TYPE_INCIDENT.equals(noticeType)) return IncidentKind.INCIDENT; + if (PortalNoticeUI.NOTICE_TYPE_MAINTENANCE.equals(noticeType)) return IncidentKind.MAINTENANCE; + return null; } @@ -91,9 +123,39 @@ public class PortalNoticeManService extends BaseService { PortalNoticeUI ui = portalNoticeUIMapper.toVo(portalNotice); setFileInfo(portalNotice, ui); + populateIncident(portalNotice, ui); return ui; } + private void populateIncident(PortalNotice portalNotice, PortalNoticeUI ui) { + if (!isIncidentKind(portalNotice.getNoticeType())) { + ui.setAffectedApis(Collections.emptyList()); + return; + } + Optional incidentOpt = incidentRepository.findByNoticeId(portalNotice.getId()); + if (!incidentOpt.isPresent()) { + ui.setAffectedApis(Collections.emptyList()); + return; + } + DjbApistatusIncident incident = incidentOpt.get(); + ui.setIncidentId(incident.getIncidentId()); + ui.setSummary(incident.getSummary()); + ui.setStartedAt(incident.getStartedAt()); + ui.setEndAt(incident.getEndAt()); + ui.setState(incident.getState() == null ? null : incident.getState().name()); + ui.setPreviousState(incident.getPreviousState() == null ? null : incident.getPreviousState().name()); + + List apis = incidentApiRepository + .findByIncidentIdOrderByApiId(incident.getIncidentId()).stream() + .map(api -> { + IncidentAffectedApiUI vo = new IncidentAffectedApiUI(); + vo.setApiId(api.getApiId()); + vo.setApiName(api.getApiName()); + return vo; + }).collect(Collectors.toList()); + ui.setAffectedApis(apis); + } + public void insert(PortalNoticeUI portalNoticeUI) throws IOException { //portalNoticeUI.setNoticeSubject(decodeString(portalNoticeUI.getNoticeSubject())); //portalNoticeUI.setNoticeDetail(decodeString(StringEscapeUtils.unescapeHtml(portalNoticeUI.getNoticeDetail()))); @@ -108,6 +170,61 @@ public class PortalNoticeManService extends BaseService { } portalNoticeService.save(portalNotice); + + if (isIncidentKind(portalNoticeUI.getNoticeType())) { + persistIncident(portalNoticeUI, portalNotice, true); + } + } + + private void persistIncident(PortalNoticeUI portalNoticeUI, PortalNotice portalNotice, boolean isCreate) { + IncidentKind kind = toKind(portalNoticeUI.getNoticeType()); + + DjbApistatusIncident incident = incidentRepository.findByNoticeId(portalNotice.getId()) + .orElseGet(DjbApistatusIncident::new); + + incident.setKind(kind); + incident.setTitle(portalNotice.getNoticeSubject()); + incident.setSummary(portalNoticeUI.getSummary()); + incident.setStartedAt(portalNoticeUI.getStartedAt() != null + ? portalNoticeUI.getStartedAt() : LocalDateTime.now()); + incident.setEndAt(portalNoticeUI.getEndAt()); + incident.setDetectedBy("MANUAL"); + incident.setNoticeId(portalNotice.getId()); + incident.setDraftYn("N"); + incident.setFixYn(StringUtils.defaultIfBlank(portalNotice.getFixYn(), "N")); + + if (kind == IncidentKind.INCIDENT) { + IncidentState newState = StringUtils.isNotBlank(portalNoticeUI.getState()) + ? IncidentState.valueOf(portalNoticeUI.getState()) + : IncidentState.INVESTIGATING; + if (!isCreate && incident.getState() != newState) { + incident.setPreviousState(incident.getState()); + } + incident.setState(newState); + } else { + incident.setState(null); + incident.setPreviousState(null); + } + + DjbApistatusIncident saved = incidentRepository.save(incident); + + // 영향 API 동기화 — 단순 전체 삭제 후 재삽입 + incidentApiRepository.deleteByIncidentId(saved.getIncidentId()); + List affected = portalNoticeUI.getAffectedApis(); + if (affected != null && !affected.isEmpty()) { + List rows = new ArrayList<>(); + for (IncidentAffectedApiUI src : affected) { + if (StringUtils.isBlank(src.getApiId())) continue; + DjbApistatusIncidentApi api = new DjbApistatusIncidentApi(); + api.setIncidentId(saved.getIncidentId()); + api.setApiId(src.getApiId()); + api.setApiName(src.getApiName()); + rows.add(api); + } + if (!rows.isEmpty()) { + incidentApiRepository.saveAll(rows); + } + } } private void handleFileUpload(PortalNoticeUI portalNoticeUI, PortalNotice portalNotice) throws IOException { @@ -131,6 +248,7 @@ public class PortalNoticeManService extends BaseService { portalNoticeUI.setNoticeDetail(StringEscapeUtils.unescapeHtml(portalNoticeUI.getNoticeDetail())); PortalNotice portalNotice = portalNoticeService.getById(portalNoticeUI.getId()); + String previousNoticeType = portalNotice.getNoticeType(); if (portalNoticeUI.getFiles() != null && !portalNoticeUI.getFiles().isEmpty()) { handleFileUpload(portalNoticeUI, portalNotice); @@ -141,11 +259,27 @@ public class PortalNoticeManService extends BaseService { portalNoticeUIMapper.updateToEntity(portalNoticeUI, portalNotice); portalNoticeService.save(portalNotice); + + if (isIncidentKind(portalNotice.getNoticeType())) { + persistIncident(portalNoticeUI, portalNotice, false); + } else if (isIncidentKind(previousNoticeType)) { + // 일반 공지로 유형 변경 → 기존 incident 정리 + deleteIncidentIfExists(portalNotice.getId()); + } + } + + private void deleteIncidentIfExists(String noticeId) { + incidentRepository.findByNoticeId(noticeId).ifPresent(incident -> { + incidentApiRepository.deleteByIncidentId(incident.getIncidentId()); + incidentTimelineRepository.deleteByIncidentId(incident.getIncidentId()); + incidentRepository.delete(incident); + }); } public void delete(String id) { PortalNotice portalNotice = portalNoticeService.getById(id); Optional.ofNullable(portalNotice.getFileId()).ifPresent(fileService::deleteFile); + deleteIncidentIfExists(id); portalNoticeService.deleteById(id); } diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/portalnotice/PortalNoticeUI.java b/src/main/java/com/eactive/eai/rms/onl/apim/portalnotice/PortalNoticeUI.java index bded835..db2a1da 100644 --- a/src/main/java/com/eactive/eai/rms/onl/apim/portalnotice/PortalNoticeUI.java +++ b/src/main/java/com/eactive/eai/rms/onl/apim/portalnotice/PortalNoticeUI.java @@ -8,11 +8,16 @@ import org.springframework.format.annotation.DateTimeFormat; import org.springframework.web.multipart.MultipartFile; import java.time.LocalDateTime; +import java.util.List; @Data public class PortalNoticeUI { + public static final String NOTICE_TYPE_NORMAL = "1"; + public static final String NOTICE_TYPE_MAINTENANCE = "2"; + public static final String NOTICE_TYPE_INCIDENT = "3"; + private String id; private String noticeSubject; @@ -53,4 +58,24 @@ public class PortalNoticeUI { private boolean hasAttachment; // 첨부파일 여부 private boolean fileDeleted; // 파일 삭제 여부 + + // ───── 장애/점검(djb_apistatus_incident) 연동 필드 ───── + private Long incidentId; + + private String summary; + + // HTML 는 'T' 구분자 형식 (예: 2026-06-16T10:29). + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm") + @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm") + private LocalDateTime startedAt; + + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm") + @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm") + private LocalDateTime endAt; + + private String state; // INCIDENT 한정 (INVESTIGATING/IDENTIFIED/MONITORING/RESOLVED/CANCELED) + + private String previousState; // 직전 상태 + + private List affectedApis; } diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/portalnotice/PortalNoticeUIMapper.java b/src/main/java/com/eactive/eai/rms/onl/apim/portalnotice/PortalNoticeUIMapper.java index d8fa0ed..02aa8c1 100644 --- a/src/main/java/com/eactive/eai/rms/onl/apim/portalnotice/PortalNoticeUIMapper.java +++ b/src/main/java/com/eactive/eai/rms/onl/apim/portalnotice/PortalNoticeUIMapper.java @@ -3,7 +3,9 @@ package com.eactive.eai.rms.onl.apim.portalnotice; import com.eactive.apim.portal.portalNotice.entity.PortalNotice; import org.mapstruct.*; -@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE) +@Mapper(componentModel = "spring", + unmappedTargetPolicy = ReportingPolicy.IGNORE, + unmappedSourcePolicy = ReportingPolicy.IGNORE) public interface PortalNoticeUIMapper { PortalNoticeUI toVo(PortalNotice entity); diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/portalorg/PortalOrgManService.java b/src/main/java/com/eactive/eai/rms/onl/apim/portalorg/PortalOrgManService.java index 65d6db6..90db7dd 100644 --- a/src/main/java/com/eactive/eai/rms/onl/apim/portalorg/PortalOrgManService.java +++ b/src/main/java/com/eactive/eai/rms/onl/apim/portalorg/PortalOrgManService.java @@ -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 selectList(Pageable pageable, PortalOrgUISearch portalOrgUISearch) { Page 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 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 selectDetailWithUsers(String id) { return getDetailWithUsers(id, true); } diff --git a/src/main/java/com/eactive/eai/rms/onl/common/service/ums/UmsDispatchService.java b/src/main/java/com/eactive/eai/rms/onl/common/service/ums/UmsDispatchService.java index dc952fa..02bc0c0 100644 --- a/src/main/java/com/eactive/eai/rms/onl/common/service/ums/UmsDispatchService.java +++ b/src/main/java/com/eactive/eai/rms/onl/common/service/ums/UmsDispatchService.java @@ -1,19 +1,5 @@ package com.eactive.eai.rms.onl.common.service.ums; -import com.eactive.apim.portal.template.entity.MessageRequest; -import com.eactive.apim.portal.template.repository.MessageRequestRepository; -import com.eactive.ext.kjb.ums.KjbEmailSendModule; -import com.eactive.ext.kjb.ums.KjbUmsService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.stereotype.Service; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.transaction.support.TransactionTemplate; - -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; import java.time.LocalDateTime; import java.util.List; import java.util.concurrent.LinkedBlockingQueue; @@ -21,6 +7,23 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import javax.annotation.PostConstruct; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionTemplate; + +import com.eactive.apim.portal.template.entity.MessageRequest; +import com.eactive.apim.portal.template.repository.MessageRequestRepository; +import com.eactive.eai.rms.common.datasource.DataSourceContextHolder; +import com.eactive.eai.rms.common.datasource.DataSourceTypeManager; +import com.eactive.eai.rms.ext.djb.ums.service.UmsService; + +import lombok.extern.slf4j.Slf4j; @Slf4j @@ -40,13 +43,17 @@ public class UmsDispatchService { private EntityManager entityManager; private final MessageRequestRepository messageRequestRepository; + + private final UmsService umsService; @Autowired public UmsDispatchService( @Qualifier("transactionManagerForEMS") PlatformTransactionManager transactionManager, - MessageRequestRepository messageRequestRepository + MessageRequestRepository messageRequestRepository, + UmsService umsService ) { this.messageRequestRepository = messageRequestRepository; + this.umsService = umsService; this.executorService = new ThreadPoolExecutor( CORE_POOL_SIZE, @@ -67,15 +74,8 @@ public class UmsDispatchService { // KjbEmailSendModule.getInstance().setRepository(messageRequestRepository); log.info("UmsDispatchService initialized - KjbUmsService and KjbEmailSendModule ready via singleton"); } + -// private KjbUmsService getUmsService() { -// return KjbUmsService.getInstance(); -// } -// -// private KjbEmailSendModule getEmailModule() { -// return KjbEmailSendModule.getInstance(); -// } -// @SuppressWarnings("unchecked") @Transactional(transactionManager = "transactionManagerForEMS") public void processMessages() { @@ -112,7 +112,7 @@ public class UmsDispatchService { log.info("Starting to submit {} messages for processing", messagesToProcess.size()); for (MessageRequest message : messagesToProcess) { - //submitMessageTask(message); + submitMessageTask(message); } } else { log.info("No pending messages found"); @@ -123,66 +123,42 @@ public class UmsDispatchService { } -// private void submitMessageTask(MessageRequest message) { -// int queueSize = executorService.getQueue().size(); -// log.debug("Current queue size before submission: {}", queueSize); -// -// executorService.submit(() -> { -// try { -// transactionTemplate.execute(status -> { -// try { -// // 광주은행에서는 사용하지 않는 메소드 -//// processMessage(message); -// -// if (KjbUmsService.isAllowChannel(message)) { -// getUmsService().send(message); -// } -// -// if (KjbEmailSendModule.isAllowChannel(message)) { -// getEmailModule().sendEmail(message); -// } -// -// return null; -// } catch (Exception e) { -// log.error("Failed to process message: {} - Error: {}", -// message.getId(), e.getMessage(), e); -// updateMessageStatus(message, "FAILED"); -// status.setRollbackOnly(); -// return null; -// } -// }); -// } catch (Exception e) { -// log.error("Transaction execution failed for message: {} - Error: {}", -// message.getId(), e.getMessage(), e); -// } -// }); -// } + private void submitMessageTask(MessageRequest message) { + int queueSize = executorService.getQueue().size(); + log.debug("Current queue size before submission: {}", queueSize); - /** - * kbank 방식으로 광주은행에서는 사용하지 않음 - * @param message - * @throws IOException - */ -// @Deprecated -// public void processMessage(MessageRequest message) throws IOException { -// try { -// String standardMessage = messageFactory.createMessage(message); -// log.debug("Processing message ID: {} - Type: {}", message.getId(), message.getMessageType()); -// -// umsSender.sendMessage(standardMessage); -// updateMessageStatus(message, "SENT"); -// log.debug("Successfully processed and sent message ID: {}", message.getId()); -// } catch (Exception e) { -// log.error("Message processing failed for ID: {} - Error: {}", -// message.getId(), e.getMessage(), e); -// updateMessageStatus(message, "FAILED"); -// throw e; -// } -// } + executorService.submit(() -> { + try { + transactionTemplate.execute(status -> { + try { + umsService.send(message); + updateMessageStatus(message, "SENT"); + return null; + } catch (Exception e) { + // checked exception을 RuntimeException으로 감싸 전파 → TransactionTemplate이 rollback 후 re-throw + throw new RuntimeException(e); + } + }); + } catch (Exception e) { + log.error("Failed to process message: {} - Error: {}", message.getId(), e.getMessage(), e); + try { + transactionTemplate.execute(s -> { + updateMessageStatus(message, "FAILED"); + return null; + }); + } catch (Exception ex) { + log.error("Failed to update FAIL status for message: {}", message.getId(), ex); + } + } + }); + } + + public void updateMessageStatus(MessageRequest message, String status) { message.setRequestStatus(status); message.setSentDate(LocalDateTime.now()); entityManager.merge(message); + entityManager.flush(); } } \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/rms/onl/common/util/K8sUtil.java b/src/main/java/com/eactive/eai/rms/onl/common/util/K8sUtil.java deleted file mode 100644 index a723ceb..0000000 --- a/src/main/java/com/eactive/eai/rms/onl/common/util/K8sUtil.java +++ /dev/null @@ -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> getServerInfoByK8sUrl(String namespace, String serviceName, int port, String extUrl) throws Exception { - - List> resultList = getServerInfoByK8sApi(namespace,serviceName); - - for (Map map : resultList) { - String EAISEVRINSTNCNAME = map.get("EAISEVRINSTNCNAME"); - String EAISEVRIP = map.get("EAISEVRIP"); - Map 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> getServerInfoByK8sApi(String namespace, String serviceName) throws Exception { - - logger.info("K8sAgentUtilService getServerInfoByK8sApi start."); - - List> 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 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; - } - -} \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/rms/onl/manage/inflow/inflow/InflowAdapterControlManController.java b/src/main/java/com/eactive/eai/rms/onl/manage/inflow/inflow/InflowAdapterControlManController.java index 37cbe36..01ea2b4 100644 --- a/src/main/java/com/eactive/eai/rms/onl/manage/inflow/inflow/InflowAdapterControlManController.java +++ b/src/main/java/com/eactive/eai/rms/onl/manage/inflow/inflow/InflowAdapterControlManController.java @@ -85,4 +85,12 @@ public class InflowAdapterControlManController extends OnlBaseAnnotationControll return new ModelAndView("jsonView", resultMap); } + @RequestMapping(value = "/onl/admin/inflow/inflowAdapterControlMan.json", params = "cmd=LIST_BUCKET_STATUS") + public ModelAndView getBucketStatus(HttpServletRequest request, HttpServletResponse response, String adapterId) { + List> statusList = service.getAdapterBucketStatus(adapterId); + Map resultMap = new HashMap<>(); + resultMap.put("servers", statusList); + return new ModelAndView("jsonView", resultMap); + } + } \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/rms/onl/manage/inflow/inflow/InflowClientControlManController.java b/src/main/java/com/eactive/eai/rms/onl/manage/inflow/inflow/InflowClientControlManController.java index a384d01..897414d 100644 --- a/src/main/java/com/eactive/eai/rms/onl/manage/inflow/inflow/InflowClientControlManController.java +++ b/src/main/java/com/eactive/eai/rms/onl/manage/inflow/inflow/InflowClientControlManController.java @@ -83,4 +83,12 @@ public class InflowClientControlManController extends OnlBaseAnnotationControlle return new ModelAndView("jsonView", resultMap); } + @RequestMapping(value = "/onl/admin/inflow/inflowClientControlMan.json", params = "cmd=LIST_BUCKET_STATUS") + public ModelAndView getBucketStatus(HttpServletRequest request, HttpServletResponse response, String clientId) { + List> statusList = service.getClientBucketStatus(clientId); + Map resultMap = new HashMap<>(); + resultMap.put("servers", statusList); + return new ModelAndView("jsonView", resultMap); + } + } \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/rms/onl/manage/inflow/inflow/InflowControlManService.java b/src/main/java/com/eactive/eai/rms/onl/manage/inflow/inflow/InflowControlManService.java index c5c6d19..4638120 100644 --- a/src/main/java/com/eactive/eai/rms/onl/manage/inflow/inflow/InflowControlManService.java +++ b/src/main/java/com/eactive/eai/rms/onl/manage/inflow/inflow/InflowControlManService.java @@ -1,12 +1,21 @@ package com.eactive.eai.rms.onl.manage.inflow.inflow; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Optional; +import org.apache.commons.lang3.StringUtils; +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.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; import com.eactive.eai.data.entity.onl.inflow.InflowControl; import com.eactive.eai.data.entity.onl.inflow.InflowControlId; @@ -14,10 +23,13 @@ import com.eactive.eai.rms.common.base.BaseService; import com.eactive.eai.rms.common.combo.ComboService; import com.eactive.eai.rms.data.entity.onl.inflow.InflowControlService; import com.eactive.eai.rms.data.entity.onl.inflow.InflowControlServiceDto; +import com.eactive.eai.rms.onl.common.service.EaiServerInfoService; @Service public class InflowControlManService extends BaseService { + private static final Logger logger = LoggerFactory.getLogger(InflowControlManService.class); + private static final String ADAPTER_TYPE_INFLOW = "01"; private static final String INTERFACE_TYPE_INFLOW = "02"; private static final String CLIENT_TYPE_INFLOW = "03"; @@ -32,6 +44,11 @@ public class InflowControlManService extends BaseService { @Autowired private InflowControlManMapper mapper; + @Autowired + private EaiServerInfoService eaiServerInfoService; + + private RestTemplate restTemplate = new RestTemplate(); + // 어댑터 유량제어 public Page selectAdapterList(Pageable pageable, String searchName) { Page dtoList = service.findAllForAdapter(pageable, searchName); @@ -123,6 +140,64 @@ public class InflowControlManService extends BaseService { InflowControlId id = toId(CLIENT_TYPE_INFLOW, name); service.findById(id).ifPresent(service::delete); } + + @SuppressWarnings("unchecked") + public List> getClientBucketStatus(String clientId) { + return getBucketStatus("client", clientId, "클라이언트가 로드되지 않음", "클라이언트 버킷 상태 조회 실패"); + } + + @SuppressWarnings("unchecked") + public List> getAdapterBucketStatus(String adapterId) { + return getBucketStatus("adapter", adapterId, "어댑터가 로드되지 않음", "어댑터 버킷 상태 조회 실패"); + } + + @SuppressWarnings("unchecked") + public List> getInterfaceBucketStatus(String interfaceId) { + return getBucketStatus("interface", interfaceId, "인터페이스가 로드되지 않음", "인터페이스 버킷 상태 조회 실패"); + } + + @SuppressWarnings("unchecked") + private List> getBucketStatus(String target, String targetId, String noDataMessage, String logPrefix) { + List> result = new ArrayList<>(); + List> serverList = eaiServerInfoService.getEaiServerIpList(); + + for (Map server : serverList) { + String serverName = server.get("EAISVRINSTNM"); + String serverIp = server.get("EAISVRIP"); + String serverPort = server.get("WEBSERVERPORT"); + + if (StringUtils.isEmpty(serverPort)) { + serverPort = server.get("EAISVRLSNPORT"); + } + + Map serverStatus = new HashMap<>(); + serverStatus.put("serverName", serverName); + serverStatus.put("serverIp", serverIp); + serverStatus.put("serverPort", serverPort); + + try { + String url = String.format("http://%s:%s/manage/inflow/%s/%s/bucket-status", + serverIp, serverPort, target, targetId); + ResponseEntity response = restTemplate.getForEntity(url, Map.class); + + if (response.getBody() != null && Boolean.TRUE.equals(response.getBody().get("success"))) { + serverStatus.put("status", "online"); + serverStatus.put("data", response.getBody().get("data")); + } else { + serverStatus.put("status", "no_data"); + serverStatus.put("message", noDataMessage); + } + } catch (Exception e) { + logger.warn("{} - server: {}, error: {}", logPrefix, serverName, e.getMessage()); + serverStatus.put("status", "offline"); + serverStatus.put("message", "서버 연결 실패"); + } + + result.add(serverStatus); + } + + return result; + } private InflowControlId toId(InflowControlManUI ui) { diff --git a/src/main/java/com/eactive/eai/rms/onl/manage/inflow/inflow/InflowInterfaceControlManController.java b/src/main/java/com/eactive/eai/rms/onl/manage/inflow/inflow/InflowInterfaceControlManController.java index e2eef98..a72d95c 100644 --- a/src/main/java/com/eactive/eai/rms/onl/manage/inflow/inflow/InflowInterfaceControlManController.java +++ b/src/main/java/com/eactive/eai/rms/onl/manage/inflow/inflow/InflowInterfaceControlManController.java @@ -83,4 +83,12 @@ public class InflowInterfaceControlManController extends OnlBaseAnnotationContro return new ModelAndView("jsonView", resultMap); } + @RequestMapping(value = "/onl/admin/inflow/inflowInterfaceControlMan.json", params = "cmd=LIST_BUCKET_STATUS") + public ModelAndView getBucketStatus(HttpServletRequest request, HttpServletResponse response, String interfaceId) { + List> statusList = service.getInterfaceBucketStatus(interfaceId); + Map resultMap = new HashMap<>(); + resultMap.put("servers", statusList); + return new ModelAndView("jsonView", resultMap); + } + } \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/rms/onl/transaction/apim/ApiInterfaceService.java b/src/main/java/com/eactive/eai/rms/onl/transaction/apim/ApiInterfaceService.java index 0c7b620..89b6bbb 100644 --- a/src/main/java/com/eactive/eai/rms/onl/transaction/apim/ApiInterfaceService.java +++ b/src/main/java/com/eactive/eai/rms/onl/transaction/apim/ApiInterfaceService.java @@ -565,7 +565,7 @@ public class ApiInterfaceService extends OnlBaseService { restOption.setMethod(outboundHttpMethod); restOption.setExtraPath(outboundRestPath); - String pathType = outboundRestPath.matches(".*\\{[_a-zA-Z]+\\}.*") ? "variableUrlRequest" : "simpleRequest"; + String pathType = outboundRestPath.matches(".*\\{.+\\}.*") ? "variableUrlRequest" : "simpleRequest"; restOption.setType(pathType); if (StringUtils.isNotBlank(contentType)) { diff --git a/src/main/java/com/eactive/eai/rms/onl/transaction/simpleinterface/SimpleInterfaceService.java b/src/main/java/com/eactive/eai/rms/onl/transaction/simpleinterface/SimpleInterfaceService.java index 3e415a1..ff70543 100644 --- a/src/main/java/com/eactive/eai/rms/onl/transaction/simpleinterface/SimpleInterfaceService.java +++ b/src/main/java/com/eactive/eai/rms/onl/transaction/simpleinterface/SimpleInterfaceService.java @@ -400,7 +400,7 @@ public class SimpleInterfaceService extends OnlBaseService { jsonObject.put("method", outboundHttpMethod); jsonObject.put("extraPath", outboundRestPath); - String pathType = outboundRestPath.matches(".*\\{[a-zA-Z]+\\}.*") ? "variableUrlRequest" : "simpleRequest"; + String pathType = outboundRestPath.matches(".*\\{.+\\}.*") ? "variableUrlRequest" : "simpleRequest"; jsonObject.put("type", pathType); restOption = jsonObject.toString(); return restOption; diff --git a/src/main/java/com/eactive/ext/kjb/statistics/ApiStatsAggregationController.java b/src/main/java/com/eactive/ext/kjb/statistics/ApiStatsAggregationController.java index a8b1df0..6d19e66 100644 --- a/src/main/java/com/eactive/ext/kjb/statistics/ApiStatsAggregationController.java +++ b/src/main/java/com/eactive/ext/kjb/statistics/ApiStatsAggregationController.java @@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; +import com.eactive.eai.rms.ext.djb.job.ApiStatsHourlyAggregationJob; import com.eactive.ext.kjb.statistics.job.DailyToMonthlyAggregationJob; import com.eactive.ext.kjb.statistics.job.HourlyToDailyAggregationJob; import com.eactive.ext.kjb.statistics.job.MonthlyToYearlyAggregationJob; @@ -33,12 +34,62 @@ public class ApiStatsAggregationController { private final HourlyToDailyAggregationJob hourlyToDailyAggregationJob; private final DailyToMonthlyAggregationJob dailyToMonthlyAggregationJob; private final MonthlyToYearlyAggregationJob monthlyToYearlyAggregationJob; + + private final ApiStatsHourlyAggregationJob apiStatsHourlyAggregationJob; @GetMapping(value = "/onl/kjb/statistics/apiStatsAggregationMan.view") public String view() { return "/onl/kjb/statistics/apiStatsAggregationMan"; } + /** + * 거래로그→시간별 통계 집계 수동 실행 + * @param targetDate 집계 대상 날짜 (yyyyMMdd 형식, 예: 20250120) + * @return 처리 결과 + */ + @PostMapping(value = "/onl/kjb/statistics/apiStatsAggregationMan.json", params = "cmd=AGGREGATION_HOUR") + public ResponseEntity> executeAggregationHourly( + @RequestParam(required = false) String targetDate) { + + Map result = new HashMap<>(); + + try { + // 날짜 입력 검증 + if (targetDate == null || targetDate.trim().isEmpty()) { + log.error("targetDate 미입력"); + result.put("success", false); + result.put("message", "대상 날짜를 입력하세요. (예: 20250120)"); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(result); + } + + LocalDate date; + try { + date = LocalDate.parse(targetDate, DateTimeFormatter.ofPattern("yyyyMMdd")); + } catch (DateTimeParseException e) { + log.error("잘못된 날짜 형식: {}", targetDate); + result.put("success", false); + result.put("message", "잘못된 날짜 형식입니다. yyyyMMdd 형식으로 입력하세요. (예: 20250120)"); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(result); + } + + log.info("거래로그→시간별 통계 집계 수동 실행 시작. targetDate: {}", date); + int count = apiStatsHourlyAggregationJob.executeManual(date); + + result.put("success", true); + result.put("message", "거래로그→시간별 통계 집계가 완료되었습니다."); + result.put("targetDate", date.format(DateTimeFormatter.ofPattern("yyyyMMdd"))); + result.put("processedCount", count); + + return ResponseEntity.ok(result); + + } catch (Exception e) { + log.error("거래로그→시간별 통계 집계 실행 중 오류 발생", e); + result.put("success", false); + result.put("message", "집계 실행 중 오류가 발생했습니다: " + e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result); + } + } + /** * 시간별→일별 통계 집계 수동 실행 * @param targetDate 집계 대상 날짜 (yyyyMMdd 형식, 예: 20250120) diff --git a/src/main/java/com/eactive/ext/kjb/statistics/job/DailyToMonthlyAggregationJob.java b/src/main/java/com/eactive/ext/kjb/statistics/job/DailyToMonthlyAggregationJob.java index cb25000..c6e56ba 100644 --- a/src/main/java/com/eactive/ext/kjb/statistics/job/DailyToMonthlyAggregationJob.java +++ b/src/main/java/com/eactive/ext/kjb/statistics/job/DailyToMonthlyAggregationJob.java @@ -138,9 +138,9 @@ public class DailyToMonthlyAggregationJob implements Job { qd.seq900SystemErrCnt.sum().as("seq900SystemErrCnt"), qd.seq900BizErrCnt.sum().as("seq900BizErrCnt"), Expressions.cases() - .when(qd.successCnt.sum().gt(0)) - .then(qd.avgRespTime.multiply(qd.successCnt).sum() - .divide(qd.successCnt.sum()).castToNum(BigDecimal.class)) + .when(qd.totalCnt.sum().gt(0)) + .then(qd.avgRespTime.multiply(qd.totalCnt).sum() + .divide(qd.totalCnt.sum()).castToNum(BigDecimal.class)) .otherwise((BigDecimal) null) .as("avgRespTime"), qd.minRespTime.min().castToNum(BigDecimal.class).as("minRespTime"), diff --git a/src/main/java/com/eactive/ext/kjb/statistics/job/HourlyToDailyAggregationJob.java b/src/main/java/com/eactive/ext/kjb/statistics/job/HourlyToDailyAggregationJob.java index a71b783..97729b7 100644 --- a/src/main/java/com/eactive/ext/kjb/statistics/job/HourlyToDailyAggregationJob.java +++ b/src/main/java/com/eactive/ext/kjb/statistics/job/HourlyToDailyAggregationJob.java @@ -133,9 +133,9 @@ public class HourlyToDailyAggregationJob implements Job { qh.seq900SystemErrCnt.sum().as("seq900SystemErrCnt"), qh.seq900BizErrCnt.sum().as("seq900BizErrCnt"), Expressions.cases() - .when(qh.successCnt.sum().gt(0)) - .then(qh.avgRespTime.multiply(qh.successCnt).sum() - .divide(qh.successCnt.sum()).castToNum(BigDecimal.class)) + .when(qh.totalCnt.sum().gt(0)) + .then(qh.avgRespTime.multiply(qh.totalCnt).sum() + .divide(qh.totalCnt.sum()).castToNum(BigDecimal.class)) .otherwise((BigDecimal) null) .as("avgRespTime"), qh.minRespTime.min().castToNum(BigDecimal.class).as("minRespTime"), diff --git a/src/main/java/com/eactive/ext/kjb/statistics/job/MonthlyToYearlyAggregationJob.java b/src/main/java/com/eactive/ext/kjb/statistics/job/MonthlyToYearlyAggregationJob.java index c3fe895..a7ebf6c 100644 --- a/src/main/java/com/eactive/ext/kjb/statistics/job/MonthlyToYearlyAggregationJob.java +++ b/src/main/java/com/eactive/ext/kjb/statistics/job/MonthlyToYearlyAggregationJob.java @@ -136,9 +136,9 @@ public class MonthlyToYearlyAggregationJob implements Job { qm.seq900SystemErrCnt.sum().as("seq900SystemErrCnt"), qm.seq900BizErrCnt.sum().as("seq900BizErrCnt"), Expressions.cases() - .when(qm.successCnt.sum().gt(0)) - .then(qm.avgRespTime.multiply(qm.successCnt).sum() - .divide(qm.successCnt.sum()).castToNum(BigDecimal.class)) + .when(qm.totalCnt.sum().gt(0)) + .then(qm.avgRespTime.multiply(qm.totalCnt).sum() + .divide(qm.totalCnt.sum()).castToNum(BigDecimal.class)) .otherwise((BigDecimal) null) .as("avgRespTime"), qm.minRespTime.min().castToNum(BigDecimal.class).as("minRespTime"), diff --git a/src/main/java/com/eactive/ext/kjb/statistics/service/ApiStatsExcelExportService.java b/src/main/java/com/eactive/ext/kjb/statistics/service/ApiStatsExcelExportService.java index a97c2fe..ec92563 100644 --- a/src/main/java/com/eactive/ext/kjb/statistics/service/ApiStatsExcelExportService.java +++ b/src/main/java/com/eactive/ext/kjb/statistics/service/ApiStatsExcelExportService.java @@ -36,9 +36,8 @@ public class ApiStatsExcelExportService { private static final String[] COLUMN_HEADERS = { "통계시간", "API명", "인스턴스", "업무구분", "클라이언트ID", "Inbound Adapter", "Outbound Adapter", - "총건수", "성공건수", "Timeout건수", "시스템오류건수", "업무오류건수", - "Seq900 Timeout", "Seq900 시스템오류", "Seq900 업무오류", - "평균응답시간", "최소응답시간", "최대응답시간", "P50응답시간", "P95응답시간" + "총건수", "성공건수", "Timeout건수", "시스템오류건수", + "평균응답시간", "최소응답시간", "최대응답시간" }; /** @@ -193,17 +192,13 @@ public class ApiStatsExcelExportService { createLongCell(row, colNum++, data.getSuccessCnt(), numberStyle); createLongCell(row, colNum++, data.getTimeoutCnt(), numberStyle); createLongCell(row, colNum++, data.getSystemErrCnt(), numberStyle); - createLongCell(row, colNum++, data.getBizErrCnt(), numberStyle); - createLongCell(row, colNum++, data.getSeq900TimeoutCnt(), numberStyle); - createLongCell(row, colNum++, data.getSeq900SystemErrCnt(), numberStyle); - createLongCell(row, colNum++, data.getSeq900BizErrCnt(), numberStyle); + // 소수점 컬럼 (15-19) createDecimalCell(row, colNum++, data.getAvgRespTime(), decimalStyle); createDecimalCell(row, colNum++, data.getMinRespTime(), decimalStyle); createDecimalCell(row, colNum++, data.getMaxRespTime(), decimalStyle); - createDecimalCell(row, colNum++, data.getP50RespTime(), decimalStyle); - createDecimalCell(row, colNum++, data.getP95RespTime(), decimalStyle); + } /** diff --git a/src/main/java/com/eactive/ext/kjb/statistics/ui/ApiStatsSearch.java b/src/main/java/com/eactive/ext/kjb/statistics/ui/ApiStatsSearch.java index 96c0e58..2f354fd 100644 --- a/src/main/java/com/eactive/ext/kjb/statistics/ui/ApiStatsSearch.java +++ b/src/main/java/com/eactive/ext/kjb/statistics/ui/ApiStatsSearch.java @@ -18,6 +18,8 @@ public class ApiStatsSearch { private String searchClientId; private String searchInboundAdapter; private String searchOutboundAdapter; + private String searchOrgName; + private String searchType; /** 파싱된 시작시간 */ private LocalDateTime parsedStartDateTime; diff --git a/src/main/java/com/eactive/ext/kjb/statistics/ui/ApiStatsUI.java b/src/main/java/com/eactive/ext/kjb/statistics/ui/ApiStatsUI.java index 19e0368..83ee405 100644 --- a/src/main/java/com/eactive/ext/kjb/statistics/ui/ApiStatsUI.java +++ b/src/main/java/com/eactive/ext/kjb/statistics/ui/ApiStatsUI.java @@ -14,6 +14,7 @@ public class ApiStatsUI { private String gwInstanceId; private String bizDivCode; private String clientId; + private String orgName; private String inboundAdapter; private String outboundAdapter; @@ -22,15 +23,13 @@ public class ApiStatsUI { private Long successCnt; private Long timeoutCnt; private Long systemErrCnt; - private Long bizErrCnt; - private Long seq900TimeoutCnt; - private Long seq900SystemErrCnt; - private Long seq900BizErrCnt; // 응답시간 메트릭 private BigDecimal avgRespTime; private BigDecimal minRespTime; private BigDecimal maxRespTime; - private BigDecimal p50RespTime; - private BigDecimal p95RespTime; + + //성공율,실패율 + private BigDecimal successRate; + private BigDecimal failRate; } diff --git a/src/main/resources/com/eactive/eai/rms/bap/manage/common/ServerMan-oracle.xml b/src/main/resources/com/eactive/eai/rms/bap/manage/common/ServerMan-oracle.xml index 3bf0f0c..eed9c57 100644 --- a/src/main/resources/com/eactive/eai/rms/bap/manage/common/ServerMan-oracle.xml +++ b/src/main/resources/com/eactive/eai/rms/bap/manage/common/ServerMan-oracle.xml @@ -23,7 +23,6 @@ FLOVRSEVRNAME AS "FAILOVERSVRNM", HOSTNAME AS "HOSTNAME" FROM $schemaId$.TSEAIBP03 - WHERE ROWNUM = 1 \ No newline at end of file diff --git a/src/main/resources/hotswap-agent.properties b/src/main/resources/hotswap-agent.properties index cc2d7ce..fa14761 100644 --- a/src/main/resources/hotswap-agent.properties +++ b/src/main/resources/hotswap-agent.properties @@ -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 diff --git a/src/main/resources/logback-dev.xml b/src/main/resources/logback-dev.xml index 5e5b9c3..aace92d 100644 --- a/src/main/resources/logback-dev.xml +++ b/src/main/resources/logback-dev.xml @@ -4,7 +4,7 @@ - + diff --git a/src/main/resources/logback-local.xml b/src/main/resources/logback-local.xml index 63b833d..00b1dd8 100644 --- a/src/main/resources/logback-local.xml +++ b/src/main/resources/logback-local.xml @@ -4,7 +4,7 @@ - + diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 99ddf16..7c40395 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -4,7 +4,7 @@ - + diff --git a/작업내역_20260506_eapim-admin_feature-crypto-module.md b/작업내역_20260506_eapim-admin_feature-crypto-module.md index e69b861..7336a9d 100644 --- a/작업내역_20260506_eapim-admin_feature-crypto-module.md +++ b/작업내역_20260506_eapim-admin_feature-crypto-module.md @@ -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) diff --git a/작업내역_20260507_eapim-admin_feature-crypto-module.md b/작업내역_20260507_eapim-admin_feature-crypto-module.md index 5628e3d..9622ffd 100644 --- a/작업내역_20260507_eapim-admin_feature-crypto-module.md +++ b/작업내역_20260507_eapim-admin_feature-crypto-module.md @@ -19,10 +19,10 @@ | 항목 | 내용 | |---|---| -| `EMSADM.CRYPTO_MODULE_CONFIG` 테이블 컬럼 추가 | 기존 DDL에서 누락된 `MODIFIED_BY`, `MODIFIED_AT` 컬럼 추가 | +| `EMSAPP.CRYPTO_MODULE_CONFIG` 테이블 컬럼 추가 | 기존 DDL에서 누락된 `MODIFIED_BY`, `MODIFIED_AT` 컬럼 추가 | ```sql -ALTER TABLE EMSADM.CRYPTO_MODULE_CONFIG +ALTER TABLE EMSAPP.CRYPTO_MODULE_CONFIG ADD ( MODIFIED_BY VARCHAR2(50), MODIFIED_AT TIMESTAMP @@ -31,7 +31,7 @@ ADD ( > 전체 DDL (신규 환경용) ```sql -CREATE TABLE EMSADM.CRYPTO_MODULE_CONFIG ( +CREATE TABLE EMSAPP.CRYPTO_MODULE_CONFIG ( CRYPTO_ID VARCHAR2(36) NOT NULL, CRYPTO_NAME VARCHAR2(100) NOT NULL, CRYPTO_DESC VARCHAR2(500),