Compare commits
112 Commits
c6632eac59
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
115e403497 | ||
|
|
b20a4edfdd | ||
|
|
6c318ebeae | ||
|
|
17d9f3cb77 | ||
|
|
e1908b4b7a | ||
|
|
d45720d3e4 | ||
|
|
9a8f716ec7 | ||
|
|
88d94e8b83 | ||
|
|
c9701a5a94 | ||
|
|
91c34a2fb3 | ||
|
|
bb782e1d38 | ||
|
|
ac432a95dd | ||
|
|
a58af8178a | ||
|
|
a9d2f920e0 | ||
|
|
459895ed40 | ||
|
|
072a31d883 | ||
|
|
69204cb6dd | ||
|
|
82c087e2da | ||
|
|
693ddea18d | ||
|
|
e6bcfe8934 | ||
|
|
2dc0c19c5a | ||
|
|
a85a5e8d33 | ||
|
|
a6eb4c4fe5 | ||
|
|
f9b96bf7ab | ||
|
|
1efd8617a7 | ||
|
|
cfee4e381e | ||
|
|
cfd074163b | ||
|
|
cd9f597297 | ||
|
|
1e9b8506d0 | ||
|
|
2809f1fd3a | ||
|
|
14cb280b0e | ||
|
|
c791929c9e | ||
|
|
35ec9d0730 | ||
|
|
f7ed8e8788 | ||
|
|
94b7c1a395 | ||
|
|
5894b23095 | ||
|
|
dc8eaaa17b | ||
|
|
d857069a3c | ||
|
|
658b761778 | ||
|
|
b66a708757 | ||
|
|
d5f644e666 | ||
|
|
afd9d57523 | ||
|
|
69ac0ab1df | ||
|
|
23cafbf8e2 | ||
|
|
ba65417119 | ||
|
|
e73a006bac | ||
|
|
309614a319 | ||
|
|
c5a36814e2 | ||
|
|
561520778e | ||
|
|
a362fe69d9 | ||
|
|
8810126185 | ||
|
|
ffe5777fea | ||
|
|
2fbf805afb | ||
|
|
bb8020b99c | ||
|
|
182d50a373 | ||
|
|
9fb3d28d52 | ||
| 0feb3e9d26 | |||
|
|
a19f1d75b9 | ||
|
|
fa5335131e | ||
|
|
52ae31d853 | ||
|
|
d73e597543 | ||
|
|
4986f9801c | ||
|
|
400fa3b6c1 | ||
|
|
90829432c4 | ||
|
|
bce603c3b6 | ||
|
|
40ca961239 | ||
|
|
0085ec6919 | ||
|
|
64c422378f | ||
|
|
d2fb804e5c | ||
|
|
fdd6c6b99a | ||
|
|
f25b6252d1 | ||
|
|
69c1cb30af | ||
|
|
e998127fa5 | ||
|
|
9cdf313a77 | ||
|
|
870412f0c7 | ||
|
|
d5a07cd846 | ||
|
|
6d6eaab742 | ||
|
|
17524c5887 | ||
|
|
ebef388893 | ||
|
|
eac481da41 | ||
|
|
0122de84aa | ||
|
|
bb98eac44c | ||
|
|
6060eb9f93 | ||
|
|
7737ac8905 | ||
|
|
de2d1061aa | ||
|
|
35fa033a1c | ||
|
|
3216148ee2 | ||
|
|
fbae7618eb | ||
|
|
c390b89250 | ||
|
|
b4ffac0cd5 | ||
|
|
6ec809fb65 | ||
|
|
ebaff567c6 | ||
|
|
c0069c2419 | ||
|
|
9f347ef9de | ||
|
|
91b2b67d85 | ||
|
|
b8fd9492c6 | ||
|
|
406e3c79b7 | ||
|
|
8bd9b9e922 | ||
|
|
0816c0ff0c | ||
|
|
59f9a8eaad | ||
|
|
a420c46c89 | ||
|
|
f70ee854ec | ||
|
|
6e65228fb9 | ||
|
|
a8ca65657a | ||
|
|
e66e97ecc3 | ||
|
|
ac73a86979 | ||
|
|
1411f39ffc | ||
|
|
1d69bd914e | ||
|
|
d8a8501c4e | ||
|
|
f38633fa9e | ||
|
|
ead0cb4375 | ||
|
|
b0a0f38691 |
@@ -33,7 +33,11 @@
|
||||
* 처리계 UI ↔ 처리계 AP 구간: `HTTPS` / `SSV`를 사용하며, FW에서 x-api를 통해 SSV↔DTO 변환을 수행한다.
|
||||
|
||||
* 신한라이프 표준 로그 기준 (Logback 설정):
|
||||
* **로그 생성 경로:** `/swlog/어플리케이션명(모듈명)/코드명/` (예: `/swlog/dap-gateway/A01/`)
|
||||
* **로그 생성 경로:** `/swlog/어플리케이션명(모듈명)/코드명/` (예: `/swlog/dat-gateway/A01/`)
|
||||
* **로그 네이밍 규칙:** `${HOSTNAME}_코드명_yyyyMMdd.log` (예: `${HOSTNAME}_A01_20260722.log`)
|
||||
* **기본 로그 구분 코드:** 시스템 운영기록 가동기록의 경우 `A01`을 기본으로 사용한다.
|
||||
* **호스트명 동적 할당:** Logback 설정 시 `<property name="HOSTNAME" value="${HOSTNAME}" />` 를 선언하여 사용한다.
|
||||
|
||||
* 프로젝트 작업 범위 및 툴 연동 규칙:
|
||||
* 현재 `axhub-backend-main` 워크스페이스 내에서는 **오직 `dat-gateway` 모듈만 수정**한다.
|
||||
* 툴(Tool) 연동 개발 작업은 반드시 별도의 워크스페이스인 **`C:\eGovFrameDev-4.3.1-64bit\workspace-egov\dat-was-datmt`** 폴더에서 수행한다.
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
apt-get update
|
||||
apt-get install -y nodejs git openjdk-21-jdk rsync
|
||||
elif command -v apk &> /dev/null; then
|
||||
apk add --no-cache nodejs git openjdk21 rsync
|
||||
apk add --no-cache nodejs git openjdk21 rsync docker-cli docker-cli-compose
|
||||
fi
|
||||
|
||||
- name: Checkout Code
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -122,3 +122,6 @@ ehthumbs.db
|
||||
# ===== ?섍꼍?ㅼ젙 (誘쇨컧?뺣낫 遺꾨━ ?? =====
|
||||
# application-local.yml
|
||||
# application-secret.yml
|
||||
|
||||
# ===== Generated Sources =====
|
||||
**/src/main/generated/
|
||||
|
||||
BIN
.gradle/8.14.3/checksums/checksums.lock
Normal file
BIN
.gradle/8.14.3/checksums/checksums.lock
Normal file
Binary file not shown.
BIN
.gradle/8.14.3/checksums/md5-checksums.bin
Normal file
BIN
.gradle/8.14.3/checksums/md5-checksums.bin
Normal file
Binary file not shown.
BIN
.gradle/8.14.3/checksums/sha1-checksums.bin
Normal file
BIN
.gradle/8.14.3/checksums/sha1-checksums.bin
Normal file
Binary file not shown.
BIN
.gradle/8.14.3/checksums/sha256-checksums.bin
Normal file
BIN
.gradle/8.14.3/checksums/sha256-checksums.bin
Normal file
Binary file not shown.
BIN
.gradle/8.14.3/checksums/sha512-checksums.bin
Normal file
BIN
.gradle/8.14.3/checksums/sha512-checksums.bin
Normal file
Binary file not shown.
BIN
.gradle/8.14.3/executionHistory/executionHistory.bin
Normal file
BIN
.gradle/8.14.3/executionHistory/executionHistory.bin
Normal file
Binary file not shown.
BIN
.gradle/8.14.3/executionHistory/executionHistory.lock
Normal file
BIN
.gradle/8.14.3/executionHistory/executionHistory.lock
Normal file
Binary file not shown.
BIN
.gradle/8.14.3/fileChanges/last-build.bin
Normal file
BIN
.gradle/8.14.3/fileChanges/last-build.bin
Normal file
Binary file not shown.
BIN
.gradle/8.14.3/fileHashes/fileHashes.bin
Normal file
BIN
.gradle/8.14.3/fileHashes/fileHashes.bin
Normal file
Binary file not shown.
BIN
.gradle/8.14.3/fileHashes/fileHashes.lock
Normal file
BIN
.gradle/8.14.3/fileHashes/fileHashes.lock
Normal file
Binary file not shown.
BIN
.gradle/8.14.3/fileHashes/resourceHashesCache.bin
Normal file
BIN
.gradle/8.14.3/fileHashes/resourceHashesCache.bin
Normal file
Binary file not shown.
0
.gradle/8.14.3/gc.properties
Normal file
0
.gradle/8.14.3/gc.properties
Normal file
BIN
.gradle/9.6.1/checksums/checksums.lock
Normal file
BIN
.gradle/9.6.1/checksums/checksums.lock
Normal file
Binary file not shown.
BIN
.gradle/9.6.1/checksums/md5-checksums.bin
Normal file
BIN
.gradle/9.6.1/checksums/md5-checksums.bin
Normal file
Binary file not shown.
BIN
.gradle/9.6.1/checksums/sha1-checksums.bin
Normal file
BIN
.gradle/9.6.1/checksums/sha1-checksums.bin
Normal file
Binary file not shown.
BIN
.gradle/9.6.1/executionHistory/executionHistory.bin
Normal file
BIN
.gradle/9.6.1/executionHistory/executionHistory.bin
Normal file
Binary file not shown.
BIN
.gradle/9.6.1/executionHistory/executionHistory.lock
Normal file
BIN
.gradle/9.6.1/executionHistory/executionHistory.lock
Normal file
Binary file not shown.
BIN
.gradle/9.6.1/fileChanges/last-build.bin
Normal file
BIN
.gradle/9.6.1/fileChanges/last-build.bin
Normal file
Binary file not shown.
BIN
.gradle/9.6.1/fileHashes/fileHashes.bin
Normal file
BIN
.gradle/9.6.1/fileHashes/fileHashes.bin
Normal file
Binary file not shown.
BIN
.gradle/9.6.1/fileHashes/fileHashes.lock
Normal file
BIN
.gradle/9.6.1/fileHashes/fileHashes.lock
Normal file
Binary file not shown.
BIN
.gradle/9.6.1/fileHashes/resourceHashesCache.bin
Normal file
BIN
.gradle/9.6.1/fileHashes/resourceHashesCache.bin
Normal file
Binary file not shown.
0
.gradle/9.6.1/gc.properties
Normal file
0
.gradle/9.6.1/gc.properties
Normal file
BIN
.gradle/buildOutputCleanup/buildOutputCleanup.lock
Normal file
BIN
.gradle/buildOutputCleanup/buildOutputCleanup.lock
Normal file
Binary file not shown.
2
.gradle/buildOutputCleanup/cache.properties
Normal file
2
.gradle/buildOutputCleanup/cache.properties
Normal file
@@ -0,0 +1,2 @@
|
||||
#Wed Jul 29 17:05:55 KST 2026
|
||||
gradle.version=8.14.3
|
||||
BIN
.gradle/buildOutputCleanup/outputFiles.bin
Normal file
BIN
.gradle/buildOutputCleanup/outputFiles.bin
Normal file
Binary file not shown.
BIN
.gradle/file-system.probe
Normal file
BIN
.gradle/file-system.probe
Normal file
Binary file not shown.
0
.gradle/vcs-1/gc.properties
Normal file
0
.gradle/vcs-1/gc.properties
Normal file
10
.idea/.gitignore
generated
vendored
Normal file
10
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
# 디폴트 무시된 파일
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 에디터 기반 HTTP 클라이언트 요청
|
||||
/httpRequests/
|
||||
# 쿼리 파일을 포함한 무시된 디폴트 폴더
|
||||
/queries/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
1
.idea/.name
generated
Normal file
1
.idea/.name
generated
Normal file
@@ -0,0 +1 @@
|
||||
dap-admin
|
||||
153
.idea/compiler.xml
generated
Normal file
153
.idea/compiler.xml
generated
Normal file
@@ -0,0 +1,153 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<annotationProcessing>
|
||||
<profile default="true" name="Default" enabled="true" />
|
||||
<profile name="Gradle Imported" enabled="true">
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<option name="mapstruct.defaultComponentModel" value="spring" />
|
||||
<processorPath useClasspath="false">
|
||||
<entry name="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.projectlombok/lombok/1.18.42/8365263844ebb62398e0dc33057ba10ba472d3b8/lombok-1.18.42.jar" />
|
||||
<entry name="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.mapstruct/mapstruct-processor/1.6.3/52e345c907fbf173b376586c7f8b131d53fa5867/mapstruct-processor-1.6.3.jar" />
|
||||
</processorPath>
|
||||
<module name="dap-admin.dat-was-pro.main" />
|
||||
<module name="dap-admin.dat-was-cus.main" />
|
||||
<module name="dap-admin.dat-was-sys.main" />
|
||||
<module name="dap-admin.dat-was-lib.main" />
|
||||
<module name="dap-admin.dap-tool-report.main" />
|
||||
<module name="dap-admin.dat-gateway.main" />
|
||||
<module name="dap-admin.dat-was-sal.main" />
|
||||
</profile>
|
||||
<profile name="Gradle Imported" enabled="true">
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<option name="mapstruct.defaultComponentModel" value="spring" />
|
||||
<processorPath useClasspath="false">
|
||||
<entry name="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.projectlombok/lombok/1.18.42/8365263844ebb62398e0dc33057ba10ba472d3b8/lombok-1.18.42.jar" />
|
||||
</processorPath>
|
||||
<module name="dap-admin.dat-was-cus.test" />
|
||||
<module name="dap-admin.dat-was-sal.test" />
|
||||
<module name="dap-admin.dap-tool-report.test" />
|
||||
<module name="dap-admin.dat-was-sys.test" />
|
||||
<module name="dap-admin.dat-gateway.test" />
|
||||
<module name="dap-admin.dat-was-pro.test" />
|
||||
<module name="dap-admin.dat-was-lib.test" />
|
||||
</profile>
|
||||
</annotationProcessing>
|
||||
<bytecodeTargetLevel target="21" />
|
||||
</component>
|
||||
<component name="JavacSettings">
|
||||
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
|
||||
<module name="axhub-admin.axhub-common" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-common.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-common.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-gateway" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-gateway.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-gateway.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-core" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-core.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-core.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-email" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-email.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-email.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-hr" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-hr.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-hr.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-other" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-other.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-other.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-payment" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-payment.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-payment.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-sms" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-sms.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-sms.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin" options="-parameters" />
|
||||
<module name="dap-admin.dap-common" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-common.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-common.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-core" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-core.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-core.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-email" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-email.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-email.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-hr" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-hr.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-hr.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-oth" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-oth.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-oth.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-other" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-other.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-other.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-payment" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-payment.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-payment.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-report" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-report.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-report.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-sms" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-sms.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-sms.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-gateway" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-gateway.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-gateway.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-tool-report" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-tool-report.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-tool-report.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-cus" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-cus.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-cus.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-lib" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-lib.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-lib.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-oth" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-oth.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-oth.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-pro" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-pro.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-pro.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-sal" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-sal.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-sal.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-sms" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-sms.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-sms.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-sys" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-sys.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-sys.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.main" options="-parameters" />
|
||||
<module name="dap-admin.test" options="-parameters" />
|
||||
<module name="dat-admin.dat-gateway" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-gateway.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-gateway.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-tool-report" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-tool-report.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-tool-report.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-was-cus" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-was-cus.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-was-cus.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-was-lib" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-was-lib.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-was-lib.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-was-pro" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-was-pro.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-was-pro.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-was-sal" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-was-sal.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-was-sal.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-was-sys" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-was-sys.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-was-sys.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="io.shinhanlife.dat-admin.dap-tool-core" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="io.shinhanlife.dat-admin.dap-tool-core.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="io.shinhanlife.dat-admin.dap-tool-core.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="io.shinhanlife.dat-admin.dap-tool-oth" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="io.shinhanlife.dat-admin.dap-tool-oth.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="io.shinhanlife.dat-admin.dap-tool-oth.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="io.shinhanlife.dat-admin.dap-tool-sms" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="io.shinhanlife.dat-admin.dap-tool-sms.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="io.shinhanlife.dat-admin.dap-tool-sms.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
16
.idea/dataSources.local.xml
generated
Normal file
16
.idea/dataSources.local.xml
generated
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="dataSourceStorageLocal" created-in="IU-261.25134.95">
|
||||
<data-source name="MCP_Redis" uuid="e04b0a95-55ed-4687-8007-98dd44e0ab05">
|
||||
<database-info product="Redis Standalone" version="8.10.0" jdbc-version="4.2" driver-name="Redis JDBC Driver" driver-version="1.5" dbms="REDIS" exact-version="8.10.0" exact-driver-version="1.5" />
|
||||
<case-sensitivity plain-identifiers="mixed" quoted-identifiers="mixed" />
|
||||
<secret-storage>master_key</secret-storage>
|
||||
<auth-provider>no-auth</auth-provider>
|
||||
<schema-mapping>
|
||||
<introspection-scope>
|
||||
<node kind="schema" qname="@" />
|
||||
</introspection-scope>
|
||||
</schema-mapping>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
||||
17
.idea/dataSources.xml
generated
Normal file
17
.idea/dataSources.xml
generated
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="MCP_Redis" uuid="e04b0a95-55ed-4687-8007-98dd44e0ab05">
|
||||
<driver-ref>redis</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>jdbc.RedisDriver</jdbc-driver>
|
||||
<jdbc-url>jdbc:redis://localhost:6379/0</jdbc-url>
|
||||
<jdbc-additional-properties>
|
||||
<property name="com.intellij.clouds.kubernetes.db.host.port" />
|
||||
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
|
||||
<property name="com.intellij.clouds.kubernetes.db.container.port" />
|
||||
</jdbc-additional-properties>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
||||
25
.idea/dataSources/data_sources_history.xml
generated
Normal file
25
.idea/dataSources/data_sources_history.xml
generated
Normal file
@@ -0,0 +1,25 @@
|
||||
<DataSourcesHistory>
|
||||
<DataSourceFromHistory isRemovedFromProject="false">
|
||||
<data-source source="LOCAL" name="MCP_Redis" uuid="e04b0a95-55ed-4687-8007-98dd44e0ab05">
|
||||
<database-info product="Redis Standalone" version="8.10.0" jdbc-version="4.2" driver-name="Redis JDBC Driver" driver-version="1.5" dbms="REDIS" exact-version="8.10.0" exact-driver-version="1.5" />
|
||||
<case-sensitivity plain-identifiers="mixed" quoted-identifiers="mixed" />
|
||||
<driver-ref>redis</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>jdbc.RedisDriver</jdbc-driver>
|
||||
<jdbc-url>jdbc:redis://localhost:6379/0</jdbc-url>
|
||||
<jdbc-additional-properties>
|
||||
<property name="com.intellij.clouds.kubernetes.db.host.port" />
|
||||
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
|
||||
<property name="com.intellij.clouds.kubernetes.db.container.port" />
|
||||
</jdbc-additional-properties>
|
||||
<secret-storage>master_key</secret-storage>
|
||||
<auth-provider>no-auth</auth-provider>
|
||||
<schema-mapping>
|
||||
<introspection-scope>
|
||||
<node kind="schema" qname="@" />
|
||||
</introspection-scope>
|
||||
</schema-mapping>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
</DataSourceFromHistory>
|
||||
</DataSourcesHistory>
|
||||
6
.idea/encodings.xml
generated
Normal file
6
.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
||||
23
.idea/gradle.xml
generated
Normal file
23
.idea/gradle.xml
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/dat-gateway" />
|
||||
<option value="$PROJECT_DIR$/dap-tool-report" />
|
||||
<option value="$PROJECT_DIR$/dat-was-cus" />
|
||||
<option value="$PROJECT_DIR$/dat-was-lib" />
|
||||
<option value="$PROJECT_DIR$/dat-was-pro" />
|
||||
<option value="$PROJECT_DIR$/dat-was-sal" />
|
||||
<option value="$PROJECT_DIR$/dat-was-sys" />
|
||||
</set>
|
||||
</option>
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
20
.idea/jarRepositories.xml
generated
Normal file
20
.idea/jarRepositories.xml
generated
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteRepositoriesConfiguration">
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Central Repository" />
|
||||
<option name="url" value="https://repo.maven.apache.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Maven Central repository" />
|
||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="jboss.community" />
|
||||
<option name="name" value="JBoss Community repository" />
|
||||
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
||||
17
.idea/misc.xml
generated
Normal file
17
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<file type="web" url="file://$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="21" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
17
.idea/modules.xml
generated
Normal file
17
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/dap-tool-core/dap-admin.dap-tool-core.iml" filepath="$PROJECT_DIR$/.idea/modules/dap-tool-core/dap-admin.dap-tool-core.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/dap-tool-oth/dap-admin.dap-tool-oth.iml" filepath="$PROJECT_DIR$/.idea/modules/dap-tool-oth/dap-admin.dap-tool-oth.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/dap-tool-oth/dap-admin.dap-tool-oth.test.iml" filepath="$PROJECT_DIR$/.idea/modules/dap-tool-oth/dap-admin.dap-tool-oth.test.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/dap-tool-report/dap-admin.dap-tool-report.main.iml" filepath="$PROJECT_DIR$/.idea/modules/dap-tool-report/dap-admin.dap-tool-report.main.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/dap-tool-sms/dap-admin.dap-tool-sms.iml" filepath="$PROJECT_DIR$/.idea/modules/dap-tool-sms/dap-admin.dap-tool-sms.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/dap-tool-sms/dap-admin.dap-tool-sms.test.iml" filepath="$PROJECT_DIR$/.idea/modules/dap-tool-sms/dap-admin.dap-tool-sms.test.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/dat-gateway/dap-admin.dat-gateway.main.iml" filepath="$PROJECT_DIR$/.idea/modules/dat-gateway/dap-admin.dat-gateway.main.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/dat-gateway/dap-admin.dat-gateway.test.iml" filepath="$PROJECT_DIR$/.idea/modules/dat-gateway/dap-admin.dat-gateway.test.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/dat-was-lib/dap-admin.dat-was-lib.main.iml" filepath="$PROJECT_DIR$/.idea/modules/dat-was-lib/dap-admin.dat-was-lib.main.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/dat-was-lib/dap-admin.dat-was-lib.test.iml" filepath="$PROJECT_DIR$/.idea/modules/dat-was-lib/dap-admin.dat-was-lib.test.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
8
.idea/modules/dap-tool-core/dap-admin.dap-tool-core.main.iml
generated
Normal file
8
.idea/modules/dap-tool-core/dap-admin.dap-tool-core.main.iml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module version="4">
|
||||
<component name="AdditionalModuleElements">
|
||||
<content url="file://$MODULE_DIR$/../../../dap-tool-core/build/generated/sources/annotationProcessor/java/main">
|
||||
<sourceFolder url="file://$MODULE_DIR$/../../../dap-tool-core/build/generated/sources/annotationProcessor/java/main" isTestSource="false" generated="true" />
|
||||
</content>
|
||||
</component>
|
||||
</module>
|
||||
8
.idea/modules/dap-tool-core/dap-admin.dap-tool-core.test.iml
generated
Normal file
8
.idea/modules/dap-tool-core/dap-admin.dap-tool-core.test.iml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module version="4">
|
||||
<component name="AdditionalModuleElements">
|
||||
<content url="file://$MODULE_DIR$/../../../dap-tool-core/build/generated/sources/annotationProcessor/java/test">
|
||||
<sourceFolder url="file://$MODULE_DIR$/../../../dap-tool-core/build/generated/sources/annotationProcessor/java/test" isTestSource="true" generated="true" />
|
||||
</content>
|
||||
</component>
|
||||
</module>
|
||||
8
.idea/modules/dap-tool-oth/dap-admin.dap-tool-oth.main.iml
generated
Normal file
8
.idea/modules/dap-tool-oth/dap-admin.dap-tool-oth.main.iml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module version="4">
|
||||
<component name="AdditionalModuleElements">
|
||||
<content url="file://$MODULE_DIR$/../../../dap-tool-oth/build/generated/sources/annotationProcessor/java/main">
|
||||
<sourceFolder url="file://$MODULE_DIR$/../../../dap-tool-oth/build/generated/sources/annotationProcessor/java/main" isTestSource="false" generated="true" />
|
||||
</content>
|
||||
</component>
|
||||
</module>
|
||||
8
.idea/modules/dap-tool-sms/dap-admin.dap-tool-sms.main.iml
generated
Normal file
8
.idea/modules/dap-tool-sms/dap-admin.dap-tool-sms.main.iml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module version="4">
|
||||
<component name="AdditionalModuleElements">
|
||||
<content url="file://$MODULE_DIR$/../../../dap-tool-sms/build/generated/sources/annotationProcessor/java/main">
|
||||
<sourceFolder url="file://$MODULE_DIR$/../../../dap-tool-sms/build/generated/sources/annotationProcessor/java/main" isTestSource="false" generated="true" />
|
||||
</content>
|
||||
</component>
|
||||
</module>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
868
.idea/workspace.xml
generated
Normal file
868
.idea/workspace.xml
generated
Normal file
@@ -0,0 +1,868 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AutoImportSettings">
|
||||
<option name="autoReloadType" value="SELECTIVE" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="f2db61ef-4338-4148-81d8-f16ac2f70318" name="ë³€ê²? comment="log 경로 ?˜ì •" />
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="EmbeddingIndexingInfo">
|
||||
<option name="cachedIndexableFilesCount" value="477" />
|
||||
</component>
|
||||
<component name="ExternalProjectsData">
|
||||
<projectState path="$PROJECT_DIR$">
|
||||
<ProjectState />
|
||||
</projectState>
|
||||
</component>
|
||||
<component name="ExternalProjectsManager">
|
||||
<system id="GRADLE">
|
||||
<state>
|
||||
<task path="$PROJECT_DIR$">
|
||||
<activation />
|
||||
</task>
|
||||
<projects_view>
|
||||
<tree_state>
|
||||
<expand />
|
||||
<select />
|
||||
</tree_state>
|
||||
</projects_view>
|
||||
</state>
|
||||
</system>
|
||||
</component>
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_BRANCH_BY_REPOSITORY">
|
||||
<map>
|
||||
<entry key="$PROJECT_DIR$" value="feature/khs_0708" />
|
||||
</map>
|
||||
</option>
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="GitHubPullRequestSearchHistory">{
|
||||
"lastFilter": {
|
||||
"state": "OPEN",
|
||||
"assignee": "jadekimhs-creator"
|
||||
}
|
||||
}</component>
|
||||
<component name="GithubPullRequestsUISettings">{
|
||||
"selectedUrlAndAccountId": {
|
||||
"url": "https://github.com/jadekimhs-creator/axhub-backend-main.git",
|
||||
"accountId": "d14f3549-59c6-4914-b2fc-96612b565c83"
|
||||
}
|
||||
}</component>
|
||||
<component name="HighlightingSettingsPerFile">
|
||||
<setting file="file://$PROJECT_DIR$/axhub-tool-core/src/main/java/io/shinhanlife/axhub/biz/mcp/adapter/test/MockEimsHttpServer.java" root0="SKIP_INSPECTION" />
|
||||
</component>
|
||||
<component name="HttpClientOnboardingState">{
|
||||
"isOnboardingCommentShown": true
|
||||
}</component>
|
||||
<component name="KubernetesApiPersistence">{}</component>
|
||||
<component name="KubernetesApiProvider">{
|
||||
"isMigrated": true
|
||||
}</component>
|
||||
<component name="MavenImportPreferences">
|
||||
<option name="generalSettings">
|
||||
<MavenGeneralSettings>
|
||||
<option name="mavenHomeTypeForPersistence" value="WRAPPER" />
|
||||
</MavenGeneralSettings>
|
||||
</option>
|
||||
</component>
|
||||
<component name="McpProjectServerCommands">
|
||||
<commands />
|
||||
<urls />
|
||||
</component>
|
||||
<component name="NextEditCompletionFeaturesState">
|
||||
<decayedCancelled>
|
||||
<entry key="MS100" value="1.0" />
|
||||
<entry key="MS500" value="1.0" />
|
||||
<entry key="S2" value="1.0" />
|
||||
<entry key="S5" value="1.0" />
|
||||
<entry key="S10" value="1.0" />
|
||||
<entry key="S30" value="1.0" />
|
||||
<entry key="S60" value="1.0" />
|
||||
<entry key="M2" value="1.0" />
|
||||
<entry key="M5" value="1.0000004063583507" />
|
||||
<entry key="M10" value="1.0014248804304775" />
|
||||
<entry key="M15" value="1.0216507258806833" />
|
||||
<entry key="M30" value="1.3290052443235425" />
|
||||
<entry key="H1" value="2.282572068736398" />
|
||||
<entry key="H2" value="3.532355821946708" />
|
||||
<entry key="H4" value="4.558341371394377" />
|
||||
<entry key="D1" value="9.094716752814406" />
|
||||
<entry key="W1" value="70.60325442196253" />
|
||||
</decayedCancelled>
|
||||
<decayedSelected>
|
||||
<entry key="MS100" value="0.0" />
|
||||
<entry key="MS500" value="0.0" />
|
||||
<entry key="S2" value="0.0" />
|
||||
<entry key="S5" value="0.0" />
|
||||
<entry key="S10" value="0.0" />
|
||||
<entry key="S30" value="0.0" />
|
||||
<entry key="S60" value="0.0" />
|
||||
<entry key="M2" value="0.0" />
|
||||
<entry key="M5" value="4.0E-323" />
|
||||
<entry key="M10" value="1.1150340305311683E-161" />
|
||||
<entry key="M15" value="7.191589989208289E-108" />
|
||||
<entry key="M30" value="4.642712610093428E-54" />
|
||||
<entry key="H1" value="3.731613880427818E-27" />
|
||||
<entry key="H2" value="1.0580265938924739E-13" />
|
||||
<entry key="H4" value="5.633917287885551E-7" />
|
||||
<entry key="D1" value="0.2594845425222435" />
|
||||
<entry key="W1" value="3.6444939982225684" />
|
||||
</decayedSelected>
|
||||
<decayedShown>
|
||||
<entry key="MS100" value="0.0018350053695586157" />
|
||||
<entry key="MS500" value="0.2836138712339885" />
|
||||
<entry key="S2" value="0.729763044896567" />
|
||||
<entry key="S5" value="0.881602365474992" />
|
||||
<entry key="S10" value="0.9389368272013789" />
|
||||
<entry key="S30" value="0.979216654035547" />
|
||||
<entry key="S60" value="0.9895537651060436" />
|
||||
<entry key="M2" value="0.9947631703606863" />
|
||||
<entry key="M5" value="0.9979023743437856" />
|
||||
<entry key="M10" value="1.00037529616311" />
|
||||
<entry key="M15" value="1.0209507142357308" />
|
||||
<entry key="M30" value="1.328653905977673" />
|
||||
<entry key="H1" value="2.2823944051974228" />
|
||||
<entry key="H2" value="3.532265686879452" />
|
||||
<entry key="H4" value="4.558296332164408" />
|
||||
<entry key="D1" value="9.354155020042933" />
|
||||
<entry key="W1" value="74.24764635087368" />
|
||||
</decayedShown>
|
||||
</component>
|
||||
<component name="ProjectColorInfo">{
|
||||
"associatedIndex": 0,
|
||||
"fromUser": false
|
||||
}</component>
|
||||
<component name="ProjectId" id="3Fz4p4oVzuFdzoWCnvW5rfH4F4v" />
|
||||
<component name="ProjectViewState">
|
||||
<option name="autoscrollFromSource" value="true" />
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent">{
|
||||
"keyToString": {
|
||||
"Docker.docker-compose.yml.gateway: Compose ë°°í<C2B0>¬.executor": "Run",
|
||||
"Docker.docker-compose.yml.tool-sms: Compose ë°°í<C2B0>¬.executor": "Run",
|
||||
"Docker.docker-compose.yml: Compose ë°°í<C2B0>¬.executor": "Run",
|
||||
"Docker.docker-compose.yml[gateway, redis, tool-email, tool-other, tool-payment, tool-sms, mci-mock, tool-hr]: Compose ë°°í<C2B0>¬.executor": "Run",
|
||||
"Docker.docker-compose.yml[gateway, redis, tool-email, tool-other, tool-payment, tool-sms, mci-mock]: Compose ë°°í<C2B0>¬.executor": "Run",
|
||||
"Docker.docker-compose.yml[gateway, redis, tool-email, tool-other, tool-payment, tool-sms]: Compose ë°°í<C2B0>¬.executor": "Run",
|
||||
"Docker.docker-compose.yml[tool-sms, tool-email, gateway, tool-other]: Compose ë°°í<C2B0>¬.executor": "Run",
|
||||
"Gradle.SampleMciMapperTest.testAiToMciMapping.executor": "Debug",
|
||||
"Gradle.ShinhanMciSenderTest.testJsonUnwrapped.executor": "Run",
|
||||
"Gradle.axhub-backend-main [:axhub-gateway:bootBuildImage].executor": "Run",
|
||||
"Gradle.axhub-backend-main [:axhub-gateway:build].executor": "Run",
|
||||
"Gradle.axhub-backend-main [bootRun --args=&quot;--server.port=8082&quot;].executor": "Run",
|
||||
"Gradle.axhub-backend-main [clean build -x test].executor": "Run",
|
||||
"Gradle.axhub-backend-main 빌드.executor": "Run",
|
||||
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
||||
"Notification.DisplayName-DoNotAsk-DatabaseConfigFileWatcher.found": "???…ë<E280A6>°?´íЏ???°ì<C2B0>´?°ë² ?´ìФ 구성??발견?˜ì—ˆ?µë‹ˆ??quot;,
|
||||
"Notification.DoNotAsk-DatabaseConfigFileWatcher.found": "true",
|
||||
"RequestMappingsPanelOrder0": "0",
|
||||
"RequestMappingsPanelOrder1": "1",
|
||||
"RequestMappingsPanelWidth0": "75",
|
||||
"RequestMappingsPanelWidth1": "75",
|
||||
"RunOnceActivity.MCP Project settings loaded": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
||||
"RunOnceActivity.git.unshallow": "true",
|
||||
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
||||
"Spring Boot.AxHubAdminApplication.executor": "Debug",
|
||||
"Spring Boot.AxHubGatewayApplication (1).executor": "Run",
|
||||
"Spring Boot.AxHubGatewayApplication.executor": "Run",
|
||||
"Spring Boot.AxHubToolEmailApplication.executor": "Debug",
|
||||
"Spring Boot.AxHubToolOtherApplication.executor": "Run",
|
||||
"Spring Boot.AxHubToolSmsApplication.executor": "Debug",
|
||||
"Spring Boot.HrToolApplication.executor": "Debug",
|
||||
"Spring Boot.PaymentToolApplication.executor": "Debug",
|
||||
"codeWithMe.voiceChat.enabledByDefault": "false",
|
||||
"com.intellij.ml.llm.matterhorn.ej.ui.settings.DefaultModelSelectionForGA.v1": "true",
|
||||
"git-widget-placeholder": "feature/mcp-refactoring",
|
||||
"junie.onboarding.icon.badge.shown": "true",
|
||||
"kotlin-language-version-configured": "true",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.tslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"node.js.selected.package.tslint": "(autodetect)",
|
||||
"nodejs_package_manager_path": "npm",
|
||||
"project.structure.last.edited": "모듈",
|
||||
"project.structure.proportion": "0.0",
|
||||
"project.structure.side.proportion": "0.0",
|
||||
"run.configurations.included.in.services": "true",
|
||||
"settings.editor.selected.configurable": "preferences.lookFeel",
|
||||
"to.speed.mode.migration.done": "true",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
},
|
||||
"keyToStringList": {
|
||||
"DatabaseDriversLRU": [
|
||||
"redis"
|
||||
]
|
||||
}
|
||||
}</component>
|
||||
<component name="RecentsManager">
|
||||
<key name="CopyClassDialog.RECENTS_KEY">
|
||||
<recent name="io.shinhanlife.axhub.biz.mcp.tool.service" />
|
||||
<recent name="io.shinhanlife.axhub.biz.mcp.tool.sms.service" />
|
||||
<recent name="io.shinhanlife.axhub.biz.mcp.tool.mapper" />
|
||||
</key>
|
||||
</component>
|
||||
<component name="RunDashboard">
|
||||
<option name="configurationTypes">
|
||||
<set>
|
||||
<option value="KtorApplicationConfigurationType" />
|
||||
<option value="MicronautRunConfigurationType" />
|
||||
<option value="QuarkusRunConfigurationType" />
|
||||
<option value="SpringBootApplicationConfigurationType" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
<component name="RunManager" selected="Docker.docker-compose.yml[gateway, redis, tool-email, tool-other, tool-payment, tool-sms, mci-mock]: Compose ë°°í<C2B0>¬">
|
||||
<configuration name="SampleMciMapperTest.testAiToMciMapping" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value=":axhub-tool-core:test" />
|
||||
<option value="--tests" />
|
||||
<option value=""io.shinhanlife.axhub.biz.mcp.sample.mapper.SampleMciMapperTest.testAiToMciMapping"" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<ExternalSystemDebugDisabled>false</ExternalSystemDebugDisabled>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<RunAsTest>true</RunAsTest>
|
||||
<GradleProfilingDisabled>false</GradleProfilingDisabled>
|
||||
<GradleCoverageDisabled>false</GradleCoverageDisabled>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="ShinhanMciSenderTest.testJsonUnwrapped" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value=":axhub-tool-core:test" />
|
||||
<option value="--tests" />
|
||||
<option value=""io.shinhanlife.axhub.biz.mcp.adapter.sender.ShinhanMciSenderTest.testJsonUnwrapped"" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<ExternalSystemDebugDisabled>false</ExternalSystemDebugDisabled>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<RunAsTest>true</RunAsTest>
|
||||
<GradleProfilingDisabled>false</GradleProfilingDisabled>
|
||||
<GradleCoverageDisabled>false</GradleCoverageDisabled>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="axhub-backend-main [:axhub-gateway:bootBuildImage]" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value=":axhub-gateway:bootBuildImage" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<ExternalSystemDebugDisabled>false</ExternalSystemDebugDisabled>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<RunAsTest>false</RunAsTest>
|
||||
<GradleProfilingDisabled>false</GradleProfilingDisabled>
|
||||
<GradleCoverageDisabled>false</GradleCoverageDisabled>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="axhub-backend-main [bootRun --args=&quot;--server.port=8082&quot;]" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="-PmainClass=io.shinhanlife.AxHubToolApplication" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value="bootRun" />
|
||||
<option value="--args=&quot;--server.port=8082&quot;" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<ExternalSystemDebugDisabled>false</ExternalSystemDebugDisabled>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<RunAsTest>false</RunAsTest>
|
||||
<GradleProfilingDisabled>false</GradleProfilingDisabled>
|
||||
<GradleCoverageDisabled>false</GradleCoverageDisabled>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="AxHubGatewayApplication (1)" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
|
||||
<module name="dap-admin.dat-gateway.main" />
|
||||
<option name="SPRING_BOOT_MAIN_CLASS" value="io.shinhanlife.dat.mcg.AxHubGatewayApplication" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
<configuration name="AxHubToolEmailApplication (1)" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
|
||||
<module name="dap-admin.dap-tool-email.main" />
|
||||
<option name="SPRING_BOOT_MAIN_CLASS" value="io.shinhanlife.dat.mcc.email.AxHubToolEmailApplication" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
<configuration name="AxHubToolOtherApplication (1)" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
|
||||
<module name="dap-admin.dap-tool-other.main" />
|
||||
<option name="SPRING_BOOT_MAIN_CLASS" value="io.shinhanlife.dat.mcc.other.AxHubToolOtherApplication" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
<configuration name="AxHubToolSmsApplication (1)" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
|
||||
<module name="dap-admin.dap-tool-sms.main" />
|
||||
<option name="SPRING_BOOT_MAIN_CLASS" value="io.shinhanlife.dat.mcc.sms.AxHubToolSmsApplication" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
<configuration name="PaymentToolApplication (1)" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
|
||||
<module name="dap-admin.dap-tool-payment.main" />
|
||||
<option name="SPRING_BOOT_MAIN_CLASS" value="io.shinhanlife.dat.mcc.payment.PaymentToolApplication" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
<configuration default="true" type="docker-deploy" factoryName="docker-compose.yml" temporary="true">
|
||||
<deployment type="docker-compose.yml">
|
||||
<settings />
|
||||
</deployment>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="docker-compose.yml[gateway, redis, tool-email, tool-other, tool-payment, tool-sms, mci-mock]: Compose ë°°í<C2B0>¬" type="docker-deploy" factoryName="docker-compose.yml" temporary="true" server-name="Docker">
|
||||
<deployment type="docker-compose.yml">
|
||||
<settings>
|
||||
<option name="commandLineOptions" value="--build" />
|
||||
<option name="services">
|
||||
<list>
|
||||
<option value="gateway" />
|
||||
<option value="redis" />
|
||||
<option value="tool-email" />
|
||||
<option value="tool-other" />
|
||||
<option value="tool-payment" />
|
||||
<option value="tool-sms" />
|
||||
<option value="mci-mock" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="sourceFilePath" value="docker-compose.yml" />
|
||||
</settings>
|
||||
</deployment>
|
||||
<method v="2">
|
||||
<option name="Gradle.BeforeRunTask" enabled="false" tasks="clean build -x test" externalProjectPath="$PROJECT_DIR$" vmOptions="" scriptParameters="" />
|
||||
</method>
|
||||
</configuration>
|
||||
<list>
|
||||
<item itemvalue="Docker.docker-compose.yml[gateway, redis, tool-email, tool-other, tool-payment, tool-sms, mci-mock]: Compose ë°°í<C2B0>¬" />
|
||||
<item itemvalue="Gradle.ShinhanMciSenderTest.testJsonUnwrapped" />
|
||||
<item itemvalue="Gradle.axhub-backend-main [:axhub-gateway:bootBuildImage]" />
|
||||
<item itemvalue="Gradle.SampleMciMapperTest.testAiToMciMapping" />
|
||||
<item itemvalue="Gradle.axhub-backend-main [bootRun --args=&quot;--server.port=8082&quot;]" />
|
||||
<item itemvalue="Spring Boot.AxHubGatewayApplication (1)" />
|
||||
<item itemvalue="Spring Boot.AxHubToolEmailApplication (1)" />
|
||||
<item itemvalue="Spring Boot.AxHubToolOtherApplication (1)" />
|
||||
<item itemvalue="Spring Boot.AxHubToolSmsApplication (1)" />
|
||||
<item itemvalue="Spring Boot.PaymentToolApplication (1)" />
|
||||
</list>
|
||||
<recent_temporary>
|
||||
<list>
|
||||
<item itemvalue="Docker.docker-compose.yml[gateway, redis, tool-email, tool-other, tool-payment, tool-sms, mci-mock]: Compose ë°°í<C2B0>¬" />
|
||||
<item itemvalue="Gradle.ShinhanMciSenderTest.testJsonUnwrapped" />
|
||||
<item itemvalue="Gradle.axhub-backend-main [:axhub-gateway:bootBuildImage]" />
|
||||
<item itemvalue="Gradle.SampleMciMapperTest.testAiToMciMapping" />
|
||||
<item itemvalue="Gradle.axhub-backend-main [bootRun --args=&quot;--server.port=8082&quot;]" />
|
||||
</list>
|
||||
</recent_temporary>
|
||||
</component>
|
||||
<component name="SharedIndexes">
|
||||
<attachedChunks>
|
||||
<set>
|
||||
<option value="bundled-jdk-30f59d01ecdd-37e91769500f-intellij.indexing.shared.core-IU-261.25134.95" />
|
||||
<option value="bundled-js-predefined-d6986cc7102b-31caf2ab9e3c-JavaScript-IU-261.25134.95" />
|
||||
</set>
|
||||
</attachedChunks>
|
||||
</component>
|
||||
<component name="StructureViewState">
|
||||
<option name="selectedTab" value="?¼ë¦¬" />
|
||||
</component>
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="?”í<E2809D>´???‘ì—…">
|
||||
<changelist id="f2db61ef-4338-4148-81d8-f16ac2f70318" name="변� comment="" />
|
||||
<created>1783064745410</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1783064745410</updated>
|
||||
<workItem from="1783064746565" duration="3558000" />
|
||||
<workItem from="1783295622537" duration="82000" />
|
||||
<workItem from="1783296277574" duration="21824000" />
|
||||
<workItem from="1783381712525" duration="17941000" />
|
||||
<workItem from="1783468449372" duration="71000" />
|
||||
<workItem from="1783468566397" duration="22445000" />
|
||||
<workItem from="1783554872446" duration="40241000" />
|
||||
<workItem from="1783673983621" duration="5000" />
|
||||
<workItem from="1783900184231" duration="22468000" />
|
||||
<workItem from="1783988452900" duration="11918000" />
|
||||
<workItem from="1784003561991" duration="2555000" />
|
||||
<workItem from="1784007005258" duration="1025000" />
|
||||
<workItem from="1784008697772" duration="917000" />
|
||||
<workItem from="1784009797198" duration="27131000" />
|
||||
<workItem from="1784159561976" duration="18127000" />
|
||||
<workItem from="1784187067292" duration="1473000" />
|
||||
<workItem from="1784189910429" duration="11107000" />
|
||||
</task>
|
||||
<task id="LOCAL-00001" summary="index.html ?ŒìФ ?˜ì •">
|
||||
<option name="closed" value="true" />
|
||||
<created>1783316153458</created>
|
||||
<option name="number" value="00001" />
|
||||
<option name="presentableId" value="LOCAL-00001" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1783316153458</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00002" summary="?ŒìФ ?˜ì •">
|
||||
<option name="closed" value="true" />
|
||||
<created>1783326414753</created>
|
||||
<option name="number" value="00002" />
|
||||
<option name="presentableId" value="LOCAL-00002" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1783326414753</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00003" summary="?˜í”Œ ?ŒìФ ?˜ì •">
|
||||
<option name="closed" value="true" />
|
||||
<created>1783476059190</created>
|
||||
<option name="number" value="00003" />
|
||||
<option name="presentableId" value="LOCAL-00003" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1783476059191</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00004" summary="?ŒìФ ?˜ì •">
|
||||
<option name="closed" value="true" />
|
||||
<created>1783563177105</created>
|
||||
<option name="number" value="00004" />
|
||||
<option name="presentableId" value="LOCAL-00004" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1783563177105</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00005" summary="hr ?„로?<3F>트 ?? œ">
|
||||
<option name="closed" value="true" />
|
||||
<created>1783910068516</created>
|
||||
<option name="number" value="00005" />
|
||||
<option name="presentableId" value="LOCAL-00005" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1783910068516</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00006" summary="hr ?„로?<3F>트 ?? œ">
|
||||
<option name="closed" value="true" />
|
||||
<created>1783910259418</created>
|
||||
<option name="number" value="00006" />
|
||||
<option name="presentableId" value="LOCAL-00006" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1783910259418</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00007" summary="?´ëª¨ì§€ py ?Œì<C592>¼ ?? œ">
|
||||
<option name="closed" value="true" />
|
||||
<created>1783927746213</created>
|
||||
<option name="number" value="00007" />
|
||||
<option name="presentableId" value="LOCAL-00007" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1783927746213</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00008" summary="?ŒìФ ?˜ì • ?„료">
|
||||
<option name="closed" value="true" />
|
||||
<created>1783931448779</created>
|
||||
<option name="number" value="00008" />
|
||||
<option name="presentableId" value="LOCAL-00008" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1783931448779</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00009" summary="?ŒìФ ?˜ì • ?„료">
|
||||
<option name="closed" value="true" />
|
||||
<created>1783931684286</created>
|
||||
<option name="number" value="00009" />
|
||||
<option name="presentableId" value="LOCAL-00009" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1783931684286</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00010" summary="?ŒìФ ?˜ì • ?„료">
|
||||
<option name="closed" value="true" />
|
||||
<created>1783931990807</created>
|
||||
<option name="number" value="00010" />
|
||||
<option name="presentableId" value="LOCAL-00010" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1783931990807</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00011" summary="보통 금융ê¶?? 한?¼ì<C2BC>´?????€??ë³´í—˜?????´ë? ?ˆê±°???œìФ??>
|
||||
<option name="closed" value="true" />
|
||||
<created>1783992292399</created>
|
||||
<option name="number" value="00011" />
|
||||
<option name="presentableId" value="LOCAL-00011" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1783992292399</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00012" summary="?ŒìФ ?˜ì •">
|
||||
<option name="closed" value="true" />
|
||||
<created>1783994134409</created>
|
||||
<option name="number" value="00012" />
|
||||
<option name="presentableId" value="LOCAL-00012" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1783994134409</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00013" summary="?ŒìФ ?˜ì •">
|
||||
<option name="closed" value="true" />
|
||||
<created>1783995434263</created>
|
||||
<option name="number" value="00013" />
|
||||
<option name="presentableId" value="LOCAL-00013" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1783995434263</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00014" summary="?ŒìФ ?˜ì •">
|
||||
<option name="closed" value="true" />
|
||||
<created>1784001064429</created>
|
||||
<option name="number" value="00014" />
|
||||
<option name="presentableId" value="LOCAL-00014" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1784001064429</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00015" summary="?ŒìФ ?˜ì •">
|
||||
<option name="closed" value="true" />
|
||||
<created>1784094367009</created>
|
||||
<option name="number" value="00015" />
|
||||
<option name="presentableId" value="LOCAL-00015" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1784094367009</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00016" summary="?˜ì • ë²„ì „">
|
||||
<option name="closed" value="true" />
|
||||
<created>1784098815515</created>
|
||||
<option name="number" value="00016" />
|
||||
<option name="presentableId" value="LOCAL-00016" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1784098815515</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00017" summary="?˜ì • ë²„ì „">
|
||||
<option name="closed" value="true" />
|
||||
<created>1784105860688</created>
|
||||
<option name="number" value="00017" />
|
||||
<option name="presentableId" value="LOCAL-00017" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1784105860688</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00018" summary="?˜ì • ë²„ì „">
|
||||
<option name="closed" value="true" />
|
||||
<created>1784161174384</created>
|
||||
<option name="number" value="00018" />
|
||||
<option name="presentableId" value="LOCAL-00018" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1784161174384</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00019" summary="?˜ì • ë²„ì „">
|
||||
<option name="closed" value="true" />
|
||||
<created>1784191131041</created>
|
||||
<option name="number" value="00019" />
|
||||
<option name="presentableId" value="LOCAL-00019" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1784191131041</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00020" summary="?˜ì • ë²„ì „111">
|
||||
<option name="closed" value="true" />
|
||||
<created>1784191828823</created>
|
||||
<option name="number" value="00020" />
|
||||
<option name="presentableId" value="LOCAL-00020" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1784191828823</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00021" summary="?˜ì • ë²„ì „111">
|
||||
<option name="closed" value="true" />
|
||||
<created>1784192641209</created>
|
||||
<option name="number" value="00021" />
|
||||
<option name="presentableId" value="LOCAL-00021" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1784192641209</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00022" summary="log 경로 ?˜ì •">
|
||||
<option name="closed" value="true" />
|
||||
<created>1784505706146</created>
|
||||
<option name="number" value="00022" />
|
||||
<option name="presentableId" value="LOCAL-00022" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1784505706146</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="23" />
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
<option name="version" value="3" />
|
||||
</component>
|
||||
<component name="Vcs.Log.Tabs.Properties">
|
||||
<option name="OPEN_GENERIC_TABS">
|
||||
<map>
|
||||
<entry key="2c2de5f9-e5f5-4565-9d3d-1bc8a056e08f" value="TOOL_WINDOW" />
|
||||
<entry key="39879709-a44e-4eae-b130-7a588c5a5cd8" value="TOOL_WINDOW" />
|
||||
<entry key="4b40888d-9986-4854-8984-53a06ca83373" value="TOOL_WINDOW" />
|
||||
<entry key="7bdac3ab-6a4e-4e39-a92b-4a2c5ea226bd" value="TOOL_WINDOW" />
|
||||
<entry key="26d7f06e-58b7-469d-946b-8ab4827b57da" value="TOOL_WINDOW" />
|
||||
<entry key="efe781d4-6494-48db-a04e-65359ea237cd" value="TOOL_WINDOW" />
|
||||
<entry key="860d5318-a419-41ba-a3c6-f009a6f61676" value="TOOL_WINDOW" />
|
||||
<entry key="57842608-e2e0-4925-a066-a4c097b4b0ad" value="TOOL_WINDOW" />
|
||||
</map>
|
||||
</option>
|
||||
<option name="TAB_STATES">
|
||||
<map>
|
||||
<entry key="26d7f06e-58b7-469d-946b-8ab4827b57da">
|
||||
<value>
|
||||
<State>
|
||||
<option name="FILTERS">
|
||||
<map>
|
||||
<entry key="branch">
|
||||
<value>
|
||||
<list>
|
||||
<option value="HEAD" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="roots">
|
||||
<value>
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
<option name="SHOW_ONLY_AFFECTED_CHANGES" value="true" />
|
||||
</State>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="2c2de5f9-e5f5-4565-9d3d-1bc8a056e08f">
|
||||
<value>
|
||||
<State>
|
||||
<option name="FILTERS">
|
||||
<map>
|
||||
<entry key="branch">
|
||||
<value>
|
||||
<list>
|
||||
<option value="HEAD" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="roots">
|
||||
<value>
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
<option name="SHOW_ONLY_AFFECTED_CHANGES" value="true" />
|
||||
</State>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="39879709-a44e-4eae-b130-7a588c5a5cd8">
|
||||
<value>
|
||||
<State>
|
||||
<option name="FILTERS">
|
||||
<map>
|
||||
<entry key="branch">
|
||||
<value>
|
||||
<list>
|
||||
<option value="HEAD" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="roots">
|
||||
<value>
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
<option name="SHOW_ONLY_AFFECTED_CHANGES" value="true" />
|
||||
</State>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="4b40888d-9986-4854-8984-53a06ca83373">
|
||||
<value>
|
||||
<State>
|
||||
<option name="FILTERS">
|
||||
<map>
|
||||
<entry key="branch">
|
||||
<value>
|
||||
<list>
|
||||
<option value="HEAD" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="roots">
|
||||
<value>
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
<option name="SHOW_ONLY_AFFECTED_CHANGES" value="true" />
|
||||
</State>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="57842608-e2e0-4925-a066-a4c097b4b0ad">
|
||||
<value>
|
||||
<State>
|
||||
<option name="FILTERS">
|
||||
<map>
|
||||
<entry key="branch">
|
||||
<value>
|
||||
<list>
|
||||
<option value="HEAD" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="roots">
|
||||
<value>
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
<option name="SHOW_ONLY_AFFECTED_CHANGES" value="true" />
|
||||
</State>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="7bdac3ab-6a4e-4e39-a92b-4a2c5ea226bd">
|
||||
<value>
|
||||
<State>
|
||||
<option name="FILTERS">
|
||||
<map>
|
||||
<entry key="branch">
|
||||
<value>
|
||||
<list>
|
||||
<option value="HEAD" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="roots">
|
||||
<value>
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
<option name="SHOW_ONLY_AFFECTED_CHANGES" value="true" />
|
||||
</State>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="860d5318-a419-41ba-a3c6-f009a6f61676">
|
||||
<value>
|
||||
<State>
|
||||
<option name="FILTERS">
|
||||
<map>
|
||||
<entry key="branch">
|
||||
<value>
|
||||
<list>
|
||||
<option value="HEAD" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="roots">
|
||||
<value>
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
<option name="SHOW_ONLY_AFFECTED_CHANGES" value="true" />
|
||||
</State>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="MAIN">
|
||||
<value>
|
||||
<State />
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="efe781d4-6494-48db-a04e-65359ea237cd">
|
||||
<value>
|
||||
<State>
|
||||
<option name="FILTERS">
|
||||
<map>
|
||||
<entry key="branch">
|
||||
<value>
|
||||
<list>
|
||||
<option value="HEAD" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="roots">
|
||||
<value>
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
<option name="SHOW_ONLY_AFFECTED_CHANGES" value="true" />
|
||||
</State>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
<component name="VcsManagerConfiguration">
|
||||
<MESSAGE value="index.html ?ŒìФ ?˜ì •" />
|
||||
<MESSAGE value="?˜í”Œ ?ŒìФ ?˜ì •" />
|
||||
<MESSAGE value="hr ?„로?<3F>트 ?? œ" />
|
||||
<MESSAGE value="?´ëª¨ì§€ py ?Œì<C592>¼ ?? œ" />
|
||||
<MESSAGE value="?ŒìФ ?˜ì • ?„료" />
|
||||
<MESSAGE value="보통 금융ê¶?? 한?¼ì<C2BC>´?????€??ë³´í—˜?????´ë? ?ˆê±°???œìФ?? />
|
||||
<MESSAGE value="?ŒìФ ?˜ì •" />
|
||||
<MESSAGE value="?˜ì • ë²„ì „" />
|
||||
<MESSAGE value="?˜ì • ë²„ì „111" />
|
||||
<MESSAGE value="log 경로 ?˜ì •" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="log 경로 ?˜ì •" />
|
||||
</component>
|
||||
</project>
|
||||
103
AddJavadoc.java
103
AddJavadoc.java
@@ -1,103 +0,0 @@
|
||||
import java.io.*;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.*;
|
||||
import java.util.regex.*;
|
||||
|
||||
public class AddJavadoc {
|
||||
|
||||
static final String ROOT_DIR = ".";
|
||||
|
||||
static final String TEMPLATE =
|
||||
"/**\n" +
|
||||
" * @package %s\n" +
|
||||
" * @className %s\n" +
|
||||
" * @description AX HUB ?쒖뒪??泥섎━ ?대옒??n" +
|
||||
" * @author 源?뺤떇\n" +
|
||||
" * @create 2026.09.01\n" +
|
||||
" * <pre>\n" +
|
||||
" * ---------- 媛쒖젙?대젰 ----------\n" +
|
||||
" * ?섏젙?? ?섏젙?? ?섏젙?댁슜\n" +
|
||||
" * ---------- -------- ---------------------------\n" +
|
||||
" * 2026.09.01 源?뺤떇 理쒖큹?앹꽦\n" +
|
||||
" * \n" +
|
||||
" * </pre>\n" +
|
||||
" */";
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
Files.walkFileTree(Paths.get(ROOT_DIR), new SimpleFileVisitor<Path>() {
|
||||
@Override
|
||||
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
|
||||
String name = dir.getFileName().toString();
|
||||
if (name.equals(".git") || name.equals("build") || name.equals(".gradle") || name.equals("scratch") || name.equals("out") || name.equals("bin")) {
|
||||
return FileVisitResult.SKIP_SUBTREE;
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
if (file.toString().endsWith(".java") && !file.getFileName().toString().equals("AddJavadoc.java")) {
|
||||
processJavaFile(file);
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
System.out.println("Done.");
|
||||
}
|
||||
|
||||
private static void processJavaFile(Path file) throws IOException {
|
||||
List<String> lines = Files.readAllLines(file);
|
||||
String content = String.join("\n", lines);
|
||||
|
||||
if (content.contains("---------- 媛쒖젙?대젰 ----------") || content.contains("@className")) {
|
||||
System.out.println("Skipping (already has javadoc): " + file);
|
||||
return;
|
||||
}
|
||||
|
||||
String packageName = "unknown";
|
||||
String className = "unknown";
|
||||
|
||||
Matcher pkgMatcher = Pattern.compile("(?m)^\\s*package\\s+([\\w\\.]+)\\s*;").matcher(content);
|
||||
if (pkgMatcher.find()) {
|
||||
packageName = pkgMatcher.group(1);
|
||||
}
|
||||
|
||||
int classDeclIdx = -1;
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
String line = lines.get(i);
|
||||
Matcher classMatcher = Pattern.compile("^\\s*(?:public\\s+|protected\\s+|private\\s+|abstract\\s+|final\\s+|static\\s+)*(class|interface|enum|record)\\s+(\\w+)").matcher(line);
|
||||
if (classMatcher.find()) {
|
||||
classDeclIdx = i;
|
||||
className = classMatcher.group(2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (classDeclIdx == -1) {
|
||||
System.out.println("Skipping (no class declaration found): " + file);
|
||||
return;
|
||||
}
|
||||
|
||||
int insertIdx = classDeclIdx;
|
||||
for (int i = classDeclIdx - 1; i >= 0; i--) {
|
||||
String line = lines.get(i).trim();
|
||||
if (line.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (line.startsWith("@")) {
|
||||
insertIdx = i;
|
||||
} else if (line.startsWith("//") || line.startsWith("/*") || line.startsWith("*")) {
|
||||
break;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
String javadoc = String.format(TEMPLATE, packageName, className);
|
||||
lines.add(insertIdx, javadoc);
|
||||
|
||||
Files.write(file, String.join("\n", lines).getBytes("UTF-8"));
|
||||
System.out.println("Updated: " + file);
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/**
|
||||
* @package io.shinhanlife
|
||||
* @className McpBridge
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 0986406 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.time.Duration;
|
||||
|
||||
public class McpBridge {
|
||||
|
||||
public static void main(String[] args) {
|
||||
HttpClient client = HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofSeconds(5))
|
||||
.build();
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
line = line.trim();
|
||||
if (line.isEmpty()) continue;
|
||||
|
||||
try {
|
||||
HttpRequest req = HttpRequest.newBuilder()
|
||||
// Spring AI MCP Server??怨듭떇 ?⑥씪 ?붾뱶?ъ씤??
|
||||
.uri(URI.create("http://localhost:8081/mcp"))
|
||||
.header("Content-Type", "application/json")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(line))
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = client.send(req, HttpResponse.BodyHandlers.ofString());
|
||||
System.out.println(response.body());
|
||||
System.out.flush();
|
||||
} catch (Exception e) {
|
||||
// MCP ?쒖? ?먮윭 ?щ㎎?쇰줈 諛섑솚 (?꾩쓽濡?id 異붿텧 ?쒖쇅, 理쒖냼?쒖쓽 ?먮윭 ?묐떟)
|
||||
System.out.println("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32603,\"message\":\"" + e.getMessage().replace("\"", "\\\"") + "\"}}");
|
||||
System.out.flush();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
144
README.md
144
README.md
@@ -4,22 +4,22 @@
|
||||
|
||||
| Pod role | Gradle module | Container service | Internal endpoint | Local Docker endpoint |
|
||||
|---|---|---|---|---|
|
||||
| Customer/common integration | `dap-was-cus` | `was-cus` | `http://was-cus:8084/mcp` | `http://localhost:8284/mcp` |
|
||||
| Sales/notification integration | `dap-was-sal` | `was-sal` | `http://was-sal:8082/mcp` | `http://localhost:8282/mcp` |
|
||||
| Process integration (new) | `dap-was-pro` | `was-pro` | `http://was-pro:8085/mcp` | `http://localhost:8085/mcp` |
|
||||
| System integration (new) | `dap-was-sys` | `was-sys` | `http://was-sys:8086/mcp` | `http://localhost:8086/mcp` |
|
||||
| Customer/common integration | `dat-was-cus` | `was-cus` | `http://was-cus:8084/mcp` | `http://localhost:8284/mcp` |
|
||||
| Sales/notification integration | `dat-was-sal` | `was-sal` | `http://was-sal:8082/mcp` | `http://localhost:8282/mcp` |
|
||||
| Process integration (new) | `dat-was-pro` | `was-pro` | `http://was-pro:8085/mcp` | `http://localhost:8085/mcp` |
|
||||
| System integration (new) | `dat-was-sys` | `was-sys` | `http://was-sys:8086/mcp` | `http://localhost:8086/mcp` |
|
||||
|
||||
- The former `dap-was-oth` module is now `dap-was-cus`; the former `dap-was-sms` module is now `dap-was-sal`.
|
||||
- The former `dat-was-oth` module is now `dat-was-cus`; the former `dat-was-sms` module is now `dat-was-sal`.
|
||||
- This is a deployment Pod rename only. Existing tool category and function names such as `oth_*` and `sms_*` remain valid so already registered MCP clients are not broken.
|
||||
- `dap-was-pro` and `dap-was-sys` are empty, independently deployable Tool Pods ready for new business tools.
|
||||
- `dat-was-pro` and `dat-was-sys` are empty, independently deployable Tool Pods ready for new business tools.
|
||||
|
||||
### Run the Tool Pods locally
|
||||
|
||||
```powershell
|
||||
.\gradlew.bat :dap-was-cus:bootRun
|
||||
.\gradlew.bat :dap-was-sal:bootRun
|
||||
.\gradlew.bat :dap-was-pro:bootRun
|
||||
.\gradlew.bat :dap-was-sys:bootRun
|
||||
.\gradlew.bat :dat-was-cus:bootRun
|
||||
.\gradlew.bat :dat-was-sal:bootRun
|
||||
.\gradlew.bat :dat-was-pro:bootRun
|
||||
.\gradlew.bat :dat-was-sys:bootRun
|
||||
```
|
||||
|
||||
AX HUB에서 AI Agent가 업무 Tool을 검색하고 호출할 수 있도록 Gateway와 독립 Tool Pod를 제공하는 멀티 모듈 Spring Boot 프로젝트입니다.
|
||||
@@ -30,16 +30,19 @@ AX HUB에서 AI Agent가 업무 Tool을 검색하고 호출할 수 있도록 Gat
|
||||
MCP Client / AI Agent
|
||||
|
|
||||
v
|
||||
Gateway (dap-gateway, 8081)
|
||||
Gateway (dat-gateway, 8081)
|
||||
- MCP 연결, Tool Registry, 인증·인가, 라우팅, 공통 화면
|
||||
|
|
||||
+-------------------------+
|
||||
| |
|
||||
v v
|
||||
SMS Tool Pod (dap-was-sms, 8082) OTH Tool Pod (dap-was-oth, 8084)
|
||||
- SMS 업무 Tool - 공통/업무/MCI 연계 Tool
|
||||
Sales Tool Pod (dat-was-sal, 8082) Customer Tool Pod (dat-was-cus, 8084)
|
||||
- 영업/알림 업무 Tool - 고객/공통 업무 Tool
|
||||
- Tool Manifest - Tool Manifest
|
||||
- Pod Test Console - Pod Test Console
|
||||
|
|
||||
+--> Process Tool Pod (dat-was-pro, 8085)
|
||||
+--> System Tool Pod (dat-was-sys, 8086)
|
||||
|
|
||||
v
|
||||
MCI / EAI / EIMS 등 레거시 연계 대상
|
||||
@@ -51,12 +54,14 @@ Gateway는 외부 MCP 진입점과 Tool 등록·라우팅을 담당하고, 실
|
||||
|
||||
| 모듈 | 역할 | 기본 포트 |
|
||||
|---|---|---:|
|
||||
| `dap-gateway` | MCP Gateway, Registry, 라우팅, Chat·Catalog·Tester·Scaffold 화면 | 8081 |
|
||||
| `dap-was-lib` | Tool 공통 라이브러리: 어노테이션, Schema, Manifest, MCI/EAI 어댑터, 공통 Web·보안 기능 | - |
|
||||
| `dap-was-sms` | SMS/알림 업무 Tool Pod | 8082 |
|
||||
| `dap-was-oth` | 공통/샘플/MCI 연계 업무 Tool Pod | 8084 |
|
||||
| `dat-gateway` | MCP Gateway, Registry, 라우팅, Chat·Catalog·Tester·Scaffold 화면 | 8081 |
|
||||
| `dat-was-lib` | Tool 공통 라이브러리: 어노테이션, Schema, Manifest, MCI/EAI 어댑터, 공통 Web·보안 기능 | - |
|
||||
| `dat-was-sal` | 영업/알림 업무 Tool Pod | 8082 |
|
||||
| `dat-was-cus` | 고객/공통 업무 Tool Pod | 8084 |
|
||||
| `dat-was-pro` | 프로세스 업무 Tool Pod | 8085 |
|
||||
| `dat-was-sys` | 시스템 업무 Tool Pod | 8086 |
|
||||
|
||||
> 기존 `dap-tool-core`, `dap-tool-sms`, `dap-tool-oth` 명칭은 현재 각각 `dap-was-lib`, `dap-was-sms`, `dap-was-oth`로 변경되었습니다.
|
||||
> 기존 `dap-tool-core`, `dap-tool-sms`, `dap-tool-oth` 명칭은 현재 각각 `dat-was-lib`, `dat-was-sal`, `dat-was-cus`로 변경되었습니다.
|
||||
|
||||
## 3. 개발 환경
|
||||
|
||||
@@ -82,11 +87,11 @@ $env:OPENROUTER_API_KEY = '<발급받은-키>'
|
||||
각 애플리케이션은 별도 터미널에서 실행합니다.
|
||||
|
||||
```powershell
|
||||
.\gradlew.bat :dap-gateway:bootRun
|
||||
.\gradlew.bat :dap-was-sal:bootRun
|
||||
.\gradlew.bat :dap-was-cus:bootRun
|
||||
.\gradlew.bat :dap-was-pro:bootRun
|
||||
.\gradlew.bat :dap-was-sys:bootRun
|
||||
.\gradlew.bat :dat-gateway:bootRun
|
||||
.\gradlew.bat :dat-was-sal:bootRun
|
||||
.\gradlew.bat :dat-was-cus:bootRun
|
||||
.\gradlew.bat :dat-was-pro:bootRun
|
||||
.\gradlew.bat :dat-was-sys:bootRun
|
||||
```
|
||||
|
||||
### 4.2 Docker Compose 실행
|
||||
@@ -98,8 +103,10 @@ docker compose up -d --build
|
||||
| 서비스 | 호스트 URL | 컨테이너 포트 |
|
||||
|---|---|---:|
|
||||
| Gateway | http://localhost:8281 | 8081 |
|
||||
| SMS Tool Pod | http://localhost:8282 | 8082 |
|
||||
| OTH Tool Pod | http://localhost:8284 | 8084 |
|
||||
| Sales Tool Pod | http://localhost:8282 | 8082 |
|
||||
| Customer Tool Pod | http://localhost:8284 | 8084 |
|
||||
| Process Tool Pod | http://localhost:8285 | 8085 |
|
||||
| System Tool Pod | http://localhost:8286 | 8086 |
|
||||
| MCI Mock | http://localhost:8089 | 8080 |
|
||||
| Dozzle 로그 화면 | http://localhost:8288 | 8080 |
|
||||
|
||||
@@ -118,14 +125,31 @@ docker compose up -d --build
|
||||
|
||||
Docker Compose로 실행한 경우 Gateway 화면은 포트 `8281`을 사용합니다.
|
||||
|
||||
### Pod Module New: 독립 프로젝트 생성
|
||||
|
||||
Scaffold 화면의 `Pod Module New` 탭은 기존 Pod Module 생성 흐름과 별도로 동작합니다. 새 모듈명과 포트, workspace 경로를 입력하면 해당 workspace 바로 아래에 독립 Tool Pod 프로젝트를 생성합니다.
|
||||
|
||||
기본 workspace는 `C:\eGovFrameDev-4.3.1-64bit\workspace`이며, 예를 들어 모듈명으로 `dat-was-payment`를 입력하면 `C:\eGovFrameDev-4.3.1-64bit\workspace\dat-was-payment` 프로젝트가 만들어집니다. 모듈명은 `dat-was-`로 시작해야 하며, 이미 같은 폴더가 있으면 덮어쓰지 않고 생성 요청을 거절합니다.
|
||||
|
||||
생성된 프로젝트는 같은 workspace의 `dat-lib-datmt`를 Gradle composite build로 참조합니다. 생성 직후 프로젝트 폴더에서 다음 명령으로 컴파일할 수 있습니다.
|
||||
|
||||
```powershell
|
||||
cd C:\eGovFrameDev-4.3.1-64bit\workspace\dat-was-payment
|
||||
.\gradlew.bat compileJava
|
||||
```
|
||||
|
||||
이 기능은 기존 AX HUB 멀티 모듈 Pod나 기존 `Pod Module` 탭의 구성·소스를 수정하지 않습니다.
|
||||
|
||||
### Tool Pod Test Console
|
||||
|
||||
공통 정적 화면인 `tool-test-console.html`은 Tool Pod의 `/tool-manifest`를 읽어, 해당 Pod에 등록된 Tool과 `inputSchema`를 기준으로 요청 JSON을 만들어 직접 실행합니다.
|
||||
|
||||
| 대상 Pod | 로컬 URL | Docker Compose URL |
|
||||
|---|---|---|
|
||||
| OTH | http://localhost:8084/tool-test-console.html | http://localhost:8284/tool-test-console.html |
|
||||
| SMS | http://localhost:8082/tool-test-console.html | http://localhost:8282/tool-test-console.html |
|
||||
| Customer | http://localhost:8084/tool-test-console.html | http://localhost:8284/tool-test-console.html |
|
||||
| Sales | http://localhost:8082/tool-test-console.html | http://localhost:8282/tool-test-console.html |
|
||||
| Process | http://localhost:8085/tool-test-console.html | http://localhost:8285/tool-test-console.html |
|
||||
| System | http://localhost:8086/tool-test-console.html | http://localhost:8286/tool-test-console.html |
|
||||
|
||||
사용 방법은 다음과 같습니다.
|
||||
|
||||
@@ -146,7 +170,7 @@ Docker Compose로 실행한 경우 Gateway 화면은 포트 `8281`을 사용합
|
||||
|
||||
> Auto Tester의 더미 요청 성공은 **통신·등록·기본 스키마 확인용 스모크 테스트**입니다. 실제 MCI/EAI 업무 규칙, 권한, 데이터 정합성은 보장하지 않습니다. Stress Test와 Auto Ping은 실제 Tool·레거시 호출을 유발할 수 있으므로 개발/테스트 환경 및 승인된 테스트 데이터에서만 사용합니다.
|
||||
|
||||
UI에서 사용하는 Tailwind CSS와 Chart.js는 `dap-gateway/src/main/resources/static/lib`에 포함되어 있어, 화면 라이브러리 로딩을 위해 외부 CDN 연결이 필요하지 않습니다.
|
||||
UI에서 사용하는 Tailwind CSS와 Chart.js는 `dat-gateway/src/main/resources/static/lib`에 포함되어 있어, 화면 라이브러리 로딩을 위해 외부 CDN 연결이 필요하지 않습니다.
|
||||
|
||||
## 6. 주요 HTTP API
|
||||
|
||||
@@ -214,15 +238,15 @@ Request DTO
|
||||
-> Legacy System
|
||||
```
|
||||
|
||||
- Request DTO에는 `@McpValidation`으로 단순 입력 검증을 선언합니다.
|
||||
- Request DTO 필드에는 `@Schema(description = "...")`와 Bean Validation 어노테이션을 선언합니다.
|
||||
- 복잡하거나 조건부 규칙이 필요한 입력은 `tool-schemas/{categoryKey}/...-input-schema.json` 리소스를 사용합니다.
|
||||
- 응답은 `@McpOutputSchema`로 간단한 설명을 제공하고, 복잡한 응답은 Output Schema 리소스로 명시합니다.
|
||||
- 응답 Schema가 필요한 경우 Response DTO에 `@McpOutputSchema`를 선언하고, 복잡한 경우 Output Schema 리소스로 명시합니다.
|
||||
- `null`의 업무 의미, 조건부 필드, 배열 정렬 기준, `hasMore` 여부, 민감 정보 제외 원칙을 Schema 설명에 명확히 씁니다.
|
||||
|
||||
### Schema 우선순위
|
||||
|
||||
1. `inputSchemaResource` 또는 `outputSchemaResource`가 지정된 경우: 해당 JSON Schema를 사용합니다.
|
||||
2. 리소스가 없으면: DTO 어노테이션(`@McpValidation`, `@McpOutputSchema`)과 공통 Generator로 생성합니다.
|
||||
2. 리소스가 없으면: DTO의 `@Schema` 정보와 공통 Generator로 생성합니다. 응답 Schema는 `@McpOutputSchema`가 선언된 DTO만 자동 생성합니다.
|
||||
|
||||
## 8. Manifest와 Registry
|
||||
|
||||
@@ -232,12 +256,14 @@ Gateway는 Registry 등록 및 Heartbeat 정보를 이용해 Tool을 라우팅
|
||||
|
||||
## 9. MCI/EAI 연계
|
||||
|
||||
공통 MCI/EAI 연계 기능과 Glow 기본 설정은 `dap-was-lib`에서 제공합니다. Tool Pod별 애플리케이션 프로필은 각 Pod의 `application*.yml`에서 관리합니다.
|
||||
공통 MCI/EAI 연계 기능과 Glow 기본 설정은 `dat-was-lib`에서 제공합니다. Tool Pod별 애플리케이션 프로필은 각 Pod의 `application*.yml`에서 관리합니다.
|
||||
|
||||
```text
|
||||
dap-was-lib/src/main/resources/glow/application-glow*.yml
|
||||
dap-was-sms/src/main/resources/application*.yml
|
||||
dap-was-oth/src/main/resources/application*.yml
|
||||
dat-was-lib/src/main/resources/glow/application-glow*.yml
|
||||
dat-was-sal/src/main/resources/application*.yml
|
||||
dat-was-cus/src/main/resources/application*.yml
|
||||
dat-was-pro/src/main/resources/application*.yml
|
||||
dat-was-sys/src/main/resources/application*.yml
|
||||
```
|
||||
|
||||
MCI 호출 주소는 일반적으로 `host + uri`로 구성합니다. 예를 들어 `host=https://dev-ichmci.shinhanlife.co.kr`, `uri=/ntl_mci/clc_rcv`이면 호출 대상은 다음과 같습니다.
|
||||
@@ -250,11 +276,11 @@ https://dev-ichmci.shinhanlife.co.kr/ntl_mci/clc_rcv
|
||||
## 10. 품질 검증
|
||||
|
||||
```powershell
|
||||
.\gradlew.bat :dap-was-lib:test
|
||||
.\gradlew.bat :dap-was-cus:compileJava
|
||||
.\gradlew.bat :dap-was-sal:compileJava
|
||||
.\gradlew.bat :dap-was-pro:compileJava
|
||||
.\gradlew.bat :dap-was-sys:compileJava
|
||||
.\gradlew.bat :dat-was-lib:test
|
||||
.\gradlew.bat :dat-was-cus:compileJava
|
||||
.\gradlew.bat :dat-was-sal:compileJava
|
||||
.\gradlew.bat :dat-was-pro:compileJava
|
||||
.\gradlew.bat :dat-was-sys:compileJava
|
||||
.\gradlew.bat validateMcpToolNames
|
||||
```
|
||||
|
||||
@@ -279,29 +305,31 @@ https://dev-ichmci.shinhanlife.co.kr/ntl_mci/clc_rcv
|
||||
|
||||
| 기능 | 위치 |
|
||||
|---|---|
|
||||
| Tool 공통 Controller | `dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/presentation` |
|
||||
| Tool 어노테이션 | `dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation` |
|
||||
| JSON Schema/검증 | `dap-was-lib/src/main/java/io/shinhanlife/dap/lib/schema`, `.../validation` |
|
||||
| Manifest | `dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest` |
|
||||
| Tool Scaffold | `dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolScaffolder.java` |
|
||||
| Gateway 라우터 | `dap-gateway/src/main/java/io/shinhanlife/dap/mcg/presentation/McpRouterController.java` |
|
||||
| OTH Tool | `dap-was-oth/src/main/java` |
|
||||
| SMS Tool | `dap-was-sms/src/main/java` |
|
||||
| Tool 공통 Controller | `dat-was-lib/src/main/java/io/shinhanlife/dat/mcc/presentation` |
|
||||
| Tool 어노테이션 | `dat-was-lib/src/main/java/io/shinhanlife/dat/lib/annotation` |
|
||||
| JSON Schema/검증 | `dat-was-lib/src/main/java/io/shinhanlife/dat/lib/schema`, `.../validation` |
|
||||
| Manifest | `dat-was-lib/src/main/java/io/shinhanlife/dat/lib/manifest` |
|
||||
| Tool Scaffold | `dat-was-lib/src/main/java/io/shinhanlife/dat/lib/util/ToolScaffolder.java` |
|
||||
| Gateway 라우터 | `dat-gateway/src/main/java/io/shinhanlife/dat/mcg/presentation/McpRouterController.java` |
|
||||
| Customer Tool | `dat-was-cus/src/main/java` |
|
||||
| Sales Tool | `dat-was-sal/src/main/java` |
|
||||
| Process Tool | `dat-was-pro/src/main/java` |
|
||||
| System Tool | `dat-was-sys/src/main/java` |
|
||||
## 13. 모듈명 전환 검증
|
||||
|
||||
Tool 관련 공통 기능은 `dap-was-*` 모듈명만 기준으로 동작합니다.
|
||||
Tool 관련 공통 기능은 `dat-was-*` 모듈명만 기준으로 동작합니다.
|
||||
|
||||
- `validateMcpToolNames`는 `dap-was-*` Tool Pod를 탐색하여 이름 규칙과 전역 중복을 검사합니다.
|
||||
- `validateMcpToolNames`는 `dat-was-*` Tool Pod를 탐색하여 이름 규칙과 전역 중복을 검사합니다.
|
||||
- Tool Scaffold는 Pod 이름을 Tool 함수명에 포함하지 않습니다. 함수명은 `도메인_비즈니스_행위` 형식입니다. 예: `cmm_notification_send`
|
||||
- Pod Scaffold와 Gateway Scaffold 화면/API의 모듈 목록도 `dap-was-*` 명칭으로 통일되어 있습니다.
|
||||
- Tool Source Update 기능은 `dap-was-*` 아래의 `*UseCase.java`를 검색합니다.
|
||||
- Pod Scaffold와 Gateway Scaffold 화면/API의 모듈 목록도 `dat-was-*` 명칭으로 통일되어 있습니다.
|
||||
- Tool Source Update 기능은 `dat-was-*` 아래의 `*UseCase.java`를 검색합니다.
|
||||
|
||||
## 14. BC-DAB-STD-003 Tool Schema V17 적용
|
||||
|
||||
각 Tool의 표준 명세는 Tool Pod별 다음 경로에서 관리합니다.
|
||||
|
||||
```text
|
||||
dap-was-{pod}/src/main/resources/tool-definitions/{categoryKey}/{toolName}.yml
|
||||
dat-was-{pod}/src/main/resources/tool-definitions/{categoryKey}/{toolName}.yml
|
||||
```
|
||||
|
||||
Tool 이름은 Pod 정보를 포함하지 않는 `도메인_서비스_행위` 형태의 영문 소문자 snake_case를 사용하며,
|
||||
@@ -318,11 +346,13 @@ destructive, idempotent), `parameters_schema`입니다. 입력 Schema는 루트
|
||||
|
||||
입력 Schema 우선순위는 `inputSchemaResource` → V17 `parameters_schema` → DTO 자동 생성이고, 출력은
|
||||
`outputSchemaResource` → 명시 Output Schema → `@McpOutputSchema` 기반 생성입니다. Output Schema를
|
||||
명시한 Tool만 최종 응답 검증을 수행합니다.
|
||||
명시한 Tool만 최종 응답 검증을 수행합니다. 현재 Scaffold는 Tool Definition V17 YAML과 응답 Mock JSON을
|
||||
자동 생성하지 않고, 생성한 Java Tool의 `@GrowToolHint`를 기준으로 메타데이터를 제공합니다.
|
||||
|
||||
Scaffold 화면의 `Tool Schema V17 Metadata` 영역에서는 기능 설명, 사용/비사용 조건, 입출력 제한,
|
||||
표시 설명, 예시 질의, 태그와 소유 조직을 입력합니다. Java 소스와 함께 V17 YAML이 생성되며, 기본값은
|
||||
배포 전에 업무 담당자가 실제 의미에 맞게 검토해야 합니다.
|
||||
Scaffold의 Pod Module 생성은 `local`, `dev`, `test`, `prod` 프로필 파일과
|
||||
`src/main/resources/tool-service-manifest.yml`을 만듭니다. 이 파일은 라우팅용 설정이며 `/tool-manifest`
|
||||
API 응답을 대체하지 않습니다. AI 초안은 한국어 YAML로 생성하며, 선택한 Target Module 목록에서 자기
|
||||
자신을 제외해 `confusable-servers`에 반영합니다. Tool Function의 기본 Protocol은 MCI(Legacy)입니다.
|
||||
|
||||
```powershell
|
||||
.\gradlew.bat validateMcpToolNames validateToolSchemaV17
|
||||
|
||||
7
TestClientTransport.java
Normal file
7
TestClientTransport.java
Normal file
@@ -0,0 +1,7 @@
|
||||
import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport;
|
||||
public class TestClientTransport {
|
||||
public static void main(String[] args) {
|
||||
HttpClientStreamableHttpTransport t = HttpClientStreamableHttpTransport.builder("http://localhost:8086/mcp").build();
|
||||
System.out.println("Wait, I can't print private fields easily, but let's just see if it runs.");
|
||||
}
|
||||
}
|
||||
22
TestMcpCall.java
Normal file
22
TestMcpCall.java
Normal file
@@ -0,0 +1,22 @@
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
|
||||
public class TestMcpCall {
|
||||
public static void main(String[] args) throws Exception {
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
String json = "{\"jsonrpc\":\"2.0\",\"method\":\"tools/call\",\"params\":{\"name\":\"iam_team_contact\",\"arguments\":{\"target\":\"전체\"}},\"id\":1}";
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create("http://localhost:8086/mcp"))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Authorization", "Bearer dapms") // just in case
|
||||
.POST(HttpRequest.BodyPublishers.ofString(json))
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
System.out.println("STATUS: " + response.statusCode());
|
||||
System.out.println("BODY: " + response.body());
|
||||
}
|
||||
}
|
||||
39
TestMcpClient.java
Normal file
39
TestMcpClient.java
Normal file
@@ -0,0 +1,39 @@
|
||||
import io.modelcontextprotocol.client.McpClient;
|
||||
import io.modelcontextprotocol.client.McpSyncClient;
|
||||
import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport;
|
||||
import io.modelcontextprotocol.spec.McpSchema;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
public class TestMcpClient {
|
||||
public static void main(String[] args) throws Exception {
|
||||
String endpoint = "http://localhost:8086/mcp";
|
||||
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder();
|
||||
requestBuilder.header("Authorization", "Bearer dapms");
|
||||
|
||||
HttpClientStreamableHttpTransport transport = HttpClientStreamableHttpTransport.builder(endpoint)
|
||||
.requestBuilder(requestBuilder)
|
||||
.connectTimeout(Duration.ofSeconds(5))
|
||||
.build();
|
||||
|
||||
try (McpSyncClient client = McpClient.sync(transport)
|
||||
.clientInfo(new McpSchema.Implementation("test-client", "1.0.0"))
|
||||
.requestTimeout(Duration.ofSeconds(30))
|
||||
.build()) {
|
||||
System.out.println("Initializing client...");
|
||||
client.initialize();
|
||||
System.out.println("Client initialized.");
|
||||
|
||||
System.out.println("Calling tool...");
|
||||
McpSchema.CallToolResult result = client.callTool(McpSchema.CallToolRequest.builder()
|
||||
.name("iam_team_contact")
|
||||
.arguments(Map.of("target", "전체"))
|
||||
.build());
|
||||
System.out.println("Result: " + result);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
TestReflection.class
Normal file
BIN
TestReflection.class
Normal file
Binary file not shown.
11
TestReflection.java
Normal file
11
TestReflection.java
Normal file
@@ -0,0 +1,11 @@
|
||||
import java.lang.reflect.Method;
|
||||
import io.modelcontextprotocol.server.transport.HttpServletStreamableServerTransportProvider;
|
||||
|
||||
public class TestReflection {
|
||||
public static void main(String[] args) throws Exception {
|
||||
Class<?> clazz = HttpServletStreamableServerTransportProvider.Builder.class;
|
||||
for (Method m : clazz.getDeclaredMethods()) {
|
||||
System.out.println(m.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
20
TestSseEndpoint.java
Normal file
20
TestSseEndpoint.java
Normal file
@@ -0,0 +1,20 @@
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
|
||||
public class TestSseEndpoint {
|
||||
public static void main(String[] args) throws Exception {
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create("http://localhost:8086/mcp"))
|
||||
.header("Accept", "text/event-stream")
|
||||
.header("X-Tool-Server-API-Key", "test")
|
||||
.GET()
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
System.out.println("STATUS: " + response.statusCode());
|
||||
System.out.println("BODY: " + response.body());
|
||||
}
|
||||
}
|
||||
230
build.gradle
230
build.gradle
@@ -1,113 +1,181 @@
|
||||
// 루트 Gradle 설정: 모든 Gateway/Tool Pod 모듈이 공유하는 빌드 기준을 정의합니다.
|
||||
plugins {
|
||||
// Java 컴파일, 테스트, JAR 생성 기능을 제공합니다.
|
||||
id 'java'
|
||||
|
||||
// 하위 실행 모듈에서 bootRun/bootJar를 사용하기 위한 Spring Boot 플러그인입니다.
|
||||
// 루트 프로젝트에는 적용하지 않으므로 apply false를 사용합니다.
|
||||
id 'org.springframework.boot' version '3.5.11' apply false
|
||||
|
||||
// Spring Boot / Spring AI BOM에 정의된 라이브러리 버전을 일관되게 적용합니다.
|
||||
id 'io.spring.dependency-management' version '1.1.6' apply false
|
||||
id 'org.springframework.boot' version '3.5.11'
|
||||
id 'io.spring.dependency-management' version '1.1.7'
|
||||
}
|
||||
|
||||
// 모든 모듈이 공유하는 Maven 식별자입니다.
|
||||
|
||||
// ============================================================================
|
||||
// 전체 프로젝트 공통 Maven 좌표
|
||||
// ============================================================================
|
||||
|
||||
allprojects {
|
||||
|
||||
// 모든 모듈이 공유하는 Maven Group ID입니다.
|
||||
group = 'io.shinhanlife'
|
||||
|
||||
// 모든 모듈이 공유하는 프로젝트 버전입니다.
|
||||
version = '0.0.1-SNAPSHOT'
|
||||
}
|
||||
|
||||
// dap-gateway, dap-was-lib, dap-was-oth, dap-was-sms에 공통 적용합니다.
|
||||
|
||||
subprojects {
|
||||
|
||||
// 모든 하위 모듈에서 Java Plugin을 사용합니다.
|
||||
apply plugin: 'java'
|
||||
|
||||
// Spring Boot BOM 기반 의존성 버전 관리를 위해 적용합니다.
|
||||
apply plugin: 'io.spring.dependency-management'
|
||||
|
||||
java {
|
||||
// 프로젝트 표준 Java 버전입니다.
|
||||
sourceCompatibility = '21'
|
||||
}
|
||||
|
||||
repositories {
|
||||
// 현재 공개 정식 라이브러리 저장소입니다.
|
||||
// 폐쇄망 적용 시 사내 Nexus Proxy URL로 교체합니다.
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencyManagement {
|
||||
imports {
|
||||
// Spring Boot 3.5.11과 호환되는 Spring/Jackson/Tomcat 등의 버전을 관리합니다.
|
||||
mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES
|
||||
|
||||
// Boot 3.5.11과 호환되는 Spring AI 계층은 1.1.8로 유지합니다.
|
||||
mavenBom 'org.springframework.ai:spring-ai-bom:1.1.8'
|
||||
|
||||
// MCP Java SDK만 2.0.0으로 올립니다. Spring AI 2.x 전체 BOM은 Boot 4 기반이므로 사용하지 않습니다.
|
||||
mavenBom 'io.modelcontextprotocol.sdk:mcp-bom:2.0.0'
|
||||
|
||||
// MCP SDK 2.0의 Jackson 2 전송 모듈이 요구하는 호환 버전입니다.
|
||||
mavenBom 'com.fasterxml.jackson:jackson-bom:2.20.1'
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(21)
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// 외부 개발 환경
|
||||
// --------------------------------------------------------------------
|
||||
//
|
||||
// 현재 인터넷이 가능한 환경에서는 Maven Central을 사용합니다.
|
||||
//
|
||||
// 신한라이프 폐쇄망 반입 시 아래 mavenCentral()은 제거하고
|
||||
// 사내 Nexus Repository URL로 교체해야 합니다.
|
||||
//
|
||||
// 예:
|
||||
//
|
||||
// maven {
|
||||
// url = uri('https://내부-Nexus/repository/maven-public/')
|
||||
// }
|
||||
//
|
||||
// Nexus 인증이 필요한 경우에는 credentials 설정도 추가해야 합니다.
|
||||
//
|
||||
// credentials {
|
||||
// username = findProperty('nexusUsername') ?: System.getenv('NEXUS_USERNAME')
|
||||
// password = findProperty('nexusPassword') ?: System.getenv('NEXUS_PASSWORD')
|
||||
// }
|
||||
//
|
||||
// 계정/비밀번호를 build.gradle에 직접 작성하면 안 됩니다.
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Dependency Version Management
|
||||
// ========================================================================
|
||||
|
||||
dependencyManagement {
|
||||
|
||||
imports {
|
||||
|
||||
// Spring Boot 3.5.11이 검증한
|
||||
// Spring Framework / Jackson / Tomcat / Logback /
|
||||
// JUnit 등 주요 라이브러리 버전을 공통 관리합니다.
|
||||
// 하위 모듈에서는 Spring Boot가 관리하는 라이브러리라면
|
||||
// 가급적 개별 버전을 직접 지정하지 않고 사용합니다.
|
||||
mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES
|
||||
|
||||
// Gateway에서 사용하는 Spring AI 의존성 버전 관리
|
||||
mavenBom 'org.springframework.ai:spring-ai-bom:1.1.8'
|
||||
|
||||
// MCP SDK 버전 관리
|
||||
mavenBom 'io.modelcontextprotocol.sdk:mcp-bom:2.0.0'
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Common Dependencies
|
||||
// ========================================================================
|
||||
|
||||
dependencies {
|
||||
// Lombok은 컴파일 시 getter/builder 등 반복 코드를 생성하며 실행 JAR에는 포함하지 않습니다.
|
||||
compileOnly 'org.projectlombok:lombok:1.18.32'
|
||||
annotationProcessor 'org.projectlombok:lombok:1.18.32'
|
||||
testCompileOnly 'org.projectlombok:lombok:1.18.32'
|
||||
testAnnotationProcessor 'org.projectlombok:lombok:1.18.32'
|
||||
|
||||
// Tool DTO와 MCI 요청/응답 객체 간 Converter 구현체를 컴파일 시 자동 생성합니다.
|
||||
implementation 'org.mapstruct:mapstruct:1.5.5.Final'
|
||||
annotationProcessor 'org.projectlombok:lombok-mapstruct-binding:0.2.0'
|
||||
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final'
|
||||
// ====================================================================
|
||||
// Lombok
|
||||
// ====================================================================
|
||||
|
||||
// 모든 모듈의 JUnit 5 기반 테스트 공통 의존성입니다.
|
||||
// Lombok은 컴파일 시 Getter / Setter / Builder 등의
|
||||
// 반복 코드를 생성합니다.
|
||||
//
|
||||
// compileOnly이므로 Runtime JAR에는 포함되지 않습니다.
|
||||
compileOnly 'org.projectlombok:lombok'
|
||||
|
||||
// Lombok Annotation Processor입니다.
|
||||
annotationProcessor 'org.projectlombok:lombok'
|
||||
|
||||
// 테스트 코드에서도 Lombok Annotation을 사용할 수 있도록 합니다.
|
||||
testCompileOnly 'org.projectlombok:lombok'
|
||||
|
||||
// 테스트 코드용 Lombok Annotation Processor입니다.
|
||||
testAnnotationProcessor 'org.projectlombok:lombok'
|
||||
|
||||
|
||||
// ====================================================================
|
||||
// MapStruct
|
||||
// ====================================================================
|
||||
|
||||
// Tool DTO와
|
||||
// MCI 요청/응답 DTO 사이의 Converter 구현체를
|
||||
// 컴파일 시 자동 생성하기 위해 사용합니다.
|
||||
implementation 'org.mapstruct:mapstruct:1.6.3'
|
||||
|
||||
// MapStruct Converter 구현 클래스를 생성하는
|
||||
// Annotation Processor입니다.
|
||||
annotationProcessor 'org.mapstruct:mapstruct-processor:1.6.3'
|
||||
|
||||
|
||||
// ====================================================================
|
||||
// Test
|
||||
// ====================================================================
|
||||
|
||||
// Spring Boot Test / JUnit 5 / Mockito / AssertJ 등
|
||||
// 공통 테스트 환경을 제공합니다.
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
|
||||
// Gradle / IDE 환경에서 JUnit Platform 기반 테스트 실행을 지원합니다.
|
||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
// 리플렉션/MCP Schema 생성 시 메서드 파라미터명을 사용할 수 있도록 보존합니다.
|
||||
|
||||
// ========================================================================
|
||||
// Java Compile Options
|
||||
// ========================================================================
|
||||
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
|
||||
// Reflection 및 MCP Schema 생성 시
|
||||
// Java 메서드의 실제 파라미터명을 사용할 수 있도록
|
||||
// 컴파일 결과에 파라미터 이름을 보존합니다.
|
||||
options.compilerArgs << '-parameters'
|
||||
|
||||
// MapStruct 구현체를 Spring Bean으로 생성해 생성자 주입으로 사용할 수 있게 합니다.
|
||||
// MapStruct가 생성하는 Converter 구현 클래스를
|
||||
// Spring Bean(@Component)으로 생성합니다.
|
||||
//
|
||||
// 이를 통해 생성자 주입 등의 방식으로 사용할 수 있습니다.
|
||||
options.compilerArgs << '-Amapstruct.defaultComponentModel=spring'
|
||||
}
|
||||
|
||||
tasks.withType(Test) {
|
||||
// JUnit 5 테스트 플랫폼을 사용합니다.
|
||||
|
||||
// ========================================================================
|
||||
// Test Configuration
|
||||
// ========================================================================
|
||||
|
||||
tasks.withType(Test).configureEach {
|
||||
|
||||
// 모든 하위 모듈에서 JUnit 5 Platform을 사용합니다.
|
||||
useJUnitPlatform()
|
||||
}
|
||||
}
|
||||
|
||||
// Tool 이름 중복 검사 프로그램이 포함된 공통 라이브러리 모듈입니다.
|
||||
def toolCoreProject = project(':dap-was-lib')
|
||||
|
||||
// 전체 Tool Pod의 @McpTool(name) 중복을 배포 산출물 생성 전에 차단합니다.
|
||||
tasks.register('validateMcpToolNames', JavaExec) {
|
||||
group = 'verification'
|
||||
description = 'Checks duplicate @McpTool names across all Tool modules before packaging.'
|
||||
|
||||
// 검사 Runner를 실행하기 전에 dap-was-lib 클래스를 먼저 컴파일합니다.
|
||||
dependsOn toolCoreProject.tasks.named('classes')
|
||||
classpath = toolCoreProject.sourceSets.main.runtimeClasspath
|
||||
mainClass.set('io.shinhanlife.dap.lib.validation.McpToolNameValidationRunner')
|
||||
args rootProject.projectDir.absolutePath
|
||||
}
|
||||
|
||||
tasks.register('validateToolSchemaV17', JavaExec) {
|
||||
group = 'verification'
|
||||
description = 'Validates BC-DAB-STD-003 V17 definitions for every @McpTool.'
|
||||
dependsOn toolCoreProject.tasks.named('classes')
|
||||
classpath = toolCoreProject.sourceSets.main.runtimeClasspath
|
||||
mainClass.set('io.shinhanlife.dap.lib.validation.ToolSchemaV17ValidationRunner')
|
||||
args rootProject.projectDir.absolutePath
|
||||
}
|
||||
|
||||
subprojects {
|
||||
// 배포용 Spring Boot JAR 생성 전에 Tool 이름 중복 검증을 강제합니다.
|
||||
tasks.matching { it.name == 'bootJar' }.configureEach {
|
||||
dependsOn rootProject.tasks.named('validateMcpToolNames')
|
||||
dependsOn rootProject.tasks.named('validateToolSchemaV17')
|
||||
}
|
||||
}
|
||||
|
||||
task exportAllLibs(type: Copy) {
|
||||
into 'all-exported-libs'
|
||||
subprojects {
|
||||
from(it.configurations.findByName('runtimeClasspath'))
|
||||
}
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
|
||||
BIN
build/libs/dap-admin-0.0.1-SNAPSHOT.jar
Normal file
BIN
build/libs/dap-admin-0.0.1-SNAPSHOT.jar
Normal file
Binary file not shown.
663
build/reports/problems/problems-report.html
Normal file
663
build/reports/problems/problems-report.html
Normal file
File diff suppressed because one or more lines are too long
2
build/tmp/jar/MANIFEST.MF
Normal file
2
build/tmp/jar/MANIFEST.MF
Normal file
@@ -0,0 +1,2 @@
|
||||
Manifest-Version: 1.0
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
package io.shinhanlife.dap.mcg.presentation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.ContentDisposition;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.client.RestClientResponseException;
|
||||
|
||||
/**
|
||||
* Gateway 화면에서 들어온 DTO 엑셀 요청을 실제 DTO 클래스가 있는 Tool Pod로 전달한다.
|
||||
* 브라우저가 동일 출처(8081)만 호출하도록 하여 CORS와 배포 주소 차이를 숨긴다.
|
||||
*/
|
||||
@RestController
|
||||
public class DtoDownloadProxyController {
|
||||
|
||||
private static final MediaType XLSX_MEDIA_TYPE = MediaType.parseMediaType(
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
||||
|
||||
private final RestClient restClient;
|
||||
private final String toolPodUrl;
|
||||
|
||||
public DtoDownloadProxyController(
|
||||
RestClient.Builder restClientBuilder,
|
||||
@Value("${mcp.gateway.fallback.default-url:http://localhost:8084}") String toolPodUrl) {
|
||||
this.restClient = restClientBuilder.build();
|
||||
this.toolPodUrl = toolPodUrl.replaceAll("/+$", "");
|
||||
}
|
||||
|
||||
@GetMapping("/dto-download/options")
|
||||
public List<String> options() {
|
||||
// 셀렉트박스에 표시할 DTO 이름 목록을 Tool Pod에서 조회한다.
|
||||
List<String> options = restClient.get()
|
||||
.uri(toolPodUrl + "/dto-download/options")
|
||||
.retrieve()
|
||||
.body(new ParameterizedTypeReference<>() {});
|
||||
return options == null ? List.of() : options;
|
||||
}
|
||||
|
||||
@GetMapping("/dto-download/{dtoName}")
|
||||
public ResponseEntity<byte[]> download(@PathVariable String dtoName) {
|
||||
byte[] workbook;
|
||||
try {
|
||||
// 생성된 엑셀 바이트를 그대로 브라우저에 전달한다.
|
||||
workbook = restClient.get()
|
||||
.uri(toolPodUrl + "/dto-download/{dtoName}", dtoName)
|
||||
.retrieve()
|
||||
.body(byte[].class);
|
||||
} catch (RestClientResponseException error) {
|
||||
// 형식 불일치 등의 상태 코드와 오류 메시지도 변경 없이 전달한다.
|
||||
return ResponseEntity.status(error.getStatusCode())
|
||||
.contentType(error.getResponseHeaders() != null
|
||||
&& error.getResponseHeaders().getContentType() != null
|
||||
? error.getResponseHeaders().getContentType() : MediaType.TEXT_PLAIN)
|
||||
.body(error.getResponseBodyAsByteArray());
|
||||
}
|
||||
String fileName = dtoName + ".xlsx";
|
||||
return ResponseEntity.ok()
|
||||
.contentType(XLSX_MEDIA_TYPE)
|
||||
.contentLength(workbook == null ? 0 : workbook.length)
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION,
|
||||
ContentDisposition.attachment().filename(fileName).build().toString())
|
||||
.body(workbook == null ? new byte[0] : workbook);
|
||||
}
|
||||
}
|
||||
@@ -1,432 +0,0 @@
|
||||
package io.shinhanlife.dap.mcg.presentation;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.mcg.presentation
|
||||
* @className ScaffoldingController
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 0986406 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
import io.shinhanlife.dap.lib.util.PodScaffolder;
|
||||
import io.shinhanlife.dap.lib.util.ToolScaffolder;
|
||||
import io.shinhanlife.dap.lib.util.ToolSourceUpdater;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.io.File;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/scaffold")
|
||||
public class ScaffoldingController {
|
||||
|
||||
private static final Set<String> SUPPORTED_FIELD_TYPES = Set.of(
|
||||
"String", "Integer", "Long", "Double", "Boolean", "BigDecimal", "List");
|
||||
private static final Set<String> SUPPORTED_AI_MODELS = Set.of(
|
||||
"inclusionai/ling-3.0-flash:free",
|
||||
"openai/gpt-oss-20b:free",
|
||||
"google/gemma-4-31b-it:free",
|
||||
"nvidia/nemotron-3-nano-30b-a3b:free",
|
||||
"cohere/north-mini-code:free");
|
||||
|
||||
private final ChatClient.Builder chatClientBuilder;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public ScaffoldingController(ChatClient.Builder chatClientBuilder, ObjectMapper objectMapper) {
|
||||
this.chatClientBuilder = chatClientBuilder;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@PostMapping("/pod")
|
||||
public String scaffoldPod(@RequestBody Map<String, String> req) {
|
||||
try {
|
||||
String moduleName = req.getOrDefault("moduleName", "dap-was-cus");
|
||||
if (!moduleName.startsWith("dap-was-")) moduleName = "dap-was-" + moduleName;
|
||||
String port = req.getOrDefault("port", "8085");
|
||||
String shortName = moduleName.replace("dap-was-", "").replace("-", "");
|
||||
String author = req.get("author");
|
||||
if (author == null || author.trim().isEmpty()) author = System.getProperty("user.name");
|
||||
String date = req.get("date");
|
||||
if (date == null || date.trim().isEmpty()) date = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy.MM.dd"));
|
||||
|
||||
return PodScaffolder.scaffoldPod(moduleName, port, shortName, author, date);
|
||||
} catch (Exception e) {
|
||||
return "오류 발생: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/tool")
|
||||
public String scaffoldTool(@RequestBody Map<String, String> req) {
|
||||
try {
|
||||
String baseName = req.get("baseName");
|
||||
String interfaceId = req.get("interfaceId");
|
||||
String title = req.get("title");
|
||||
if (title == null || title.isBlank()) title = baseName;
|
||||
String description = req.get("description");
|
||||
String group = req.getOrDefault("categoryKey", req.getOrDefault("group", "COMMON"));
|
||||
String routingType = req.getOrDefault("routingType", "HTTP");
|
||||
String moduleName = req.getOrDefault("moduleName", "dap-was-cus");
|
||||
String author = req.get("author");
|
||||
if (author == null || author.trim().isEmpty()) author = System.getProperty("user.name");
|
||||
String date = req.get("date");
|
||||
if (date == null || date.trim().isEmpty()) date = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy.MM.dd"));
|
||||
boolean register = Boolean.parseBoolean(req.getOrDefault("register", "true"));
|
||||
String clientSystemCode = req.get("clientSystemCode");
|
||||
String httpApiName = req.get("httpApiName");
|
||||
String inputSchemaResource = req.get("inputSchemaResource");
|
||||
String outputSchemaResource = req.get("outputSchemaResource");
|
||||
List<ToolScaffolder.FieldDefinition> inputFields = parseFields(req.get("inputFields"));
|
||||
List<ToolScaffolder.FieldDefinition> outputFields = parseFields(req.get("outputFields"));
|
||||
if (inputFields.isEmpty()) {
|
||||
inputFields = List.of(new ToolScaffolder.FieldDefinition("query", "String", "Search query", List.of("example"), "", false));
|
||||
}
|
||||
ToolScaffolder.ToolDefinitionOptions definitionOptions = new ToolScaffolder.ToolDefinitionOptions(
|
||||
req.get("functionDescription"),
|
||||
req.get("whenToUse"),
|
||||
req.get("whenNotToUse"),
|
||||
req.get("ioLimits"),
|
||||
req.get("displayDescription"),
|
||||
parseDelimited(req.get("exampleQueries")),
|
||||
parseDelimited(req.get("tags")),
|
||||
req.get("ownerOrg"));
|
||||
|
||||
return ToolScaffolder.scaffold(baseName, interfaceId, title, description, group, routingType,
|
||||
moduleName, author, date, register, clientSystemCode, inputSchemaResource,
|
||||
outputSchemaResource, inputFields, outputFields, httpApiName, definitionOptions);
|
||||
} catch (Exception e) {
|
||||
return "오류 발생: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/tool-group")
|
||||
public String scaffoldToolGroup(@RequestBody ToolGroupRequest request) {
|
||||
try {
|
||||
if (request == null || request.useCaseName() == null
|
||||
|| !request.useCaseName().trim().matches("^[A-Z][A-Za-z0-9]*$")) {
|
||||
throw new IllegalArgumentException("UseCase name must be PascalCase.");
|
||||
}
|
||||
String moduleName = request.moduleName() == null || request.moduleName().isBlank()
|
||||
? "dap-was-cus" : request.moduleName().trim();
|
||||
String author = request.author() == null || request.author().isBlank()
|
||||
? System.getProperty("user.name") : request.author().trim();
|
||||
String date = request.date() == null || request.date().isBlank()
|
||||
? LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy.MM.dd")) : request.date().trim();
|
||||
return ToolScaffolder.scaffoldUseCase(request.useCaseName().trim(), moduleName, author, date,
|
||||
request.tools() == null ? List.of() : request.tools());
|
||||
} catch (Exception e) {
|
||||
return "Error: " + safeMessage(e);
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/usecases")
|
||||
public List<String> listUseCases(@RequestParam String moduleName, @RequestParam String categoryKey) {
|
||||
if (moduleName == null || !moduleName.matches("^dap-was-[a-z0-9-]+$")) {
|
||||
throw new IllegalArgumentException("Invalid target module.");
|
||||
}
|
||||
if (categoryKey == null || !categoryKey.matches("^[a-z0-9]{3}$")) {
|
||||
throw new IllegalArgumentException("Invalid domain category.");
|
||||
}
|
||||
String sourceDir = System.getenv("AXHUB_SOURCE_DIR");
|
||||
if (sourceDir == null || sourceDir.isBlank()) sourceDir = System.getProperty("user.dir");
|
||||
File useCaseDir = new File(sourceDir, moduleName + "/src/main/java/io/shinhanlife/dap/mcc/biz/"
|
||||
+ categoryKey + "/usecase");
|
||||
File[] files = useCaseDir.listFiles(file -> file.isFile() && file.getName().endsWith("UseCase.java"));
|
||||
if (files == null) return List.of();
|
||||
return Arrays.stream(files).map(File::getName)
|
||||
.map(name -> name.substring(0, name.length() - ".java".length()))
|
||||
.sorted().toList();
|
||||
}
|
||||
|
||||
@GetMapping("/categories")
|
||||
public List<String> listCategories(@RequestParam String moduleName) {
|
||||
if (moduleName == null || !moduleName.matches("^dap-was-[a-z0-9-]+$")) {
|
||||
throw new IllegalArgumentException("Invalid target module.");
|
||||
}
|
||||
String sourceDir = System.getenv("AXHUB_SOURCE_DIR");
|
||||
if (sourceDir == null || sourceDir.isBlank()) sourceDir = System.getProperty("user.dir");
|
||||
File bizDir = new File(sourceDir, moduleName + "/src/main/java/io/shinhanlife/dap/mcc/biz");
|
||||
File[] dirs = bizDir.listFiles(File::isDirectory);
|
||||
if (dirs == null) return List.of();
|
||||
return Arrays.stream(dirs).map(File::getName)
|
||||
.filter(name -> name.matches("^[a-z0-9]{3}$"))
|
||||
.sorted().toList();
|
||||
}
|
||||
|
||||
@PostMapping("/field-draft")
|
||||
public ResponseEntity<?> generateFieldDraft(@RequestBody Map<String, String> req) {
|
||||
String description = req.getOrDefault("description", "").trim();
|
||||
String target = req.getOrDefault("target", "inputFields");
|
||||
if (description.isBlank()) {
|
||||
return ResponseEntity.badRequest().body(Map.of("error", "설명을 입력해주세요."));
|
||||
}
|
||||
if (!"inputFields".equals(target) && !"outputFields".equals(target)) {
|
||||
return ResponseEntity.badRequest().body(Map.of("error", "지원하지 않는 필드 대상입니다."));
|
||||
}
|
||||
|
||||
try {
|
||||
String targetLabel = "inputFields".equals(target) ? "INPUT request DTO" : "OUTPUT response DTO";
|
||||
String prompt = """
|
||||
Generate Java DTO fields for an MCP tool.
|
||||
Return JSON only. Do not add Markdown, explanations, or code fences.
|
||||
The response must have this exact shape:
|
||||
{"fields":[{"name":"camelCaseName","type":"String","description":"short description","examples":["example1","example2"],"pattern":"^regex$","required":true,"enumValues":[],"itemType":null,"itemFields":[]}]}
|
||||
Allowed type values: String, Integer, Long, Double, Boolean, BigDecimal, List.
|
||||
Finite values should be enforced by populating enumValues on standard types. List fields must include itemType; use Object plus itemFields for object lists.
|
||||
If applicable, provide a regex for pattern.
|
||||
Generate fields only for the requested target: %s.
|
||||
For OUTPUT fields, include resultCode and resultMessage when appropriate.
|
||||
Keep field names valid Java camelCase identifiers. Generate at most 10 fields.
|
||||
User description: %s
|
||||
""".formatted(targetLabel, description);
|
||||
|
||||
String response = generateAiContent(prompt, req.get("model"));
|
||||
FieldDraft draft = objectMapper.readValue(stripCodeFence(response), FieldDraft.class);
|
||||
List<ToolScaffolder.FieldDefinition> fields = validateFields(draft.fields());
|
||||
return ResponseEntity.ok(Map.of("fields", fields));
|
||||
} catch (Exception e) {
|
||||
return ResponseEntity.internalServerError().body(Map.of("error", "AI 초안 생성 실패: " + safeMessage(e)));
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/tool-draft")
|
||||
public ResponseEntity<?> generateToolDraft(@RequestBody Map<String, String> req) {
|
||||
String description = req.getOrDefault("description", "").trim();
|
||||
if (description.isBlank()) {
|
||||
return ResponseEntity.badRequest().body(Map.of("error", "Tool 설명을 입력해주세요."));
|
||||
}
|
||||
|
||||
try {
|
||||
String prompt = """
|
||||
Generate an MCP Tool scaffold from the user request.
|
||||
Return JSON only. Do not add Markdown, explanations, or code fences.
|
||||
The response must have this exact shape:
|
||||
{"baseName":"PascalCaseName","title":"short Korean title","description":"clear Korean LLM tool guidance","categoryKey":"cmm","routingType":"HTTP","httpApiName":"simple-api-name","functionDescription":"core business function","displayDescription":"short portal description","whenToUse":"specific user requests that should select this tool","whenNotToUse":"requests or conditions that must not select this tool","ioLimits":"allowed input and output scope and limits","exampleQueries":["query 1","query 2","query 3"],"tags":["domain","action"],"ownerOrg":"MCP_TOOL","inputFields":[{"name":"camelCaseName","type":"String","description":"short description","examples":["example1","example2"],"pattern":"^regex$","required":true,"enumValues":[],"itemType":null,"itemFields":[]}],"outputFields":[{"name":"resultCode","type":"String","description":"result code","examples":["SUCCESS"],"pattern":"","required":true,"enumValues":[],"itemType":null,"itemFields":[]}]}
|
||||
categoryKey must be exactly three lowercase letters or digits.
|
||||
routingType must be either HTTP or MCI. httpApiName can contain only letters, digits, hyphens, and underscores.
|
||||
Write every V17 metadata field for its distinct purpose; do not copy the same sentence into all fields.
|
||||
Generate 3 to 10 realistic exampleQueries and concise search tags. Use MCP_TOOL for ownerOrg unless the user names an owner.
|
||||
Allowed field type values: String, Integer, Long, Double, Boolean, BigDecimal, List. Finite values should be enforced by populating enumValues. List must include itemType and object lists include itemFields.
|
||||
If applicable, provide a regex for pattern.
|
||||
Keep all field names valid Java camelCase identifiers. Generate at most 10 fields per list.
|
||||
Do not generate interfaceId or clientSystemCode; those must come from a real integration contract.
|
||||
User request: %s
|
||||
""".formatted(description);
|
||||
|
||||
String response = generateAiContent(prompt, req.get("model"));
|
||||
ToolDraft draft = objectMapper.readValue(stripCodeFence(response), ToolDraft.class);
|
||||
ToolDraft validatedDraft = validateToolDraft(draft);
|
||||
return ResponseEntity.ok(validatedDraft);
|
||||
} catch (Exception e) {
|
||||
return ResponseEntity.internalServerError().body(Map.of("error", "AI Tool 초안 생성 실패: " + safeMessage(e)));
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/tool/update")
|
||||
public String updateTool(@RequestBody Map<String, String> req) {
|
||||
try {
|
||||
String toolName = req.get("toolName");
|
||||
String domainGroup = req.getOrDefault("categoryKey", req.get("domainGroup"));
|
||||
String description = req.get("description");
|
||||
boolean register = Boolean.parseBoolean(req.getOrDefault("register", "true"));
|
||||
Boolean requiresApproval = req.containsKey("requiresApproval") ? Boolean.parseBoolean(req.get("requiresApproval")) : null;
|
||||
|
||||
ToolSourceUpdater.updateToolSource(toolName, domainGroup, description, register, requiresApproval);
|
||||
return "성공";
|
||||
} catch (Exception e) {
|
||||
return "오류 발생: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/modules")
|
||||
public List<String> listModules() {
|
||||
try {
|
||||
String sourceDir = System.getenv("AXHUB_SOURCE_DIR");
|
||||
if (sourceDir == null) sourceDir = System.getProperty("user.dir");
|
||||
|
||||
File dir = new File(sourceDir);
|
||||
File[] files = dir.listFiles(f -> f.isDirectory() && f.getName().startsWith("dap-was-") && !f.getName().equals("dap-was-lib"));
|
||||
|
||||
if (files == null || files.length == 0) {
|
||||
return List.of("dap-was-cus", "dap-was-sal", "dap-was-pro", "dap-was-sys");
|
||||
}
|
||||
|
||||
return Arrays.stream(files).map(File::getName).sorted().collect(Collectors.toList());
|
||||
} catch (Exception e) {
|
||||
return List.of("dap-was-cus", "dap-was-sal", "dap-was-pro", "dap-was-sys");
|
||||
}
|
||||
}
|
||||
|
||||
private List<ToolScaffolder.FieldDefinition> parseFields(String source) throws Exception {
|
||||
if (source == null || source.isBlank()) {
|
||||
return List.of();
|
||||
}
|
||||
return objectMapper.readValue(source, new TypeReference<List<ToolScaffolder.FieldDefinition>>() { });
|
||||
}
|
||||
|
||||
private List<String> parseDelimited(String source) {
|
||||
if (source == null || source.isBlank()) {
|
||||
return List.of();
|
||||
}
|
||||
return Arrays.stream(source.split("[\\r\\n,]+"))
|
||||
.map(String::trim)
|
||||
.filter(value -> !value.isBlank())
|
||||
.distinct()
|
||||
.toList();
|
||||
}
|
||||
|
||||
private List<ToolScaffolder.FieldDefinition> validateFields(List<ToolScaffolder.FieldDefinition> source) {
|
||||
if (source == null || source.isEmpty()) {
|
||||
throw new IllegalArgumentException("AI가 필드를 생성하지 않았습니다.");
|
||||
}
|
||||
Set<String> names = new LinkedHashSet<>();
|
||||
return source.stream()
|
||||
.limit(10)
|
||||
.filter(field -> field != null && field.name() != null && !field.name().isBlank())
|
||||
.map(field -> new ToolScaffolder.FieldDefinition(
|
||||
field.name().trim(),
|
||||
field.type() == null ? "String" : field.type().trim(),
|
||||
field.description() == null ? "" : field.description().trim(),
|
||||
field.examples() == null ? List.of() : field.examples().stream().map(String::trim).toList(),
|
||||
field.pattern() == null ? "" : field.pattern().trim(),
|
||||
field.required(),
|
||||
field.enumValues() == null ? List.of() : field.enumValues(),
|
||||
field.itemType(),
|
||||
field.itemFields() == null ? List.of() : field.itemFields()))
|
||||
.peek(field -> {
|
||||
if (!field.name().matches("^[A-Za-z_$][A-Za-z0-9_$]*$")) {
|
||||
throw new IllegalArgumentException("AI가 올바르지 않은 필드명을 생성했습니다: " + field.name());
|
||||
}
|
||||
if (!SUPPORTED_FIELD_TYPES.contains(field.type())) {
|
||||
throw new IllegalArgumentException("AI가 지원하지 않는 Type을 생성했습니다: " + field.type());
|
||||
}
|
||||
validateStructuredField(field);
|
||||
if (!names.add(field.name())) {
|
||||
throw new IllegalArgumentException("AI가 중복 필드명을 생성했습니다: " + field.name());
|
||||
}
|
||||
})
|
||||
.toList();
|
||||
}
|
||||
|
||||
private void validateStructuredField(ToolScaffolder.FieldDefinition field) {
|
||||
if ("List".equals(field.type()) && (field.itemType() == null || field.itemType().isBlank())) {
|
||||
throw new IllegalArgumentException("List field needs itemType: " + field.name());
|
||||
}
|
||||
if ("List".equals(field.type()) && "Object".equals(field.itemType()) && field.itemFields().isEmpty()) {
|
||||
throw new IllegalArgumentException("Object List field needs itemFields: " + field.name());
|
||||
}
|
||||
}
|
||||
|
||||
private ToolDraft validateToolDraft(ToolDraft draft) {
|
||||
if (draft == null || draft.baseName() == null || !draft.baseName().trim().matches("^[A-Z][A-Za-z0-9]*$")) {
|
||||
throw new IllegalArgumentException("AI가 올바르지 않은 Base Name을 생성했습니다.");
|
||||
}
|
||||
String categoryKey = draft.categoryKey() == null ? "" : draft.categoryKey().trim().toLowerCase(Locale.ROOT);
|
||||
if (!categoryKey.matches("^[a-z0-9]{3}$")) {
|
||||
throw new IllegalArgumentException("AI가 올바르지 않은 Category Key를 생성했습니다.");
|
||||
}
|
||||
String routingType = draft.routingType() == null ? "" : draft.routingType().trim().toUpperCase(Locale.ROOT);
|
||||
if (!Set.of("HTTP", "MCI").contains(routingType)) {
|
||||
throw new IllegalArgumentException("AI가 지원하지 않는 Protocol을 생성했습니다.");
|
||||
}
|
||||
String httpApiName = draft.httpApiName() == null ? "http-api" : draft.httpApiName().trim();
|
||||
if (!httpApiName.matches("^[A-Za-z0-9_-]+$")) {
|
||||
throw new IllegalArgumentException("AI가 올바르지 않은 HTTP API Name을 생성했습니다.");
|
||||
}
|
||||
String title = draft.title() == null ? "" : draft.title().trim();
|
||||
String description = draft.description() == null ? "" : draft.description().trim();
|
||||
if (title.isBlank() || description.isBlank()) {
|
||||
throw new IllegalArgumentException("AI가 Tool 제목 또는 설명을 생성하지 않았습니다.");
|
||||
}
|
||||
String functionDescription = textOrDefault(draft.functionDescription(), description);
|
||||
String displayDescription = textOrDefault(draft.displayDescription(), title);
|
||||
String whenToUse = textOrDefault(draft.whenToUse(), description + " 요청을 처리할 때 사용한다.");
|
||||
String whenNotToUse = textOrDefault(draft.whenNotToUse(), "필수 입력값이 없거나 다른 업무 요청에는 사용하지 않는다.");
|
||||
String ioLimits = textOrDefault(draft.ioLimits(), "정의된 입력 필드만 허용하며 정의된 응답 DTO 범위만 반환한다.");
|
||||
List<String> exampleQueries = normalizedDraftList(draft.exampleQueries(), List.of(
|
||||
title + " 해줘", title + " 정보를 알려줘", title + " 결과를 확인해줘"));
|
||||
List<String> tags = normalizedDraftList(draft.tags(), List.of(categoryKey));
|
||||
String ownerOrg = textOrDefault(draft.ownerOrg(), "MCP_TOOL");
|
||||
return new ToolDraft(draft.baseName().trim(), title, description, categoryKey, routingType, httpApiName,
|
||||
functionDescription, displayDescription, whenToUse, whenNotToUse, ioLimits,
|
||||
exampleQueries, tags, ownerOrg,
|
||||
validateFields(draft.inputFields()), validateFields(draft.outputFields()));
|
||||
}
|
||||
|
||||
private String textOrDefault(String value, String fallback) {
|
||||
return value == null || value.isBlank() ? fallback : value.trim();
|
||||
}
|
||||
|
||||
private List<String> normalizedDraftList(List<String> values, List<String> fallback) {
|
||||
if (values == null) {
|
||||
return fallback;
|
||||
}
|
||||
List<String> normalized = values.stream()
|
||||
.filter(value -> value != null && !value.isBlank())
|
||||
.map(String::trim)
|
||||
.distinct()
|
||||
.toList();
|
||||
return normalized.isEmpty() ? fallback : normalized;
|
||||
}
|
||||
|
||||
private String generateAiContent(String prompt, String requestedModel) {
|
||||
return chatClientBuilder.build().prompt()
|
||||
.user(prompt)
|
||||
.options(org.springframework.ai.openai.OpenAiChatOptions.builder()
|
||||
.model(resolveModel(requestedModel)).build())
|
||||
.call()
|
||||
.content();
|
||||
}
|
||||
|
||||
private String resolveModel(String requestedModel) {
|
||||
String model = requestedModel == null || requestedModel.isBlank()
|
||||
? "cohere/north-mini-code:free" : requestedModel.trim();
|
||||
if (!SUPPORTED_AI_MODELS.contains(model)) {
|
||||
throw new IllegalArgumentException("지원하지 않는 AI 모델입니다.");
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
private String stripCodeFence(String response) {
|
||||
if (response == null) {
|
||||
throw new IllegalArgumentException("AI 응답이 비어 있습니다.");
|
||||
}
|
||||
return response.trim().replaceFirst("^```(?:json)?\\s*", "").replaceFirst("\\s*```$", "").trim();
|
||||
}
|
||||
|
||||
private String safeMessage(Exception e) {
|
||||
return e.getMessage() == null ? e.getClass().getSimpleName() : e.getMessage();
|
||||
}
|
||||
|
||||
private record FieldDraft(List<ToolScaffolder.FieldDefinition> fields) {
|
||||
}
|
||||
|
||||
private record ToolDraft(String baseName, String title, String description, String categoryKey, String routingType,
|
||||
String httpApiName, String functionDescription, String displayDescription,
|
||||
String whenToUse, String whenNotToUse, String ioLimits,
|
||||
List<String> exampleQueries, List<String> tags, String ownerOrg,
|
||||
List<ToolScaffolder.FieldDefinition> inputFields,
|
||||
List<ToolScaffolder.FieldDefinition> outputFields) {
|
||||
}
|
||||
|
||||
private record ToolGroupRequest(String useCaseName, String moduleName, String author, String date,
|
||||
List<ToolScaffolder.ToolMethodDefinition> tools) {
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
package io.shinhanlife.dap.mcg.presentation;
|
||||
|
||||
import io.shinhanlife.dap.mcc.dto.ToolMetadata;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/** Gateway 내부 MCP 목록 조회와 Report 서비스의 Excel API 프록시를 담당한다. */
|
||||
@RestController
|
||||
public class ToolReportProxyController {
|
||||
|
||||
private final RestClient restClient;
|
||||
private final String reportServiceUrl;
|
||||
private final McpRouterController mcpRouterController;
|
||||
|
||||
public ToolReportProxyController(
|
||||
RestClient.Builder restClientBuilder,
|
||||
McpRouterController mcpRouterController,
|
||||
@Value("${report.service-url}") String reportServiceUrl) {
|
||||
this.restClient = restClientBuilder.build();
|
||||
this.mcpRouterController = mcpRouterController;
|
||||
this.reportServiceUrl = reportServiceUrl.replaceAll("/+$", "");
|
||||
}
|
||||
|
||||
/** Tool 목록은 Report 서비스나 로컬 소스가 아니라 Gateway의 MCP Registry에서만 조회한다. */
|
||||
@GetMapping(value = "/report/api/report-tools", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public List<ReportToolSummary> tools() {
|
||||
return activeTools().stream().map(ReportToolSummary::from).toList();
|
||||
}
|
||||
|
||||
@PostMapping(value = "/report/api/tool-reports/excel",
|
||||
consumes = MediaType.APPLICATION_JSON_VALUE,
|
||||
produces = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
public ResponseEntity<byte[]> excel(@RequestBody Map<String, Object> request) {
|
||||
validateMcpSelection(request);
|
||||
ResponseEntity<byte[]> response = restClient.post()
|
||||
.uri(reportServiceUrl + "/api/tool-reports/excel")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.body(request)
|
||||
.retrieve()
|
||||
.toEntity(byte[].class);
|
||||
return copy(response);
|
||||
}
|
||||
|
||||
private void validateMcpSelection(Map<String, Object> request) {
|
||||
Object rawNames = request.get("toolNames");
|
||||
if (!(rawNames instanceof List<?> names) || names.isEmpty()) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "toolNames must contain at least one tool");
|
||||
}
|
||||
Set<String> registeredNames = activeTools().stream()
|
||||
.map(ToolMetadata::getName)
|
||||
.collect(java.util.stream.Collectors.toSet());
|
||||
Set<String> unknown = new LinkedHashSet<>();
|
||||
names.stream().map(String::valueOf)
|
||||
.filter(name -> !registeredNames.contains(name))
|
||||
.forEach(unknown::add);
|
||||
if (!unknown.isEmpty()) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
|
||||
"MCP에 등록되지 않은 툴이 포함되어 있습니다: " + unknown);
|
||||
}
|
||||
}
|
||||
|
||||
private List<ToolMetadata> activeTools() {
|
||||
return mcpRouterController.activeToolsForReport();
|
||||
}
|
||||
|
||||
private ResponseEntity<byte[]> copy(ResponseEntity<byte[]> response) {
|
||||
ResponseEntity.BodyBuilder builder = ResponseEntity.status(response.getStatusCode());
|
||||
MediaType contentType = response.getHeaders().getContentType();
|
||||
if (contentType != null) builder.contentType(contentType);
|
||||
String disposition = response.getHeaders().getFirst(HttpHeaders.CONTENT_DISPOSITION);
|
||||
if (disposition != null) builder.header(HttpHeaders.CONTENT_DISPOSITION, disposition);
|
||||
return builder.body(response.getBody());
|
||||
}
|
||||
|
||||
public record ReportToolSummary(
|
||||
String name,
|
||||
String title,
|
||||
String description,
|
||||
String categoryKey,
|
||||
String mappingId,
|
||||
boolean register,
|
||||
boolean requiresApproval,
|
||||
boolean readOnlyHint,
|
||||
boolean destructiveHint,
|
||||
boolean idempotentHint,
|
||||
boolean openWorldHint,
|
||||
String requestType,
|
||||
String responseType,
|
||||
String useCaseClass,
|
||||
String sourceFile,
|
||||
String inputSchemaResource,
|
||||
String outputSchemaResource) {
|
||||
|
||||
static ReportToolSummary from(ToolMetadata tool) {
|
||||
String name = tool.getName() == null || tool.getName().isBlank() ? tool.getUid() : tool.getName();
|
||||
String title = tool.getDisplayName() == null || tool.getDisplayName().isBlank()
|
||||
? name : tool.getDisplayName();
|
||||
return new ReportToolSummary(
|
||||
name, title, value(tool.getDescription()), value(tool.getCategoryKey()),
|
||||
value(tool.getMciServiceId()), Boolean.TRUE.equals(tool.getIsRegistered()),
|
||||
Boolean.TRUE.equals(tool.getRequiresApproval()), Boolean.TRUE.equals(tool.getReadOnlyHint()),
|
||||
Boolean.TRUE.equals(tool.getDestructiveHint()), Boolean.TRUE.equals(tool.getIdempotentHint()),
|
||||
Boolean.TRUE.equals(tool.getOpenWorldHint()), "", "", "", "", "", "");
|
||||
}
|
||||
|
||||
private static String value(String value) {
|
||||
return value == null ? "" : value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
|
||||
<!-- 1. 로그 패턴 설정 (MDC traceId 포함) -->
|
||||
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] %-5level %logger{36} - %msg%n" />
|
||||
|
||||
<!-- 2. 콘솔(Console) 출력 설정 (로컬 개발용) -->
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>${LOG_PATTERN}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 3. 파일(File) 출력 설정 (서버 운영용) -->
|
||||
<!-- Logback에서 시스템 Hostname을 가져오기 위한 설정 -->
|
||||
<property name="HOSTNAME" value="${HOSTNAME}" />
|
||||
|
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>/swlog/dap-gateway/A01/${HOSTNAME}_A01.log</file> <!-- 현재 로그가 쌓이는 파일 -->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<!-- 매일 자정에 날짜를 붙여서 지난 로그를 분리 저장 -->
|
||||
<fileNamePattern>/swlog/dap-gateway/A01/${HOSTNAME}_A01_%d{yyyyMMdd}.%i.log</fileNamePattern>
|
||||
<!-- 개별 로그 파일 크기를 100MB로 제한하고, 전체 보관 일수는 30일, 총 로그 누적 용량은 1GB로 제한 -->
|
||||
<maxFileSize>100MB</maxFileSize>
|
||||
<maxHistory>30</maxHistory>
|
||||
<totalSizeCap>1GB</totalSizeCap>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${LOG_PATTERN}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 4. 기본 로깅 레벨 설정 -->
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE" />
|
||||
<appender-ref ref="FILE" />
|
||||
</root>
|
||||
|
||||
<!-- 5. 우리 프로젝트 패키지는 디버그 레벨까지 상세히 보기 -->
|
||||
<logger name="io.shinhanlife" level="DEBUG" />
|
||||
</configuration>
|
||||
@@ -31,7 +31,7 @@
|
||||
.subtitle { margin: 0; color: #a1a1aa; font-size: 14px; line-height: 1.65; }
|
||||
.count-badge { padding: 7px 11px; border: 1px solid #27272a; border-radius: 8px; background: #18181b; color: #a1a1aa; font-family: "Geist Mono", Consolas, monospace; font-size: 12px; white-space: nowrap; }
|
||||
.panel { overflow: hidden; border: 1px solid #27272a; border-radius: 12px; background: #111113; box-shadow: 0 18px 50px rgba(0,0,0,.22); }
|
||||
.toolbar { display: grid; grid-template-columns: minmax(260px,1fr) 190px auto auto; gap: 10px; padding: 16px; border-bottom: 1px solid #27272a; background: #111113; }
|
||||
.toolbar { display: grid; grid-template-columns: 210px minmax(240px,1fr) 170px auto auto; gap: 10px; padding: 16px; border-bottom: 1px solid #27272a; background: #111113; }
|
||||
input, select, button { min-height: 40px; border: 1px solid #3f3f46; border-radius: 7px; background: #18181b; color: #e4e4e7; padding: 8px 12px; font: inherit; font-size: 13px; outline: none; }
|
||||
input::placeholder { color: #71717a; }
|
||||
input:focus, select:focus { border-color: #3b82f6; box-shadow: 0 0 0 3px rgba(59,130,246,.12); }
|
||||
@@ -95,6 +95,10 @@
|
||||
|
||||
<section class="panel">
|
||||
<div class="toolbar">
|
||||
<select id="sourceType" aria-label="Tool 소스 프로젝트">
|
||||
<option value="DAP_WAS_DAPMT">dap-was-dapmt 소스</option>
|
||||
<option value="DAP_ADMIN">dap-admin 소스</option>
|
||||
</select>
|
||||
<input id="keyword" type="search" autocomplete="off" placeholder="툴명, 제목 또는 설명 검색">
|
||||
<select id="category"><option value="">전체 카테고리</option></select>
|
||||
<button id="selectAll" type="button">전체 선택</button>
|
||||
@@ -114,7 +118,7 @@
|
||||
</main>
|
||||
|
||||
<script>
|
||||
const state = { tools: [], selected: new Set() };
|
||||
const state = { tools: [], selected: new Set(), sourceType: 'DAP_WAS_DAPMT' };
|
||||
const el = id => document.getElementById(id);
|
||||
|
||||
const gateway = location.origin;
|
||||
@@ -155,7 +159,11 @@
|
||||
|
||||
async function load() {
|
||||
try {
|
||||
const response = await fetch('/report/api/report-tools');
|
||||
state.sourceType = el('sourceType').value;
|
||||
state.selected.clear();
|
||||
state.tools = [];
|
||||
el('tools').innerHTML = '<tr><td class="loading-row" colspan="6">선택한 프로젝트의 Tool 소스를 분석하고 있습니다.</td></tr>';
|
||||
const response = await fetch(`/report/api/report-tools?sourceType=${encodeURIComponent(state.sourceType)}`);
|
||||
const body = await response.json().catch(() => null);
|
||||
if (!response.ok) {
|
||||
throw new Error(body?.error || body?.message || `툴 목록 조회에 실패했습니다. (${response.status})`);
|
||||
@@ -178,6 +186,10 @@
|
||||
}
|
||||
|
||||
el('keyword').addEventListener('input', render);
|
||||
el('sourceType').addEventListener('change', () => {
|
||||
el('category').innerHTML = '<option value="">전체 카테고리</option>';
|
||||
load();
|
||||
});
|
||||
el('category').addEventListener('change', render);
|
||||
el('selectAll').addEventListener('click', () => { filtered().forEach(tool => state.selected.add(tool.name)); render(); });
|
||||
el('clearAll').addEventListener('click', () => { filtered().forEach(tool => state.selected.delete(tool.name)); render(); });
|
||||
@@ -185,7 +197,8 @@
|
||||
el('download').disabled = true; updateStatus('소스를 분석하고 Excel을 생성하는 중입니다.');
|
||||
try {
|
||||
const response = await fetch('/report/api/tool-reports/excel', {
|
||||
method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({toolNames: [...state.selected]})
|
||||
method: 'POST', headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify({toolNames: [...state.selected], sourceType: state.sourceType})
|
||||
});
|
||||
if (!response.ok) {
|
||||
const body = await response.json().catch(() => ({}));
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
package io.shinhanlife.dap.mcg.presentation;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.shinhanlife.dap.lib.mcp.security.SecurityProperties;
|
||||
import io.shinhanlife.dap.lib.adapter.dto.JsonRpcResponse;
|
||||
import io.shinhanlife.dap.mcg.config.GatewayFallbackProperties;
|
||||
import io.shinhanlife.dap.mcg.registry.RedisRegistryService;
|
||||
import io.shinhanlife.dap.mcg.service.ExecuteService;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.redis.RedisConnectionFailureException;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
class McpRouterControllerTest {
|
||||
|
||||
@Test
|
||||
void returnsEmptyToolListWhenRedisIsUnavailableAndNoFallbackIsConfigured() {
|
||||
RedisRegistryService registryService = org.mockito.Mockito.mock(RedisRegistryService.class);
|
||||
when(registryService.getAllTools()).thenThrow(new RedisConnectionFailureException("Redis unavailable"));
|
||||
|
||||
McpRouterController controller = new McpRouterController(
|
||||
registryService,
|
||||
org.mockito.Mockito.mock(ExecuteService.class),
|
||||
org.mockito.Mockito.mock(SecurityProperties.class),
|
||||
new ObjectMapper(),
|
||||
new GatewayFallbackProperties());
|
||||
|
||||
ResponseEntity<JsonRpcResponse> response = assertDoesNotThrow(() -> controller.listTools(null));
|
||||
Map<?, ?> result = (Map<?, ?>) response.getBody().getResult();
|
||||
|
||||
assertEquals(List.of(), result.get("tools"));
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
package io.shinhanlife.dap.mcg.presentation;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
|
||||
class ScaffoldingControllerToolDraftTest {
|
||||
|
||||
@TempDir
|
||||
Path root;
|
||||
|
||||
@Test
|
||||
void listsExistingUseCasesForSelectedModuleAndCategory() throws Exception {
|
||||
Path useCaseDir = root.resolve("dap-was-sample/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase");
|
||||
Files.createDirectories(useCaseDir);
|
||||
Files.writeString(useCaseDir.resolve("EmployeeSearchUseCase.java"), "interface EmployeeSearchUseCase {}\n");
|
||||
Files.writeString(useCaseDir.resolve("Ignored.java"), "class Ignored {}\n");
|
||||
|
||||
String previousUserDir = System.getProperty("user.dir");
|
||||
System.setProperty("user.dir", root.toString());
|
||||
try {
|
||||
ScaffoldingController controller = new ScaffoldingController(mock(ChatClient.Builder.class), new ObjectMapper());
|
||||
assertEquals(java.util.List.of("EmployeeSearchUseCase"), controller.listUseCases("dap-was-sample", "smp"));
|
||||
} finally {
|
||||
System.setProperty("user.dir", previousUserDir);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void groupedToolRequestGeneratesOneUseCase() throws Exception {
|
||||
ChatClient.Builder builder = mock(ChatClient.Builder.class);
|
||||
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(
|
||||
new ScaffoldingController(builder, new ObjectMapper()))
|
||||
.setMessageConverters(new MappingJackson2HttpMessageConverter())
|
||||
.build();
|
||||
String moduleName = root.resolve("dap-was-customer").toString().replace("\\", "\\\\");
|
||||
String request = """
|
||||
{"useCaseName":"Customer","moduleName":"%s","author":"tester","date":"2026.08.12","tools":[
|
||||
{"baseName":"CustomerGuidance","methodName":"searchGuidance","interfaceId":"CTMNILO00007","title":"Customer guidance","description":"Search guidance","group":"cmm","routingType":"MCI","register":false,"clientSystemCode":"NILD","inputFields":[{"name":"customerId","type":"String","description":"Customer ID","example":"C001","required":true}],"outputFields":[]}
|
||||
]}
|
||||
""".formatted(moduleName);
|
||||
|
||||
mockMvc.perform(post("/api/v1/scaffold/tool-group")
|
||||
.contentType(MediaType.APPLICATION_JSON).content(request))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(org.hamcrest.Matchers.containsString("CustomerUseCase.java")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void toolDraftEndpointIsAvailable() throws Exception {
|
||||
ChatClient.Builder builder = mock(ChatClient.Builder.class);
|
||||
ChatClient chatClient = mock(ChatClient.class);
|
||||
ChatClient.ChatClientRequestSpec requestSpec = mock(ChatClient.ChatClientRequestSpec.class);
|
||||
ChatClient.CallResponseSpec responseSpec = mock(ChatClient.CallResponseSpec.class);
|
||||
when(builder.build()).thenReturn(chatClient);
|
||||
when(chatClient.prompt()).thenReturn(requestSpec);
|
||||
when(requestSpec.user(anyString())).thenReturn(requestSpec);
|
||||
when(requestSpec.options(any(ChatOptions.class))).thenReturn(requestSpec);
|
||||
when(requestSpec.call()).thenReturn(responseSpec);
|
||||
when(responseSpec.content()).thenReturn("""
|
||||
{"baseName":"CustomerContractStatus","title":"계약 상태 조회","description":"고객번호로 계약 상태를 조회합니다.","categoryKey":"cmm","routingType":"HTTP","httpApiName":"contract-status","functionDescription":"고객 계약의 현재 상태를 조회한다.","displayDescription":"고객 계약 상태 조회","whenToUse":"고객번호로 계약 상태 확인을 요청할 때 사용한다.","whenNotToUse":"계약 변경 또는 해지를 요청할 때는 사용하지 않는다.","ioLimits":"고객번호 한 건을 입력받아 계약 상태 한 건을 반환한다.","exampleQueries":["고객 C123의 계약 상태를 알려줘","C123 계약이 정상인지 확인해줘","고객번호 C123 계약 조회해줘"],"tags":["contract","status","search"],"ownerOrg":"MCP_TOOL","inputFields":[{"name":"customerId","type":"String","description":"고객번호","example":"C123","required":true}],"outputFields":[{"name":"resultCode","type":"String","description":"결과 코드","example":"SUCCESS","required":true}]}
|
||||
""");
|
||||
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(
|
||||
new ScaffoldingController(builder, new ObjectMapper()))
|
||||
.setMessageConverters(new MappingJackson2HttpMessageConverter())
|
||||
.build();
|
||||
|
||||
mockMvc.perform(post("/api/v1/scaffold/tool-draft")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"description\":\"고객번호로 계약 상태를 조회\"}"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.baseName").value("CustomerContractStatus"))
|
||||
.andExpect(jsonPath("$.functionDescription").value("고객 계약의 현재 상태를 조회한다."))
|
||||
.andExpect(jsonPath("$.displayDescription").value("고객 계약 상태 조회"))
|
||||
.andExpect(jsonPath("$.whenToUse").value("고객번호로 계약 상태 확인을 요청할 때 사용한다."))
|
||||
.andExpect(jsonPath("$.whenNotToUse").value("계약 변경 또는 해지를 요청할 때는 사용하지 않는다."))
|
||||
.andExpect(jsonPath("$.ioLimits").value("고객번호 한 건을 입력받아 계약 상태 한 건을 반환한다."))
|
||||
.andExpect(jsonPath("$.exampleQueries[0]").value("고객 C123의 계약 상태를 알려줘"))
|
||||
.andExpect(jsonPath("$.tags[0]").value("contract"))
|
||||
.andExpect(jsonPath("$.ownerOrg").value("MCP_TOOL"))
|
||||
.andExpect(jsonPath("$.inputFields[0].name").value("customerId"));
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,10 @@ plugins {
|
||||
|
||||
dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.1'
|
||||
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.17.1'
|
||||
implementation 'com.fasterxml.jackson.core:jackson-databind:2.18.4'
|
||||
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.19.1'
|
||||
implementation 'com.github.javaparser:javaparser-core:3.26.3'
|
||||
implementation 'org.apache.poi:poi-ooxml:5.3.0'
|
||||
implementation 'org.apache.poi:poi-ooxml:5.4.1'
|
||||
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package io.shinhanlife.dap.report.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/** 보고서 소스 위치와 출력 정책 설정. */
|
||||
@ConfigurationProperties(prefix = "report")
|
||||
public record ReportProperties(String sourceRoot, String outputFilenamePrefix) {
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package io.shinhanlife.dap.report.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** 사용자가 선택한 툴 보고서 생성 요청. */
|
||||
public record ToolReportRequest(List<String> toolNames) {
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
package io.shinhanlife.dap.report.source;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
||||
import com.github.javaparser.StaticJavaParser;
|
||||
import com.github.javaparser.ast.CompilationUnit;
|
||||
import com.github.javaparser.ast.body.FieldDeclaration;
|
||||
import com.github.javaparser.ast.body.TypeDeclaration;
|
||||
import com.github.javaparser.ast.expr.AnnotationExpr;
|
||||
import io.shinhanlife.dap.report.model.FieldDefinition;
|
||||
import io.shinhanlife.dap.report.model.ToolReportModel;
|
||||
import io.shinhanlife.dap.report.model.ToolSummary;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/** 선택된 툴의 DTO, JSON Schema와 원천 전문 필드를 수집한다. */
|
||||
@Component
|
||||
public class ToolDetailAnalyzer {
|
||||
|
||||
private final ToolSourceDiscovery discovery;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final ObjectMapper yamlMapper;
|
||||
|
||||
public ToolDetailAnalyzer(ToolSourceDiscovery discovery) {
|
||||
this.discovery = discovery;
|
||||
this.objectMapper = new ObjectMapper();
|
||||
this.yamlMapper = new ObjectMapper(new YAMLFactory());
|
||||
}
|
||||
|
||||
public ToolReportModel analyze(ToolSummary tool) {
|
||||
List<FieldDefinition> fields = new ArrayList<>();
|
||||
List<String> diagnostics = new ArrayList<>();
|
||||
Map<String, Path> javaFiles = indexJavaFiles();
|
||||
|
||||
collectSchema(tool, tool.inputSchemaResource(), "INPUT", fields, diagnostics);
|
||||
collectSchema(tool, tool.outputSchemaResource(), "OUTPUT", fields, diagnostics);
|
||||
boolean definitionInput = collectDefinitionSchema(tool, "parameters_schema", "INPUT", fields, diagnostics);
|
||||
boolean definitionOutput = collectDefinitionSchema(tool, "output_schema", "OUTPUT", fields, diagnostics);
|
||||
if (tool.inputSchemaResource().isBlank() && !definitionInput) {
|
||||
collectJavaType(tool, javaFiles.get(tool.requestType()), "DTO", "INPUT", fields, diagnostics);
|
||||
}
|
||||
if (tool.outputSchemaResource().isBlank() && !definitionOutput) {
|
||||
collectJavaType(tool, javaFiles.get(tool.responseType()), "DTO", "OUTPUT", fields, diagnostics);
|
||||
}
|
||||
if (!tool.mappingId().isBlank()) {
|
||||
collectJavaType(tool, javaFiles.get(tool.mappingId() + "_I"), "TELEGRAM", "INPUT", fields, diagnostics);
|
||||
collectJavaType(tool, javaFiles.get(tool.mappingId() + "_O"), "TELEGRAM", "OUTPUT", fields, diagnostics);
|
||||
}
|
||||
return new ToolReportModel(tool, List.copyOf(fields), List.copyOf(diagnostics));
|
||||
}
|
||||
|
||||
private boolean collectDefinitionSchema(ToolSummary tool, String schemaKey, String direction,
|
||||
List<FieldDefinition> fields, List<String> diagnostics) {
|
||||
if (tool.definitionFile() == null || tool.definitionFile().isBlank()) return false;
|
||||
Path path = discovery.sourceRoot().resolve(tool.definitionFile()).normalize();
|
||||
try {
|
||||
JsonNode schema = yamlMapper.readTree(path.toFile()).path(schemaKey);
|
||||
if (schema.isMissingNode() || schema.isNull() || schema.isEmpty()) return false;
|
||||
collectSchemaFields(tool, schema, "TOOL_DEFINITION", direction, "", "", relative(path), fields);
|
||||
return true;
|
||||
} catch (Exception exception) {
|
||||
diagnostics.add("Tool definition schema 분석 실패: " + relative(path) + " (" + schemaKey + ")");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Path> indexJavaFiles() {
|
||||
Map<String, Path> result = new LinkedHashMap<>();
|
||||
try (var paths = Files.walk(discovery.sourceRoot())) {
|
||||
paths.filter(path -> Files.isRegularFile(path) && path.getFileName().toString().endsWith(".java"))
|
||||
.forEach(path -> result.putIfAbsent(path.getFileName().toString().replace(".java", ""), path));
|
||||
} catch (IOException exception) {
|
||||
throw new IllegalStateException("Failed to index Java sources", exception);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void collectSchema(ToolSummary tool, String resource, String direction,
|
||||
List<FieldDefinition> fields, List<String> diagnostics) {
|
||||
if (resource == null || resource.isBlank()) return;
|
||||
String relative = resource.replaceFirst("^classpath:", "").replaceFirst("^/", "");
|
||||
List<Path> matches = new ArrayList<>();
|
||||
try (var paths = Files.walk(discovery.sourceRoot())) {
|
||||
paths.filter(path -> Files.isRegularFile(path))
|
||||
.filter(path -> path.toString().replace('\\', '/').endsWith("/src/main/resources/" + relative))
|
||||
.forEach(matches::add);
|
||||
} catch (IOException exception) {
|
||||
diagnostics.add("Schema 검색 실패: " + resource);
|
||||
return;
|
||||
}
|
||||
if (matches.isEmpty()) {
|
||||
diagnostics.add("Schema를 찾을 수 없음: " + resource);
|
||||
return;
|
||||
}
|
||||
Path path = matches.get(0);
|
||||
try {
|
||||
JsonNode root = objectMapper.readTree(path.toFile());
|
||||
collectSchemaFields(tool, root, "JSON_SCHEMA", direction, "", "", relative(path), fields);
|
||||
} catch (Exception exception) {
|
||||
diagnostics.add("Schema 분석 실패: " + relative(path));
|
||||
}
|
||||
}
|
||||
|
||||
private void collectSchemaFields(ToolSummary tool, JsonNode schema, String sourceKind, String direction,
|
||||
String ownerPath, String fieldPrefix, String sourceFile,
|
||||
List<FieldDefinition> fields) {
|
||||
List<String> required = new ArrayList<>();
|
||||
schema.path("required").forEach(node -> required.add(node.asText()));
|
||||
Iterator<Map.Entry<String, JsonNode>> iterator = schema.path("properties").fields();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<String, JsonNode> entry = iterator.next();
|
||||
JsonNode definition = entry.getValue();
|
||||
String fieldName = fieldPrefix.isBlank() ? entry.getKey() : fieldPrefix + "." + entry.getKey();
|
||||
String type = schemaType(definition);
|
||||
fields.add(new FieldDefinition(tool.name(), sourceKind, direction, ownerPath, fieldName, type,
|
||||
required.contains(entry.getKey()), definition.path("description").asText(""),
|
||||
constraints(definition), sourceFile));
|
||||
JsonNode nested = definition.path("properties").isObject() ? definition : definition.path("items");
|
||||
if (nested.path("properties").isObject()) {
|
||||
collectSchemaFields(tool, nested, sourceKind, direction, fieldName, fieldName, sourceFile, fields);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String schemaType(JsonNode definition) {
|
||||
String type = definition.path("type").asText("object");
|
||||
if ("array".equals(type)) {
|
||||
return "array<" + definition.path("items").path("type").asText("object") + ">";
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
private void collectJavaType(ToolSummary tool, Path path, String sourceKind, String direction,
|
||||
List<FieldDefinition> fields, List<String> diagnostics) {
|
||||
if (path == null) {
|
||||
diagnostics.add(sourceKind + " " + direction + " 타입을 찾을 수 없음");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
CompilationUnit unit = StaticJavaParser.parse(path);
|
||||
for (TypeDeclaration<?> type : unit.getTypes()) {
|
||||
collectFields(tool, path, type, sourceKind, direction, fields);
|
||||
type.getMembers().stream().filter(TypeDeclaration.class::isInstance)
|
||||
.map(TypeDeclaration.class::cast)
|
||||
.forEach(nested -> collectFields(tool, path, nested, sourceKind, direction, fields));
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
diagnostics.add(sourceKind + " 분석 실패: " + relative(path));
|
||||
}
|
||||
}
|
||||
|
||||
private void collectFields(ToolSummary tool, Path path, TypeDeclaration<?> owner, String sourceKind,
|
||||
String direction, List<FieldDefinition> fields) {
|
||||
for (FieldDeclaration declaration : owner.getFields()) {
|
||||
AnnotationExpr param = JavaAnnotationReader.find(declaration.getAnnotations(), "McpToolParam").orElse(null);
|
||||
AnnotationExpr schema = JavaAnnotationReader.find(declaration.getAnnotations(), "Schema").orElse(null);
|
||||
AnnotationExpr telegram = JavaAnnotationReader.find(declaration.getAnnotations(), "GlowTrgmField").orElse(null);
|
||||
declaration.getVariables().forEach(variable -> fields.add(new FieldDefinition(
|
||||
tool.name(), sourceKind, direction, owner.getNameAsString(), variable.getNameAsString(),
|
||||
variable.getTypeAsString(),
|
||||
param == null ? null : JavaAnnotationReader.bool(param, "required", false),
|
||||
param == null ? "" : JavaAnnotationReader.string(param, "description", ""),
|
||||
annotationConstraints(schema, telegram), relative(path))));
|
||||
}
|
||||
}
|
||||
|
||||
private String annotationConstraints(AnnotationExpr schema, AnnotationExpr telegram) {
|
||||
List<String> values = new ArrayList<>();
|
||||
if (schema != null) values.add("Schema=" + schema);
|
||||
if (telegram != null) values.add("GlowTrgmField=" + telegram);
|
||||
return String.join("; ", values);
|
||||
}
|
||||
|
||||
private String constraints(JsonNode node) {
|
||||
List<String> values = new ArrayList<>();
|
||||
for (String key : List.of("format", "pattern", "minimum", "maximum", "minLength", "maxLength", "example")) {
|
||||
if (node.has(key)) values.add(key + "=" + node.get(key).asText());
|
||||
}
|
||||
if (node.has("enum")) values.add("enum=" + node.get("enum"));
|
||||
return String.join("; ", values);
|
||||
}
|
||||
|
||||
private String relative(Path path) {
|
||||
return discovery.sourceRoot().relativize(path.toAbsolutePath().normalize()).toString().replace('\\', '/');
|
||||
}
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
package io.shinhanlife.dap.report.source;
|
||||
|
||||
import com.github.javaparser.StaticJavaParser;
|
||||
import com.github.javaparser.ast.CompilationUnit;
|
||||
import com.github.javaparser.ast.body.MethodDeclaration;
|
||||
import com.github.javaparser.ast.expr.AnnotationExpr;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
||||
import io.shinhanlife.dap.report.config.ReportProperties;
|
||||
import io.shinhanlife.dap.report.model.ToolSummary;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/** 프로젝트의 @McpTool 선언을 읽기 전용으로 탐색한다. */
|
||||
@Component
|
||||
public class ToolSourceDiscovery {
|
||||
|
||||
private final Path sourceRoot;
|
||||
private final ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory());
|
||||
|
||||
public ToolSourceDiscovery(ReportProperties properties) {
|
||||
String configuredRoot = properties.sourceRoot();
|
||||
if (configuredRoot == null || configuredRoot.isBlank()) {
|
||||
throw new IllegalArgumentException("report.source-root must not be blank");
|
||||
}
|
||||
this.sourceRoot = resolveProjectRoot(Path.of(configuredRoot).toAbsolutePath().normalize());
|
||||
}
|
||||
|
||||
public Path sourceRoot() {
|
||||
return sourceRoot;
|
||||
}
|
||||
|
||||
public List<ToolSummary> discover() {
|
||||
if (!Files.isDirectory(sourceRoot)) {
|
||||
throw new IllegalStateException("Report source root does not exist: " + sourceRoot);
|
||||
}
|
||||
List<ToolSummary> tools = new ArrayList<>();
|
||||
try (var paths = Files.walk(sourceRoot)) {
|
||||
paths.filter(this::isUseCaseSource).forEach(path -> parse(path, tools));
|
||||
} catch (IOException exception) {
|
||||
throw new IllegalStateException("Failed to scan tool sources: " + sourceRoot, exception);
|
||||
}
|
||||
Map<String, ToolSummary> discovered = tools.stream()
|
||||
.sorted(Comparator.comparing(ToolSummary::sourceFile))
|
||||
.collect(Collectors.toMap(ToolSummary::name, Function.identity(),
|
||||
(first, duplicate) -> first, LinkedHashMap::new));
|
||||
discoverDefinitions().forEach((name, definition) ->
|
||||
discovered.merge(name, definition, this::mergeDefinition));
|
||||
return discovered.values().stream()
|
||||
.sorted(Comparator.comparing(ToolSummary::name))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private Map<String, ToolSummary> discoverDefinitions() {
|
||||
Map<String, ToolSummary> definitions = new LinkedHashMap<>();
|
||||
try (var paths = Files.walk(sourceRoot)) {
|
||||
paths.filter(this::isToolDefinition).sorted().forEach(path -> {
|
||||
try {
|
||||
JsonNode root = yamlMapper.readTree(path.toFile());
|
||||
String name = text(root, "name");
|
||||
if (name.isBlank()) return;
|
||||
JsonNode description = root.path("description");
|
||||
String function = text(description, "function");
|
||||
definitions.putIfAbsent(name, new ToolSummary(
|
||||
name, text(root, "display_name"), function, text(root, "category_key"),
|
||||
text(root, "legacy_interface_id"), true, false,
|
||||
bool(root, "read_only"), bool(root, "destructive"), bool(root, "idempotent"), false,
|
||||
"", "", "", "", "", "", text(root, "version"), function,
|
||||
text(description, "when_to_use"), text(description, "when_not_to_use"),
|
||||
text(description, "io_limits"), text(root, "display_description"),
|
||||
joined(root.path("example_queries")), joined(root.path("tags")),
|
||||
joined(root.path("required_env_keys")), text(root, "owner_org"), relative(path)));
|
||||
} catch (IOException exception) {
|
||||
throw new IllegalStateException("Failed to parse tool definition: " + path, exception);
|
||||
}
|
||||
});
|
||||
} catch (IOException exception) {
|
||||
throw new IllegalStateException("Failed to scan tool definitions: " + sourceRoot, exception);
|
||||
}
|
||||
return definitions;
|
||||
}
|
||||
|
||||
private ToolSummary mergeDefinition(ToolSummary source, ToolSummary definition) {
|
||||
return new ToolSummary(source.name(), prefer(definition.title(), source.title()),
|
||||
prefer(definition.description(), source.description()),
|
||||
prefer(definition.categoryKey(), source.categoryKey()),
|
||||
prefer(definition.mappingId(), source.mappingId()), source.register(), source.requiresApproval(),
|
||||
definition.readOnlyHint(), definition.destructiveHint(), definition.idempotentHint(),
|
||||
source.openWorldHint(), source.requestType(), source.responseType(), source.useCaseClass(),
|
||||
source.sourceFile(), source.inputSchemaResource(), source.outputSchemaResource(), definition.version(),
|
||||
definition.functionDescription(), definition.whenToUse(), definition.whenNotToUse(),
|
||||
definition.ioLimits(), definition.displayDescription(), definition.exampleQueries(), definition.tags(),
|
||||
definition.requiredEnvKeys(), definition.ownerOrg(), definition.definitionFile());
|
||||
}
|
||||
|
||||
private boolean isToolDefinition(Path path) {
|
||||
String normalized = path.toString().replace('\\', '/');
|
||||
String fileName = path.getFileName().toString().toLowerCase();
|
||||
return Files.isRegularFile(path) && (fileName.endsWith(".yml") || fileName.endsWith(".yaml"))
|
||||
&& normalized.contains("/src/main/resources/tool-definitions/");
|
||||
}
|
||||
|
||||
private String text(JsonNode node, String field) {
|
||||
return node.path(field).asText("");
|
||||
}
|
||||
|
||||
private boolean bool(JsonNode node, String field) {
|
||||
return node.path(field).asBoolean(false);
|
||||
}
|
||||
|
||||
private String joined(JsonNode node) {
|
||||
if (!node.isArray()) return node.asText("");
|
||||
List<String> values = new ArrayList<>();
|
||||
node.forEach(value -> values.add(value.asText()));
|
||||
return String.join(" | ", values);
|
||||
}
|
||||
|
||||
private String prefer(String primary, String fallback) {
|
||||
return primary == null || primary.isBlank() ? fallback : primary;
|
||||
}
|
||||
|
||||
private String relative(Path path) {
|
||||
return sourceRoot.relativize(path.toAbsolutePath().normalize()).toString().replace('\\', '/');
|
||||
}
|
||||
|
||||
private Path resolveProjectRoot(Path configuredPath) {
|
||||
Path candidate = configuredPath;
|
||||
while (candidate != null) {
|
||||
if (Files.isRegularFile(candidate.resolve("settings.gradle"))
|
||||
&& Files.isDirectory(candidate.resolve("dap-was-lib"))) {
|
||||
return candidate;
|
||||
}
|
||||
candidate = candidate.getParent();
|
||||
}
|
||||
return configuredPath;
|
||||
}
|
||||
|
||||
private boolean isUseCaseSource(Path path) {
|
||||
String normalized = path.toString().replace('\\', '/');
|
||||
return Files.isRegularFile(path)
|
||||
&& path.getFileName().toString().endsWith("UseCase.java")
|
||||
&& normalized.contains("/src/main/java/")
|
||||
&& !normalized.contains("/dap-tool-report/");
|
||||
}
|
||||
|
||||
private void parse(Path path, List<ToolSummary> tools) {
|
||||
try {
|
||||
CompilationUnit unit = StaticJavaParser.parse(path);
|
||||
String owner = unit.getPrimaryTypeName().orElse(path.getFileName().toString().replace(".java", ""));
|
||||
for (MethodDeclaration method : unit.findAll(MethodDeclaration.class)) {
|
||||
JavaAnnotationReader.find(method.getAnnotations(), "McpTool")
|
||||
.ifPresent(annotation -> tools.add(toSummary(path, owner, method, annotation)));
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
throw new IllegalStateException("Failed to parse tool source: " + path, exception);
|
||||
}
|
||||
}
|
||||
|
||||
private ToolSummary toSummary(Path path, String owner, MethodDeclaration method, AnnotationExpr tool) {
|
||||
AnnotationExpr hint = JavaAnnotationReader.find(method.getAnnotations(), "GrowToolHint")
|
||||
.or(() -> JavaAnnotationReader.find(method.getAnnotations(), "ToolHint")).orElse(null);
|
||||
AnnotationExpr annotations = JavaAnnotationReader.nested(tool, "annotations").orElse(null);
|
||||
String name = JavaAnnotationReader.string(tool, "name", method.getNameAsString());
|
||||
String title = JavaAnnotationReader.string(tool, "title", name);
|
||||
String description = JavaAnnotationReader.string(tool, "description", "");
|
||||
String requestType = method.getParameters().isEmpty() ? "" : method.getParameter(0).getTypeAsString();
|
||||
return new ToolSummary(
|
||||
name,
|
||||
title,
|
||||
description,
|
||||
hint == null ? "common" : JavaAnnotationReader.string(hint, "categoryKey", "com"),
|
||||
hint == null ? "" : JavaAnnotationReader.string(hint, "mappingId", ""),
|
||||
hint != null && JavaAnnotationReader.bool(hint, "register", false),
|
||||
hint != null && JavaAnnotationReader.bool(hint, "requiresApproval", false),
|
||||
annotations != null && JavaAnnotationReader.bool(annotations, "readOnlyHint", false),
|
||||
annotations != null && JavaAnnotationReader.bool(annotations, "destructiveHint", false),
|
||||
annotations != null && JavaAnnotationReader.bool(annotations, "idempotentHint", false),
|
||||
annotations != null && JavaAnnotationReader.bool(annotations, "openWorldHint", false),
|
||||
requestType,
|
||||
method.getTypeAsString(),
|
||||
owner,
|
||||
sourceRoot.relativize(path.toAbsolutePath().normalize()).toString().replace('\\', '/'),
|
||||
hint == null ? "" : JavaAnnotationReader.string(hint, "inputSchemaResource", ""),
|
||||
hint == null ? "" : JavaAnnotationReader.string(hint, "outputSchemaResource", ""));
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
package io.shinhanlife.dap.report;
|
||||
package io.shinhanlife.dat.report;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.report
|
||||
* @package io.shinhanlife.dat.report
|
||||
* @className ToolReportApplication
|
||||
* @description 툴 소스를 읽기 전용으로 분석하여 Excel 보고서를 생성하는 독립 애플리케이션
|
||||
* @author 0986406
|
||||
@@ -1,10 +1,11 @@
|
||||
package io.shinhanlife.dap.report.application;
|
||||
package io.shinhanlife.dat.report.application;
|
||||
|
||||
import io.shinhanlife.dap.report.excel.ToolReportExcelWriter;
|
||||
import io.shinhanlife.dap.report.model.ToolReportModel;
|
||||
import io.shinhanlife.dap.report.model.ToolSummary;
|
||||
import io.shinhanlife.dap.report.source.ToolDetailAnalyzer;
|
||||
import io.shinhanlife.dap.report.source.ToolSourceDiscovery;
|
||||
import io.shinhanlife.dat.report.excel.ToolReportExcelWriter;
|
||||
import io.shinhanlife.dat.report.model.ToolReportModel;
|
||||
import io.shinhanlife.dat.report.model.ToolSummary;
|
||||
import io.shinhanlife.dat.report.model.ToolSourceType;
|
||||
import io.shinhanlife.dat.report.source.ToolDetailAnalyzer;
|
||||
import io.shinhanlife.dat.report.source.ToolSourceDiscovery;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -26,13 +27,18 @@ public class ToolReportService {
|
||||
this.excelWriter = excelWriter;
|
||||
}
|
||||
|
||||
public byte[] createExcel(List<String> selectedNames) {
|
||||
public List<ToolSummary> findTools(String sourceType) {
|
||||
return discovery.discover(ToolSourceType.from(sourceType));
|
||||
}
|
||||
|
||||
public byte[] createExcel(List<String> selectedNames, String sourceTypeValue) {
|
||||
if (selectedNames == null || selectedNames.isEmpty()) {
|
||||
throw new IllegalArgumentException("보고서에 포함할 툴을 하나 이상 선택해야 합니다.");
|
||||
}
|
||||
|
||||
ToolSourceType sourceType = ToolSourceType.from(sourceTypeValue);
|
||||
Map<String, ToolSummary> sourceTools = new LinkedHashMap<>();
|
||||
discovery.discover().forEach(tool -> sourceTools.put(tool.name(), tool));
|
||||
discovery.discover(sourceType).forEach(tool -> sourceTools.put(tool.name(), tool));
|
||||
List<String> missingSources = selectedNames.stream()
|
||||
.filter(name -> !sourceTools.containsKey(name)).distinct().toList();
|
||||
if (!missingSources.isEmpty()) {
|
||||
@@ -41,7 +47,7 @@ public class ToolReportService {
|
||||
|
||||
List<ToolReportModel> reports = selectedNames.stream().distinct()
|
||||
.map(sourceTools::get)
|
||||
.map(analyzer::analyze)
|
||||
.map(tool -> analyzer.analyze(tool, sourceType))
|
||||
.toList();
|
||||
return excelWriter.write(reports);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.shinhanlife.dap.report.config;
|
||||
package io.shinhanlife.dat.report.config;
|
||||
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -0,0 +1,10 @@
|
||||
package io.shinhanlife.dat.report.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/** 보고서 소스 위치와 출력 정책 설정. */
|
||||
@ConfigurationProperties(prefix = "report")
|
||||
public record ReportProperties(String dapWasDapmtSourceRoot,
|
||||
String dapAdminSourceRoot,
|
||||
String outputFilenamePrefix) {
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.shinhanlife.dap.report.excel;
|
||||
package io.shinhanlife.dat.report.excel;
|
||||
|
||||
import io.shinhanlife.dap.report.model.FieldDefinition;
|
||||
import io.shinhanlife.dap.report.model.ToolReportModel;
|
||||
import io.shinhanlife.dap.report.model.ToolSummary;
|
||||
import io.shinhanlife.dat.report.model.FieldDefinition;
|
||||
import io.shinhanlife.dat.report.model.ToolReportModel;
|
||||
import io.shinhanlife.dat.report.model.ToolSummary;
|
||||
import java.awt.Color;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.time.LocalDateTime;
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.shinhanlife.dap.report.model;
|
||||
package io.shinhanlife.dat.report.model;
|
||||
|
||||
/** DTO, JSON Schema 또는 원천 전문에서 수집한 필드 정의. */
|
||||
public record FieldDefinition(
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.shinhanlife.dap.report.model;
|
||||
package io.shinhanlife.dat.report.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package io.shinhanlife.dat.report.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** 사용자가 선택한 툴 보고서 생성 요청. */
|
||||
public record ToolReportRequest(List<String> toolNames, String sourceType) {
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package io.shinhanlife.dat.report.model;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/** Report 생성 시 참조할 Tool 소스 프로젝트. */
|
||||
public enum ToolSourceType {
|
||||
DAP_WAS_DAPMT,
|
||||
DAP_ADMIN;
|
||||
|
||||
public static ToolSourceType from(String value) {
|
||||
if (value == null || value.isBlank()) return DAP_WAS_DAPMT;
|
||||
try {
|
||||
return valueOf(value.trim().replace('-', '_').toUpperCase(Locale.ROOT));
|
||||
} catch (IllegalArgumentException exception) {
|
||||
throw new IllegalArgumentException("지원하지 않는 Tool 소스 유형입니다: " + value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.shinhanlife.dap.report.model;
|
||||
package io.shinhanlife.dat.report.model;
|
||||
|
||||
/** UI 선택 목록과 보고서 기본정보에 사용하는 툴 요약. */
|
||||
public record ToolSummary(
|
||||
@@ -1,8 +1,10 @@
|
||||
package io.shinhanlife.dap.report.presentation;
|
||||
package io.shinhanlife.dat.report.presentation;
|
||||
|
||||
import io.shinhanlife.dap.report.application.ToolReportService;
|
||||
import io.shinhanlife.dap.report.config.ReportProperties;
|
||||
import io.shinhanlife.dap.report.model.ToolReportRequest;
|
||||
import io.shinhanlife.dat.report.application.ToolReportService;
|
||||
import io.shinhanlife.dat.report.config.ReportProperties;
|
||||
import io.shinhanlife.dat.report.model.ToolReportRequest;
|
||||
import io.shinhanlife.dat.report.model.ToolSummary;
|
||||
import java.util.List;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
@@ -12,10 +14,12 @@ import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
/** 툴 선택 목록과 Excel 다운로드 API. */
|
||||
@RestController
|
||||
@@ -33,7 +37,7 @@ public class ToolReportController {
|
||||
@PostMapping(value = "/tool-reports/excel",
|
||||
produces = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
public ResponseEntity<byte[]> excel(@RequestBody ToolReportRequest request) {
|
||||
byte[] content = service.createExcel(request.toolNames());
|
||||
byte[] content = service.createExcel(request.toolNames(), request.sourceType());
|
||||
String prefix = properties.outputFilenamePrefix() == null || properties.outputFilenamePrefix().isBlank()
|
||||
? "tool-report" : properties.outputFilenamePrefix();
|
||||
String filename = prefix + "-" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss")) + ".xlsx";
|
||||
@@ -47,6 +51,11 @@ public class ToolReportController {
|
||||
.body(content);
|
||||
}
|
||||
|
||||
@GetMapping("/report-tools")
|
||||
public List<ToolSummary> tools(@RequestParam(value = "sourceType", required = false) String sourceType) {
|
||||
return service.findTools(sourceType);
|
||||
}
|
||||
|
||||
@ExceptionHandler(IllegalArgumentException.class)
|
||||
public ResponseEntity<Map<String, String>> badRequest(IllegalArgumentException exception) {
|
||||
return ResponseEntity.badRequest().body(Map.of("error", exception.getMessage()));
|
||||
@@ -1,7 +1,8 @@
|
||||
package io.shinhanlife.dap.report.source;
|
||||
package io.shinhanlife.dat.report.source;
|
||||
|
||||
import com.github.javaparser.ast.NodeList;
|
||||
import com.github.javaparser.ast.expr.AnnotationExpr;
|
||||
import com.github.javaparser.ast.expr.ArrayInitializerExpr;
|
||||
import com.github.javaparser.ast.expr.BooleanLiteralExpr;
|
||||
import com.github.javaparser.ast.expr.Expression;
|
||||
import com.github.javaparser.ast.expr.MemberValuePair;
|
||||
@@ -9,6 +10,7 @@ import com.github.javaparser.ast.expr.NormalAnnotationExpr;
|
||||
import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr;
|
||||
import com.github.javaparser.ast.expr.StringLiteralExpr;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/** JavaParser AST에서 어노테이션 값을 안전하게 읽는 도우미. */
|
||||
final class JavaAnnotationReader {
|
||||
@@ -49,6 +51,18 @@ final class JavaAnnotationReader {
|
||||
.orElse(fallback);
|
||||
}
|
||||
|
||||
static String strings(AnnotationExpr annotation, String key) {
|
||||
return value(annotation, key)
|
||||
.filter(ArrayInitializerExpr.class::isInstance)
|
||||
.map(ArrayInitializerExpr.class::cast)
|
||||
.map(array -> array.getValues().stream()
|
||||
.filter(StringLiteralExpr.class::isInstance)
|
||||
.map(StringLiteralExpr.class::cast)
|
||||
.map(StringLiteralExpr::asString)
|
||||
.collect(Collectors.joining(" | ")))
|
||||
.orElse("");
|
||||
}
|
||||
|
||||
static Optional<AnnotationExpr> nested(AnnotationExpr annotation, String key) {
|
||||
return value(annotation, key).filter(AnnotationExpr.class::isInstance).map(AnnotationExpr.class::cast);
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package io.shinhanlife.dat.report.source;
|
||||
|
||||
import com.github.javaparser.StaticJavaParser;
|
||||
import com.github.javaparser.ast.CompilationUnit;
|
||||
import com.github.javaparser.ast.body.FieldDeclaration;
|
||||
import com.github.javaparser.ast.body.TypeDeclaration;
|
||||
import com.github.javaparser.ast.expr.AnnotationExpr;
|
||||
import io.shinhanlife.dat.report.model.FieldDefinition;
|
||||
import io.shinhanlife.dat.report.model.ToolReportModel;
|
||||
import io.shinhanlife.dat.report.model.ToolSourceType;
|
||||
import io.shinhanlife.dat.report.model.ToolSummary;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/** 선택한 프로젝트의 DTO와 전문 Java 어노테이션에서 Report 파라미터를 수집한다. */
|
||||
@Component
|
||||
public class ToolDetailAnalyzer {
|
||||
|
||||
private final ToolSourceDiscovery discovery;
|
||||
|
||||
public ToolDetailAnalyzer(ToolSourceDiscovery discovery) {
|
||||
this.discovery = discovery;
|
||||
}
|
||||
|
||||
public ToolReportModel analyze(ToolSummary tool, ToolSourceType sourceType) {
|
||||
List<FieldDefinition> fields = new ArrayList<>();
|
||||
List<String> diagnostics = new ArrayList<>();
|
||||
Path sourceRoot = discovery.sourceRoot(sourceType);
|
||||
Map<String, Path> javaFiles = indexJavaFiles(sourceRoot);
|
||||
|
||||
collectJavaType(sourceRoot, tool, javaFiles.get(tool.requestType()), "DTO", "INPUT", fields, diagnostics);
|
||||
collectJavaType(sourceRoot, tool, javaFiles.get(tool.responseType()), "DTO", "OUTPUT", fields, diagnostics);
|
||||
if (!tool.mappingId().isBlank()) {
|
||||
collectJavaType(sourceRoot, tool, javaFiles.get(tool.mappingId() + "_I"),
|
||||
"TELEGRAM", "INPUT", fields, diagnostics);
|
||||
collectJavaType(sourceRoot, tool, javaFiles.get(tool.mappingId() + "_O"),
|
||||
"TELEGRAM", "OUTPUT", fields, diagnostics);
|
||||
}
|
||||
return new ToolReportModel(tool, List.copyOf(fields), List.copyOf(diagnostics));
|
||||
}
|
||||
|
||||
private Map<String, Path> indexJavaFiles(Path sourceRoot) {
|
||||
Map<String, Path> result = new LinkedHashMap<>();
|
||||
try (var paths = Files.walk(sourceRoot)) {
|
||||
paths.filter(path -> Files.isRegularFile(path) && path.getFileName().toString().endsWith(".java"))
|
||||
.forEach(path -> result.putIfAbsent(path.getFileName().toString().replace(".java", ""), path));
|
||||
} catch (IOException exception) {
|
||||
throw new IllegalStateException("Failed to index Java sources: " + sourceRoot, exception);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void collectJavaType(Path sourceRoot, ToolSummary tool, Path path, String sourceKind, String direction,
|
||||
List<FieldDefinition> fields, List<String> diagnostics) {
|
||||
if (path == null) {
|
||||
diagnostics.add(sourceKind + " " + direction + " 타입을 찾을 수 없음");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
CompilationUnit unit = StaticJavaParser.parse(path);
|
||||
for (TypeDeclaration<?> type : unit.getTypes()) {
|
||||
collectFields(sourceRoot, tool, path, type, sourceKind, direction, fields);
|
||||
type.getMembers().stream().filter(TypeDeclaration.class::isInstance)
|
||||
.map(TypeDeclaration.class::cast)
|
||||
.forEach(nested -> collectFields(sourceRoot, tool, path, nested, sourceKind, direction, fields));
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
diagnostics.add(sourceKind + " 분석 실패: " + relative(sourceRoot, path));
|
||||
}
|
||||
}
|
||||
|
||||
private void collectFields(Path sourceRoot, ToolSummary tool, Path path, TypeDeclaration<?> owner,
|
||||
String sourceKind, String direction, List<FieldDefinition> fields) {
|
||||
for (FieldDeclaration declaration : owner.getFields()) {
|
||||
AnnotationExpr param = JavaAnnotationReader.find(declaration.getAnnotations(), "McpToolParam").orElse(null);
|
||||
AnnotationExpr schema = JavaAnnotationReader.find(declaration.getAnnotations(), "Schema").orElse(null);
|
||||
AnnotationExpr telegram = JavaAnnotationReader.find(declaration.getAnnotations(), "GlowTrgmField").orElse(null);
|
||||
declaration.getVariables().forEach(variable -> fields.add(new FieldDefinition(
|
||||
tool.name(), sourceKind, direction, owner.getNameAsString(), variable.getNameAsString(),
|
||||
variable.getTypeAsString(),
|
||||
param == null ? null : JavaAnnotationReader.bool(param, "required", false),
|
||||
param == null ? "" : JavaAnnotationReader.string(param, "description", ""),
|
||||
annotationConstraints(schema, telegram), relative(sourceRoot, path))));
|
||||
}
|
||||
}
|
||||
|
||||
private String annotationConstraints(AnnotationExpr schema, AnnotationExpr telegram) {
|
||||
List<String> values = new ArrayList<>();
|
||||
if (schema != null) values.add("Schema=" + schema);
|
||||
if (telegram != null) values.add("GlowTrgmField=" + telegram);
|
||||
return String.join("; ", values);
|
||||
}
|
||||
|
||||
private String relative(Path sourceRoot, Path path) {
|
||||
return sourceRoot.relativize(path.toAbsolutePath().normalize()).toString().replace('\\', '/');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package io.shinhanlife.dat.report.source;
|
||||
|
||||
import com.github.javaparser.StaticJavaParser;
|
||||
import com.github.javaparser.ast.CompilationUnit;
|
||||
import com.github.javaparser.ast.body.MethodDeclaration;
|
||||
import com.github.javaparser.ast.expr.AnnotationExpr;
|
||||
import io.shinhanlife.dat.report.config.ReportProperties;
|
||||
import io.shinhanlife.dat.report.model.ToolSummary;
|
||||
import io.shinhanlife.dat.report.model.ToolSourceType;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.EnumMap;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/** 프로젝트의 @McpTool 선언을 읽기 전용으로 탐색한다. */
|
||||
@Component
|
||||
public class ToolSourceDiscovery {
|
||||
|
||||
private final Map<ToolSourceType, Path> sourceRoots = new EnumMap<>(ToolSourceType.class);
|
||||
|
||||
public ToolSourceDiscovery(ReportProperties properties) {
|
||||
sourceRoots.put(ToolSourceType.DAP_WAS_DAPMT,
|
||||
resolveConfiguredRoot(properties.dapWasDapmtSourceRoot(), "report.dap-was-dapmt-source-root"));
|
||||
sourceRoots.put(ToolSourceType.DAP_ADMIN,
|
||||
resolveConfiguredRoot(properties.dapAdminSourceRoot(), "report.dap-admin-source-root"));
|
||||
}
|
||||
|
||||
public Path sourceRoot(ToolSourceType sourceType) {
|
||||
return sourceRoots.get(sourceType);
|
||||
}
|
||||
|
||||
public List<ToolSummary> discover(ToolSourceType sourceType) {
|
||||
Path sourceRoot = sourceRoot(sourceType);
|
||||
if (!Files.isDirectory(sourceRoot)) {
|
||||
throw new IllegalStateException("Report source root does not exist: " + sourceRoot);
|
||||
}
|
||||
List<ToolSummary> tools = new ArrayList<>();
|
||||
try (var paths = Files.walk(sourceRoot)) {
|
||||
paths.filter(this::isUseCaseSource).forEach(path -> parse(sourceRoot, path, tools));
|
||||
} catch (IOException exception) {
|
||||
throw new IllegalStateException("Failed to scan tool sources: " + sourceRoot, exception);
|
||||
}
|
||||
return tools.stream()
|
||||
.sorted(Comparator.comparing(ToolSummary::sourceFile))
|
||||
.collect(java.util.stream.Collectors.toMap(ToolSummary::name, java.util.function.Function.identity(),
|
||||
(first, duplicate) -> first, LinkedHashMap::new)).values().stream()
|
||||
.sorted(Comparator.comparing(ToolSummary::name))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private Path resolveConfiguredRoot(String configuredRoot, String propertyName) {
|
||||
if (configuredRoot == null || configuredRoot.isBlank()) {
|
||||
throw new IllegalArgumentException(propertyName + " must not be blank");
|
||||
}
|
||||
return resolveProjectRoot(Path.of(configuredRoot).toAbsolutePath().normalize());
|
||||
}
|
||||
|
||||
private Path resolveProjectRoot(Path configuredPath) {
|
||||
Path candidate = configuredPath;
|
||||
while (candidate != null) {
|
||||
if (Files.isRegularFile(candidate.resolve("settings.gradle"))
|
||||
&& Files.isDirectory(candidate.resolve("dat-was-lib"))) {
|
||||
return candidate;
|
||||
}
|
||||
candidate = candidate.getParent();
|
||||
}
|
||||
return configuredPath;
|
||||
}
|
||||
|
||||
private boolean isUseCaseSource(Path path) {
|
||||
String normalized = path.toString().replace('\\', '/');
|
||||
return Files.isRegularFile(path)
|
||||
&& path.getFileName().toString().endsWith("UseCase.java")
|
||||
&& normalized.contains("/src/main/java/")
|
||||
&& !normalized.contains("/dap-tool-report/");
|
||||
}
|
||||
|
||||
private void parse(Path sourceRoot, Path path, List<ToolSummary> tools) {
|
||||
try {
|
||||
CompilationUnit unit = StaticJavaParser.parse(path);
|
||||
String owner = unit.getPrimaryTypeName().orElse(path.getFileName().toString().replace(".java", ""));
|
||||
for (MethodDeclaration method : unit.findAll(MethodDeclaration.class)) {
|
||||
JavaAnnotationReader.find(method.getAnnotations(), "McpTool")
|
||||
.ifPresent(annotation -> tools.add(toSummary(sourceRoot, path, owner, method, annotation)));
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
throw new IllegalStateException("Failed to parse tool source: " + path, exception);
|
||||
}
|
||||
}
|
||||
|
||||
private ToolSummary toSummary(Path sourceRoot, Path path, String owner, MethodDeclaration method, AnnotationExpr tool) {
|
||||
AnnotationExpr hint = JavaAnnotationReader.find(method.getAnnotations(), "GrowToolHint")
|
||||
.or(() -> JavaAnnotationReader.find(method.getAnnotations(), "ToolHint")).orElse(null);
|
||||
AnnotationExpr annotations = JavaAnnotationReader.nested(tool, "annotations").orElse(null);
|
||||
String name = JavaAnnotationReader.string(tool, "name", method.getNameAsString());
|
||||
String title = JavaAnnotationReader.string(tool, "title", name);
|
||||
String description = JavaAnnotationReader.string(tool, "description", "");
|
||||
String requestType = method.getParameters().isEmpty() ? "" : method.getParameter(0).getTypeAsString();
|
||||
return new ToolSummary(
|
||||
name,
|
||||
title,
|
||||
description,
|
||||
hint == null ? "common" : JavaAnnotationReader.string(hint, "categoryKey", "com"),
|
||||
hint == null ? "" : JavaAnnotationReader.string(hint, "mappingId", ""),
|
||||
hint != null && JavaAnnotationReader.bool(hint, "register", false),
|
||||
hint != null && JavaAnnotationReader.bool(hint, "requiresApproval", false),
|
||||
annotations != null && JavaAnnotationReader.bool(annotations, "readOnlyHint", false),
|
||||
annotations != null
|
||||
? JavaAnnotationReader.bool(annotations, "destructiveHint",
|
||||
hint != null && JavaAnnotationReader.bool(hint, "destructive", false))
|
||||
: hint != null && JavaAnnotationReader.bool(hint, "destructive", false),
|
||||
annotations != null
|
||||
? JavaAnnotationReader.bool(annotations, "idempotentHint",
|
||||
hint != null && JavaAnnotationReader.bool(hint, "idempotent", false))
|
||||
: hint != null && JavaAnnotationReader.bool(hint, "idempotent", false),
|
||||
annotations != null && JavaAnnotationReader.bool(annotations, "openWorldHint", false),
|
||||
requestType,
|
||||
method.getTypeAsString(),
|
||||
owner,
|
||||
sourceRoot.relativize(path.toAbsolutePath().normalize()).toString().replace('\\', '/'),
|
||||
hint == null ? "" : JavaAnnotationReader.string(hint, "inputSchemaResource", ""),
|
||||
hint == null ? "" : JavaAnnotationReader.string(hint, "outputSchemaResource", ""),
|
||||
hint == null ? "" : JavaAnnotationReader.string(hint, "version", ""),
|
||||
hint == null ? "" : JavaAnnotationReader.string(hint, "functionDescription", ""),
|
||||
hint == null ? "" : JavaAnnotationReader.string(hint, "whenToUse", ""),
|
||||
hint == null ? "" : JavaAnnotationReader.string(hint, "whenNotToUse", ""),
|
||||
hint == null ? "" : JavaAnnotationReader.string(hint, "ioLimits", ""),
|
||||
hint == null ? "" : JavaAnnotationReader.string(hint, "displayDescription", ""),
|
||||
hint == null ? "" : JavaAnnotationReader.strings(hint, "exampleQueries"),
|
||||
hint == null ? "" : JavaAnnotationReader.strings(hint, "tags"),
|
||||
hint == null ? "" : JavaAnnotationReader.strings(hint, "requiredEnvKeys"),
|
||||
hint == null ? "" : JavaAnnotationReader.string(hint, "ownerOrg", ""), "");
|
||||
}
|
||||
}
|
||||
@@ -7,5 +7,6 @@ spring:
|
||||
name: dap-tool-report
|
||||
|
||||
report:
|
||||
source-root: ${REPORT_SOURCE_ROOT:.}
|
||||
dap-was-dapmt-source-root: ${REPORT_DAP_WAS_DAPMT_SOURCE_ROOT:../dap-was-dapmt}
|
||||
dap-admin-source-root: ${REPORT_DAP_ADMIN_SOURCE_ROOT:.}
|
||||
output-filename-prefix: tool-report
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
package io.shinhanlife.dap.report.source;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import io.shinhanlife.dap.report.config.ReportProperties;
|
||||
import java.nio.file.Path;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class ToolSourceDiscoveryTest {
|
||||
|
||||
@Test
|
||||
void discoversMcpToolsFromProjectSources() {
|
||||
Path projectRoot = Path.of("..").toAbsolutePath().normalize();
|
||||
ToolSourceDiscovery discovery = new ToolSourceDiscovery(
|
||||
new ReportProperties(projectRoot.toString(), "tool-report"));
|
||||
|
||||
assertThat(discovery.discover())
|
||||
.extracting(tool -> tool.name())
|
||||
.contains("cmm_customer_tool", "cmm_claim_search", "iam_system_status");
|
||||
|
||||
assertThat(discovery.discover().stream()
|
||||
.filter(tool -> tool.name().equals("cmm_customer_tool"))
|
||||
.findFirst().orElseThrow())
|
||||
.satisfies(tool -> {
|
||||
assertThat(tool.version()).isEqualTo("1.0.0");
|
||||
assertThat(tool.mappingId()).isEqualTo("ONILD0320");
|
||||
assertThat(tool.definitionFile()).endsWith("tool-definitions/cmm/cmm_customer_tool.yml");
|
||||
assertThat(tool.whenToUse()).contains("고객 ID");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void collectsJsonSchemaFieldsEmbeddedInToolDefinitionYaml() {
|
||||
Path projectRoot = Path.of("..").toAbsolutePath().normalize();
|
||||
ToolSourceDiscovery discovery = new ToolSourceDiscovery(
|
||||
new ReportProperties(projectRoot.toString(), "tool-report"));
|
||||
var tool = discovery.discover().stream()
|
||||
.filter(candidate -> candidate.name().equals("cmm_customer_tool"))
|
||||
.findFirst().orElseThrow();
|
||||
|
||||
var report = new ToolDetailAnalyzer(discovery).analyze(tool);
|
||||
|
||||
assertThat(report.fields())
|
||||
.anySatisfy(field -> {
|
||||
assertThat(field.sourceKind()).isEqualTo("TOOL_DEFINITION");
|
||||
assertThat(field.direction()).isEqualTo("INPUT");
|
||||
assertThat(field.fieldName()).isEqualTo("csNo");
|
||||
assertThat(field.required()).isTrue();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
package io.shinhanlife.dap.report.excel;
|
||||
package io.shinhanlife.dat.report.excel;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import io.shinhanlife.dap.report.model.FieldDefinition;
|
||||
import io.shinhanlife.dap.report.model.ToolReportModel;
|
||||
import io.shinhanlife.dap.report.model.ToolSummary;
|
||||
import io.shinhanlife.dat.report.model.FieldDefinition;
|
||||
import io.shinhanlife.dat.report.model.ToolReportModel;
|
||||
import io.shinhanlife.dat.report.model.ToolSummary;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.List;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
@@ -0,0 +1,55 @@
|
||||
package io.shinhanlife.dat.report.source;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import io.shinhanlife.dat.report.config.ReportProperties;
|
||||
import io.shinhanlife.dat.report.model.ToolSourceType;
|
||||
import java.nio.file.Path;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class ToolSourceDiscoveryTest {
|
||||
|
||||
private final Path adminRoot = projectRoot();
|
||||
private final Path dapmtRoot = adminRoot.resolve("../dap-was-dapmt").normalize();
|
||||
|
||||
@Test
|
||||
void discoversToolsFromBothSelectableProjects() {
|
||||
ToolSourceDiscovery discovery = discovery();
|
||||
|
||||
assertThat(discovery.discover(ToolSourceType.DAP_ADMIN))
|
||||
.extracting(tool -> tool.name())
|
||||
.contains("cmm_claim_search");
|
||||
assertThat(discovery.discover(ToolSourceType.DAP_WAS_DAPMT))
|
||||
.extracting(tool -> tool.name())
|
||||
.contains("spr_field_inquiry_list");
|
||||
}
|
||||
|
||||
@Test
|
||||
void collectsParametersFromDtoAnnotationsInsteadOfYamlDefinitions() {
|
||||
ToolSourceDiscovery discovery = discovery();
|
||||
var tool = discovery.discover(ToolSourceType.DAP_WAS_DAPMT).stream()
|
||||
.filter(candidate -> candidate.name().equals("spr_field_inquiry_list"))
|
||||
.findFirst().orElseThrow();
|
||||
|
||||
var report = new ToolDetailAnalyzer(discovery).analyze(tool, ToolSourceType.DAP_WAS_DAPMT);
|
||||
|
||||
assertThat(report.fields()).anySatisfy(field -> {
|
||||
assertThat(field.sourceKind()).isEqualTo("DTO");
|
||||
assertThat(field.direction()).isEqualTo("INPUT");
|
||||
assertThat(field.fieldName()).isEqualTo("inquiryId");
|
||||
assertThat(field.constraints()).contains("Schema=");
|
||||
});
|
||||
assertThat(report.fields()).noneMatch(field -> field.sourceKind().equals("TOOL_DEFINITION"));
|
||||
}
|
||||
|
||||
private ToolSourceDiscovery discovery() {
|
||||
return new ToolSourceDiscovery(new ReportProperties(
|
||||
dapmtRoot.toString(), adminRoot.toString(), "tool-report"));
|
||||
}
|
||||
|
||||
private Path projectRoot() {
|
||||
Path current = Path.of("").toAbsolutePath().normalize();
|
||||
return java.nio.file.Files.isRegularFile(current.resolve("settings.gradle"))
|
||||
? current : current.getParent();
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
FROM eclipse-temurin:21-jre-alpine
|
||||
WORKDIR /app
|
||||
RUN apk add --no-cache tzdata
|
||||
ENV TZ=Asia/Seoul
|
||||
COPY dap-was-cus/build/libs/*-SNAPSHOT.jar app.jar
|
||||
EXPOSE 8084
|
||||
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
plugins {
|
||||
// OTH Tool Pod를 독립 실행 가능한 Spring Boot JAR로 생성합니다.
|
||||
id 'org.springframework.boot'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// MCP Server, Tool 공통 처리, MCI/EAI 연동 기반은 dap-was-lib에서 상속합니다.
|
||||
implementation project(':dap-was-lib')
|
||||
implementation 'org.apache.poi:poi-ooxml:5.3.0'
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package io.shinhanlife.dap.mcc.biz.cmm.converter;
|
||||
|
||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.CcCstUnfcNotiCarhDto;
|
||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.CustomerGuidanceToolRequest;
|
||||
import io.shinhanlife.dap.mcc.infra.itrf.mci.nil.d.io.ONILD0320_I;
|
||||
import io.shinhanlife.dap.mcc.infra.itrf.mci.nil.d.io.ONILD0320_O.CstUnfcNotiCarhInqrOutDto;
|
||||
import org.mapstruct.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
|
||||
public interface CustomerGuidanceToolConverter {
|
||||
|
||||
/**
|
||||
* 고객통합이력조회 전사 Request -> I/F
|
||||
*/
|
||||
@Mapping(target = "cstUnfcNotiCarhInqrInDto.inqrEndYmd", source = "inqrEndYmd")
|
||||
@Mapping(target = "cstUnfcNotiCarhInqrInDto.inqrStrtYmd", source = "inqrStrYmd")
|
||||
@Mapping(target = "cstUnfcNotiCarhInqrInDto.notiPmlMdCd", source = "notiPmlMdCd")
|
||||
@Mapping(target = "cstUnfcNotiCarhInqrInDto.ntleCd", source = "ntleCd")
|
||||
@Mapping(target = "cstUnfcNotiCarhInqrInDto.csNo", source = "csNo")
|
||||
public abstract ONILD0320_I toONILD0320_I(CustomerGuidanceToolRequest req);
|
||||
|
||||
@Named("toCcCstUnfcNotiCarhDto")
|
||||
public abstract CcCstUnfcNotiCarhDto toCcCstUnfcNotiCarhDto(CstUnfcNotiCarhInqrOutDto cstUnfcNotiCarhInqrOutDto);
|
||||
|
||||
@IterableMapping(qualifiedByName = "toCcCstUnfcNotiCarhDto")
|
||||
public abstract List<CcCstUnfcNotiCarhDto> toCcCstUnfcNotiCarhDtoList(List<CstUnfcNotiCarhInqrOutDto> cstUnfcNotiCarhInqrOutDto);
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package io.shinhanlife.dap.mcc.biz.cmm.converter;
|
||||
|
||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeRequest;
|
||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeResponse;
|
||||
import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_I;
|
||||
import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_O;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.mcc.biz.cmm.converter
|
||||
* @className MetaCommonCodeConverter
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 09863409
|
||||
* @create 2026.07.29
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.07.29 09863409 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Mapper(componentModel = "spring")
|
||||
public interface MetaCommonCodeConverter {
|
||||
@Mapping(target = "csNo", source = "groupCode", defaultValue = "GRP_COMM_CD")
|
||||
CLCNNB00001_I toLegacyRequest(MetaCommonCodeRequest req);
|
||||
|
||||
@Mapping(target = "codeList", ignore = true)
|
||||
MetaCommonCodeResponse toResponse(CLCNNB00001_O mciRes);
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package io.shinhanlife.dap.mcc.biz.cmm.converter;
|
||||
|
||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableRequest;
|
||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableResponse;
|
||||
import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_I;
|
||||
import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_O;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.mcc.biz.cmm.converter
|
||||
* @className MetaTableConverter
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 09863409
|
||||
* @create 2026.07.29
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.07.29 09863409 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Mapper(componentModel = "spring")
|
||||
public interface MetaTableConverter {
|
||||
@Mapping(target = "csNo", source = "tableName", defaultValue = "TB_META_BAS")
|
||||
CLCNNB00001_I toLegacyRequest(MetaTableRequest req);
|
||||
|
||||
@Mapping(target = "tableList", ignore = true)
|
||||
MetaTableResponse toResponse(CLCNNB00001_O mciRes);
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class AnalysisDataQueryRequest {
|
||||
@Schema(description = "데이터 조회를 시작할 날짜", example = "2024-01-01")
|
||||
private String startDate;
|
||||
|
||||
@Schema(description = "데이터 조회를 종료할 날짜", example = "2024-12-31")
|
||||
private String endDate;
|
||||
|
||||
@Schema(description = "분석할 데이터 카테고리", example = "sales")
|
||||
private String category;
|
||||
|
||||
@Schema(description = "지역 필터", example = "seoul")
|
||||
private String region;
|
||||
|
||||
@Schema(description = "조회할 결과 수 제한", example = "100")
|
||||
private Integer limit;
|
||||
|
||||
@Schema(description = "조회할 결과 시작 위치", example = "0")
|
||||
private Integer offset;
|
||||
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@Data
|
||||
public class CcCstUnfcNotiCarhDto {
|
||||
@Schema(description = "고객번호", example = "CUST123")
|
||||
private String csNo;
|
||||
|
||||
@Schema(description = "안내일시", example = "20260812093000")
|
||||
private String notiDt;
|
||||
|
||||
@Schema(description = "발송일련번호", example = "1")
|
||||
private int pmlSriaNo;
|
||||
|
||||
@Schema(description = "안내신청상세일시", example = "20260812092500")
|
||||
private String notiPetDtptDt;
|
||||
|
||||
@Schema(description = "안내장코드", example = "NT001")
|
||||
private String ntleCd;
|
||||
|
||||
@Schema(description = "안내장명", example = "카드대금 결제일 안내")
|
||||
private String ntleNm;
|
||||
|
||||
@Schema(description = "안내발송일련번호", example = "10")
|
||||
private int notiPmlSriaNo;
|
||||
|
||||
@Schema(description = "안내발송방법코드", example = "SMS")
|
||||
private String notiPmlMdCd;
|
||||
|
||||
@Schema(description = "안내수신정보암호화내용", example = "AbC123EncRypTed==")
|
||||
private String notiRcvInfoEncrCt;
|
||||
|
||||
@Schema(description = "메시지ID", example = "MSG20260812000001")
|
||||
private String msgId;
|
||||
|
||||
@Schema(description = "안내내용", example = "8월 카드대금 결제일은 8월 25일입니다.")
|
||||
private String notiCt;
|
||||
|
||||
@Schema(description = "통합안내발송결과코드", example = "00")
|
||||
private String unfcNotiPmlRsltCd;
|
||||
|
||||
@Schema(description = "통합안내발송결과상세코드", example = "0001")
|
||||
private String unfcNotiPmlRsltDtptCd;
|
||||
|
||||
@Schema(description = "발송시스템코드", example = "SYS01")
|
||||
private String pmlSystCd;
|
||||
|
||||
@Schema(description = "발송조직번호", example = "1001")
|
||||
private String pmlOgnzNo;
|
||||
|
||||
@Schema(description = "발송조직명", example = "고객지원팀")
|
||||
private String pmlOgnzNm;
|
||||
|
||||
@Schema(description = "발신기관조직명", example = "카드사업본부")
|
||||
private String msdilttOgnzNm;
|
||||
|
||||
@Schema(description = "조회구분명", example = "전체")
|
||||
private String inqrDvsnNm;
|
||||
|
||||
@Schema(description = "등록일시", example = "20260812090000")
|
||||
private String rgiDt;
|
||||
|
||||
@Schema(description = "등록자명", example = "홍길동")
|
||||
private String rgstNm;
|
||||
|
||||
@Schema(description = "등록자조직명", example = "고객지원팀")
|
||||
private String rgstOgnzNm;
|
||||
|
||||
@Schema(description = "최종변경자명", example = "김철수")
|
||||
private String lstPrwpNm;
|
||||
|
||||
@Schema(description = "최종변경일시", example = "20260812101500")
|
||||
private String lstChgDt;
|
||||
|
||||
@Schema(description = "최종변경자조직명", example = "운영지원팀")
|
||||
private String lstPrwpOgnzNm;
|
||||
|
||||
@Schema(description = "원천회사구분코드", example = "01")
|
||||
private String oriCpnyDvsnCd;
|
||||
|
||||
@Schema(description = "고객한글명", example = "이보람")
|
||||
private String cstHanNm;
|
||||
|
||||
@Schema(description = "시스템등록인사번호", example = "20260001")
|
||||
private String systRgiPrafNo;
|
||||
|
||||
@Schema(description = "시스템변경인사번호", example = "20260002")
|
||||
private String systChgPrafNo;
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.*;
|
||||
|
||||
@Data
|
||||
@Getter
|
||||
@Builder
|
||||
public class CustomerGuidanceToolRequest {
|
||||
@Schema(description = "고객번호", example = "CUST123")
|
||||
private String csNo;
|
||||
|
||||
@Schema(description = "안내장코드", example = "ab12321")
|
||||
private String ntleCd;
|
||||
|
||||
@Schema(description = "안내발송방법코드", example = "aa")
|
||||
private String notiPmlMdCd;
|
||||
|
||||
@Schema(description = "조회시작일자", example = "20260101")
|
||||
private String inqrStrYmd;
|
||||
|
||||
@Schema(description = "조회종료일자", example = "20260101")
|
||||
private String inqrEndYmd;
|
||||
|
||||
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Builder
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class CustomerGuidanceToolResponse {
|
||||
|
||||
private String resultCode;
|
||||
|
||||
private String resultMessage;
|
||||
|
||||
private List<CcCstUnfcNotiCarhDto> ccCstUnfcNotiCarhDtoList;
|
||||
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import io.shinhanlife.glow.GlowMciFieldInfo;
|
||||
import lombok.Data;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.mcc.dto
|
||||
* @className MciSampleStringResponse
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
* @create 2026.09.01
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class MciSampleStringResponse {
|
||||
@GlowMciFieldInfo(order = 1, length = 10, description = "이름")
|
||||
private String name;
|
||||
|
||||
@GlowMciFieldInfo(order = 2, length = 3, description = "나이")
|
||||
private int age;
|
||||
|
||||
@GlowMciFieldInfo(order = 3, length = 8, description = "가입일자(YYYYMMDD)")
|
||||
private String joinDate;
|
||||
|
||||
@GlowMciFieldInfo(order = 4, length = 2, description = "상태코드")
|
||||
private String statusCode;
|
||||
|
||||
@GlowMciFieldInfo(order = 5, length = 30, description = "타겟 리스트", target = MciSampleTargetDto.class)
|
||||
private List<MciSampleTargetDto> targetList;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user