Compare commits
55 Commits
6dd1fe6cda
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a59249e1db | ||
|
|
128fc3d022 | ||
|
|
899053aef5 | ||
|
|
0a5bc52e6a | ||
|
|
885963c81c | ||
|
|
b8a8da3b3c | ||
|
|
1a30c3a0cd | ||
|
|
50b6b8b9b0 | ||
|
|
140be4c7e9 | ||
|
|
75b93d47c2 | ||
|
|
f880f391d5 | ||
|
|
8a53bff74f | ||
|
|
a46943ca23 | ||
|
|
11b8b08890 | ||
|
|
2138ab7efa | ||
|
|
7dca243380 | ||
|
|
c90654bd1b | ||
|
|
334af3e40e | ||
|
|
ea27ed621a | ||
|
|
fbe7091975 | ||
|
|
7ccca1812f | ||
|
|
123fdb30a9 | ||
|
|
0ed927f845 | ||
|
|
60ac3bc4be | ||
|
|
0afa489165 | ||
|
|
8cd07a255c | ||
|
|
cc98d173ad | ||
|
|
c450f26d53 | ||
|
|
7660db3c38 | ||
|
|
beed6e7001 | ||
|
|
8612dd6ea7 | ||
|
|
db6ed5b09a | ||
|
|
db949be3f3 | ||
|
|
9edcdab450 | ||
|
|
82b1e6e267 | ||
|
|
e35b378d61 | ||
|
|
f8d6ed8da3 | ||
|
|
437d061bef | ||
|
|
9cce77a142 | ||
|
|
115aafa220 | ||
|
|
da7ed2e28b | ||
|
|
48ac7b9f9f | ||
|
|
580edd98a6 | ||
|
|
b9966bbbda | ||
|
|
438e30f987 | ||
|
|
fe9e00b5fb | ||
|
|
b0f12b578d | ||
|
|
39513a1470 | ||
|
|
821b249d50 | ||
|
|
e02a4e24e8 | ||
|
|
6b8ca5581f | ||
|
|
d6869f8191 | ||
|
|
515ab409ec | ||
|
|
b55f863de9 | ||
|
|
80ff392b5a |
16
Dockerfile
16
Dockerfile
@@ -1,30 +1,30 @@
|
|||||||
# 1. 鍮뚮뱶 ?섍꼍 (JDK 21)
|
# 1. ??š®ë±???<3F>ê¼<C3AA> (JDK 21)
|
||||||
FROM eclipse-temurin:21-jdk-alpine AS builder
|
FROM eclipse-temurin:21-jdk-alpine AS builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Gradle ?섑띁? ?뚯뒪 蹂듭궗
|
# Gradle ??‘ë<E28098><C3AB>?? ???’ª 蹂ë“ê¶?
|
||||||
COPY gradlew .
|
COPY gradlew .
|
||||||
COPY gradle gradle
|
COPY gradle gradle
|
||||||
COPY build.gradle settings.gradle ./
|
COPY build.gradle settings.gradle ./
|
||||||
COPY src src
|
COPY src src
|
||||||
|
|
||||||
# 沅뚰븳 遺??諛?鍮뚮뱶 (?뚯뒪???쒖쇅)
|
# æ²…ëš°ë¸??ºÂ€??è«???š®ë±?(???’ª????–쇅)
|
||||||
RUN chmod +x gradlew
|
RUN chmod +x gradlew
|
||||||
RUN ./gradlew clean build -x test
|
RUN ./gradlew clean build -x test
|
||||||
|
|
||||||
# 2. ?ㅽ뻾 ?섍꼍 (JRE 21)
|
# 2. ??½ë»¾ ??<3F>ê¼<C3AA> (JRE 21)
|
||||||
FROM eclipse-temurin:21-jre-alpine
|
FROM eclipse-temurin:21-jre-alpine
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# ??꾩〈 ?ㅼ젙 (?쒓뎅 ?쒓컙)
|
# ???꾩ã€???¼ì ™ (??“뎅 ??“ì»™)
|
||||||
RUN apk add --no-cache tzdata
|
RUN apk add --no-cache tzdata
|
||||||
ENV TZ=Asia/Seoul
|
ENV TZ=Asia/Seoul
|
||||||
|
|
||||||
# 鍮뚮뱶??JAR ?뚯씪 蹂듭궗
|
# ??š®ë±??JAR ???”ª 蹂ë“ê¶?
|
||||||
COPY --from=builder /app/build/libs/*.jar app.jar
|
COPY --from=builder /app/build/libs/*.jar app.jar
|
||||||
|
|
||||||
# 湲곕낯 ?ы듃 ?몄텧
|
# 湲곕???????몄텧
|
||||||
EXPOSE 8081
|
EXPOSE 8081
|
||||||
|
|
||||||
# 而⑦뀒?대꼫 ?ㅽ뻾 ??JAR ?ㅽ뻾
|
# ?Œâ‘¦???€ê¼???½ë»¾ ??JAR ??½ë»¾
|
||||||
ENTRYPOINT ["java", "-jar", "app.jar"]
|
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ DAP Backend는 2개의 주요 애플리케이션으로 분리 운영됩니다:
|
|||||||
- `HR`: 휴가 등록, 연차 갯수 조회
|
- `HR`: 휴가 등록, 연차 갯수 조회
|
||||||
- `CONTRACT`: 계약 상태, 계약 상세 조회
|
- `CONTRACT`: 계약 상태, 계약 상세 조회
|
||||||
- `CUSTOMER`: 고객 등급, 고객 상세 정보 조회
|
- `CUSTOMER`: 고객 등급, 고객 상세 정보 조회
|
||||||
|
- `SAMPLE`: 날씨, 환율, 명언 조회 등 외부 연동 샘플
|
||||||
- IntelliJ IDEA: `Run/Debug Configurations`에서 `DapTool*Application` 의 `Program arguments` 에 `--mcp.tool.target=NOTIFICATION` 입력
|
- IntelliJ IDEA: `Run/Debug Configurations`에서 `DapTool*Application` 의 `Program arguments` 에 `--mcp.tool.target=NOTIFICATION` 입력
|
||||||
|
|
||||||
|
|
||||||
@@ -66,7 +67,7 @@ DAP Backend는 2개의 주요 애플리케이션으로 분리 운영됩니다:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- **특정 카테고리 툴 필터링**: `McpBridge.java` 내부의 URI 파라미터(`?categoryKey=common`)를 수정하여 원하는 도메인의 툴만 선택적으로 AI에게 학습시킬 수 있습니다.
|
- **특정 카테고리 툴 필터링**: `McpBridge.java` 내부의 URI 파라미터(`?categoryKey=sample` 등)를 수정하여 원하는 도메인의 툴만 선택적으로 AI에게 학습시킬 수 있습니다.
|
||||||
|
|
||||||
### 2. 프로덕션 클라우드 AI (Google Cloud Agent Builder 등) 연동
|
### 2. 프로덕션 클라우드 AI (Google Cloud Agent Builder 등) 연동
|
||||||
실제 라이브 서비스에서 동작하는 클라우드 Agent Builder는 REST API 기반의 OpenAPI Spec을 요구합니다.
|
실제 라이브 서비스에서 동작하는 클라우드 Agent Builder는 REST API 기반의 OpenAPI Spec을 요구합니다.
|
||||||
|
|||||||
125
add_javadoc.py
125
add_javadoc.py
@@ -1,125 +0,0 @@
|
|||||||
import os
|
|
||||||
import re
|
|
||||||
|
|
||||||
JAVADOC_FILES = [
|
|
||||||
r'.\McpBridge.java',
|
|
||||||
r'.\dap-common\src\main\java\io\shinhanlife\dap\common\mcp\exception\GlobalExceptionHandler.java',
|
|
||||||
r'.\dap-common\src\main\java\io\shinhanlife\dap\common\util\ToolSourceUpdater.java',
|
|
||||||
r'.\dap-common\src\main\java\io\shinhanlife\glow\GlowAppServiceId.java',
|
|
||||||
r'.\dap-common\src\main\java\io\shinhanlife\glow\GlowControllerId.java',
|
|
||||||
r'.\dap-common\src\main\java\io\shinhanlife\glow\GlowIndexPaging.java',
|
|
||||||
r'.\dap-common\src\main\java\io\shinhanlife\glow\GlowMciFieldInfo.java',
|
|
||||||
r'.\dap-common\src\main\java\io\shinhanlife\glow\GlowMybatisMapper.java',
|
|
||||||
r'.\dap-common\src\main\java\io\shinhanlife\glow\GlowServiceGroupId.java',
|
|
||||||
r'.\dap-common\src\main\java\io\shinhanlife\glow\GlowTrgmField.java',
|
|
||||||
r'.\dap-common\src\test\java\io\shinhanlife\glow\util\GlowMciParserTest.java',
|
|
||||||
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\DapGatewayApplication.java',
|
|
||||||
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\audit\AuditLogService.java',
|
|
||||||
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\config\AgentResponseBudgetProperties.java',
|
|
||||||
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\config\McpGatewayProperties.java',
|
|
||||||
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\guardrail\GuardrailService.java',
|
|
||||||
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\guardrail\SensitiveDataMasker.java',
|
|
||||||
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\guardrail\ToolResponseGuardrailService.java',
|
|
||||||
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\presentation\ChatController.java',
|
|
||||||
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\presentation\McpRouterController.java',
|
|
||||||
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\presentation\ScaffoldingController.java',
|
|
||||||
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\redis\McpMonitorEventService.java',
|
|
||||||
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\redis\RedisToolTraceService.java',
|
|
||||||
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\resilience\CircuitBreaker.java',
|
|
||||||
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\resilience\CircuitBreakerMonitorController.java',
|
|
||||||
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\resilience\CircuitBreakerService.java',
|
|
||||||
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\resilience\CircuitBreakerSnapshot.java',
|
|
||||||
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\resilience\CircuitBreakerState.java',
|
|
||||||
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\resilience\FailureType.java',
|
|
||||||
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\resilience\RetryPolicy.java',
|
|
||||||
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\resilience\ToolExecutionException.java',
|
|
||||||
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\security\McpRequestContext.java',
|
|
||||||
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\security\McpRequestContextResolver.java',
|
|
||||||
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\security\ToolAuthorizationService.java',
|
|
||||||
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\service\ExecuteService.java',
|
|
||||||
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\sync\DynamicMcpController.java',
|
|
||||||
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\sync\DynamicMcpServerManager.java',
|
|
||||||
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\sync\RegistryMcpToolSpecificationFactory.java',
|
|
||||||
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\tool\large\AgentResponseBudgetService.java',
|
|
||||||
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\tool\large\LargeToolResponseService.java',
|
|
||||||
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\tool\large\PaginationRequestValidator.java',
|
|
||||||
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\tool\result\ToolExecutionResult.java',
|
|
||||||
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\tool\result\ToolExecutionResultFormatter.java',
|
|
||||||
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\transport\HttpToolInvoker.java',
|
|
||||||
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\transport\ToolInvoker.java',
|
|
||||||
r'.\dap-gateway\src\test\java\io\shinhanlife\dap\biz\mcp\gateway\DapGatewayApplicationTests.java',
|
|
||||||
r'.\dap-gateway\src\test\java\io\shinhanlife\dap\biz\mcp\gateway\tool\large\PaginationRequestValidatorTest.java',
|
|
||||||
r'.\dap-tool-core\src\main\java\io\shinhanlife\dap\biz\mcp\sample\converter\SampleLegacyConverter.java',
|
|
||||||
r'.\dap-tool-core\src\main\java\io\shinhanlife\dap\biz\mcp\sample\dto\SampleAiReqDto.java',
|
|
||||||
r'.\dap-tool-core\src\main\java\io\shinhanlife\dap\biz\mcp\tool\annotation\McpFunction.java',
|
|
||||||
r'.\dap-tool-core\src\main\java\io\shinhanlife\dap\biz\mcp\tool\annotation\McpParameter.java',
|
|
||||||
r'.\dap-tool-core\src\main\java\io\shinhanlife\dap\biz\mcp\tool\annotation\McpTool.java',
|
|
||||||
r'.\dap-tool-core\src\main\java\io\shinhanlife\dap\biz\mcp\tool\aop\ToolSlaMonitoringAspect.java',
|
|
||||||
r'.\dap-tool-core\src\main\java\io\shinhanlife\dap\biz\mcp\tool\presentation\BusinessToolController.java',
|
|
||||||
r'.\dap-tool-core\src\main\java\io\shinhanlife\dap\biz\mcp\tool\service\ToolRegistryHeartbeatSender.java',
|
|
||||||
r'.\dap-tool-core\src\main\java\io\shinhanlife\dap\biz\mcp\tool\util\JsonSchemaGenerator.java',
|
|
||||||
r'.\dap-tool-core\src\test\java\io\shinhanlife\dap\biz\mcp\sample\converter\SampleLegacyConverterTest.java',
|
|
||||||
r'.\dap-tool-email\src\main\java\io\shinhanlife\dap\biz\mcp\tool\email\DapToolEmailApplication.java',
|
|
||||||
r'.\dap-tool-other\src\main\java\io\shinhanlife\dap\biz\mcp\tool\dto\BalanceReq.java',
|
|
||||||
r'.\dap-tool-other\src\main\java\io\shinhanlife\dap\biz\mcp\tool\dto\BalanceRes.java',
|
|
||||||
r'.\dap-tool-other\src\main\java\io\shinhanlife\dap\biz\mcp\tool\other\DapToolOtherApplication.java',
|
|
||||||
r'.\dap-tool-other\src\main\java\io\shinhanlife\dap\biz\mcp\tool\service\BalanceService.java',
|
|
||||||
r'.\dap-tool-payment\src\main\java\io\shinhanlife\dap\biz\mcp\tool\payment\DapToolPaymentApplication.java',
|
|
||||||
r'.\dap-tool-sms\src\main\java\io\shinhanlife\dap\biz\mcp\tool\sms\DapToolSmsApplication.java',
|
|
||||||
r'.\dap-tool-sms\src\main\java\io\shinhanlife\dap\biz\mcp\tool\sms\converter\SmsLegacyConverter.java',
|
|
||||||
r'.\dap-tool-sms\src\main\java\io\shinhanlife\dap\biz\mcp\tool\sms\dto\SmsLegacyReqDto.java',
|
|
||||||
]
|
|
||||||
|
|
||||||
JAVADOC_TEMPLATE = '''/**
|
|
||||||
* @package {package}
|
|
||||||
* @className {classname}
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 김형식
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 김형식 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/'''
|
|
||||||
|
|
||||||
def get_package_and_class(content, filename):
|
|
||||||
pkg_match = re.search(r'^package\s+([^\s;]+)', content, re.MULTILINE)
|
|
||||||
package = pkg_match.group(1) if pkg_match else 'io.shinhanlife'
|
|
||||||
classname = os.path.splitext(filename)[0]
|
|
||||||
return package, classname
|
|
||||||
|
|
||||||
def add_javadoc(file_path):
|
|
||||||
if not os.path.exists(file_path):
|
|
||||||
print(f'SKIP (not found): {file_path}')
|
|
||||||
return
|
|
||||||
with open(file_path, 'r', encoding='utf-8') as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
if '@className' in content or '@author' in content:
|
|
||||||
print(f'SKIP (already has javadoc): {file_path}')
|
|
||||||
return
|
|
||||||
|
|
||||||
filename = os.path.basename(file_path)
|
|
||||||
package, classname = get_package_and_class(content, filename)
|
|
||||||
javadoc = JAVADOC_TEMPLATE.format(package=package, classname=classname)
|
|
||||||
|
|
||||||
# Insert after package declaration
|
|
||||||
pkg_pattern = re.compile(r'^(package\s+[^\s;]+;\s*\n)', re.MULTILINE)
|
|
||||||
match = pkg_pattern.search(content)
|
|
||||||
if match:
|
|
||||||
insert_pos = match.end()
|
|
||||||
new_content = content[:insert_pos] + '\n' + javadoc + '\n' + content[insert_pos:]
|
|
||||||
else:
|
|
||||||
new_content = javadoc + '\n' + content
|
|
||||||
|
|
||||||
with open(file_path, 'w', encoding='utf-8') as f:
|
|
||||||
f.write(new_content)
|
|
||||||
print(f'OK: {file_path}')
|
|
||||||
|
|
||||||
for f in JAVADOC_FILES:
|
|
||||||
add_javadoc(f)
|
|
||||||
|
|
||||||
print('\nDone!')
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id 'java-library'
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
api 'org.springframework.boot:spring-boot-starter-web'
|
|
||||||
api 'org.springframework.boot:spring-boot-starter-validation'
|
|
||||||
api 'org.springframework.boot:spring-boot-starter-data-redis'
|
|
||||||
|
|
||||||
api 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0'
|
|
||||||
api 'io.github.resilience4j:resilience4j-core:2.2.0'
|
|
||||||
api 'io.github.resilience4j:resilience4j-circuitbreaker:2.2.0'
|
|
||||||
api 'io.github.resilience4j:resilience4j-ratelimiter'
|
|
||||||
api 'io.github.resilience4j:resilience4j-retry:2.2.0'
|
|
||||||
api 'org.springframework.boot:spring-boot-starter-aop:3.3.0'
|
|
||||||
|
|
||||||
api 'org.springframework.boot:spring-boot-starter-jdbc'
|
|
||||||
api 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3'
|
|
||||||
api 'com.h2database:h2'
|
|
||||||
api 'p6spy:p6spy:3.9.1'
|
|
||||||
api 'io.lettuce:lettuce-core:6.6.0.RELEASE'
|
|
||||||
|
|
||||||
api "org.mapstruct:mapstruct:1.5.5.Final"
|
|
||||||
|
|
||||||
api 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.17.1'
|
|
||||||
api 'com.fasterxml.jackson.core:jackson-databind:2.17.1'
|
|
||||||
api 'com.networknt:json-schema-validator:1.4.0'
|
|
||||||
api 'org.springframework.kafka:spring-kafka:3.2.0'
|
|
||||||
api 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0'
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compileOnly 'org.projectlombok:lombok:1.18.32'
|
|
||||||
annotationProcessor 'org.projectlombok:lombok:1.18.32'
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
package io.shinhanlife.dap.common.integration.mci.dto;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Builder;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.common.integration.mci.dto
|
|
||||||
* @className ShinhanCommonHeaderDto
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 김형식
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 김형식 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
public class ShinhanCommonHeaderDto {
|
|
||||||
|
|
||||||
private String envrTypeCd; // 환경유형코드 (D, T, R)
|
|
||||||
private String glbId; // 전사공통키 (37 Byte)
|
|
||||||
private String pgrsSriaNo; // 진행일련번호
|
|
||||||
private String trgmVrsnInfoValu; // 전문버전정보값
|
|
||||||
private String tgrmEncrYn; // 전문암호화여부
|
|
||||||
private String gpcpCd; // 글로벌법인코드
|
|
||||||
private String appliDutjCd; // 어플리케이션업무코드
|
|
||||||
private String rcvSvcId; // 수신서비스ID
|
|
||||||
private String reqRspnScCd; // 요청응답구분코드 (S:요청, R:응답)
|
|
||||||
private String inqrTraTypeCd; // 조회거래유형코드
|
|
||||||
private String reqTgrmTnsmDtptDt; // 요청전문전송일시
|
|
||||||
private String itrIfId; // 인터페이스ID
|
|
||||||
}
|
|
||||||
@@ -1,372 +0,0 @@
|
|||||||
package io.shinhanlife.dap.common.util;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.util.Scanner;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MCP Tool 코드를 자동 생성(Scaffolding)하는 유틸리티 클래스
|
|
||||||
*
|
|
||||||
* [실행 방법]
|
|
||||||
* 방법 1. IDE(IntelliJ 등)에서 직접 실행 (대화형 모드 추천 ⭐)
|
|
||||||
* - 이 클래스(ToolScaffolder.java)를 열고 main 메서드를 직접 실행(Run)합니다.
|
|
||||||
* - 콘솔 창에 뜨는 질문에 차례대로 값을 입력하기만 하면 파일이 생성됩니다.
|
|
||||||
*
|
|
||||||
* 방법 2. 커맨드라인(터미널)에서 실행 (명령어 기반)
|
|
||||||
* - 컴파일: javac -encoding UTF-8 dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/util/ToolScaffolder.java
|
|
||||||
* - 실행: java -cp dap-tool-core/src/main/java io.shinhanlife.dap.mcc.util.ToolScaffolder [이름] [ID] "[설명]" "[그룹]" "[통신방식]" "[모듈명]"
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.mcc.util
|
|
||||||
* @className ToolScaffolder
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 김형식
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 김형식 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
public class ToolScaffolder {
|
|
||||||
|
|
||||||
private static final String BASE_PACKAGE = "io.shinhanlife.dap.mcc";
|
|
||||||
private static final String BASE_PACKAGE_PATH = "src/main/java/io/shinhanlife/dap/mcc";
|
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
|
||||||
Scanner scanner = new Scanner(System.in);
|
|
||||||
|
|
||||||
System.out.println("=========================================");
|
|
||||||
System.out.println(" MCP Tool Scaffolder (Java CLI) ");
|
|
||||||
System.out.println("=========================================\n");
|
|
||||||
|
|
||||||
String baseName = getOrAsk(args, 0, scanner, "1. 생성할 Tool의 기본 이름 (예: ExchangeRate) [영문 PascalCase]: ");
|
|
||||||
String interfaceId = getOrAsk(args, 1, scanner, "2. 레거시 API 인터페이스 ID (예: EXCH_001): ");
|
|
||||||
String description = getOrAsk(args, 2, scanner, "3. Tool 기능 설명 (예: 환율 조회): ");
|
|
||||||
String group = getOrAsk(args, 3, scanner, "4. Tool 소속 그룹 (예: NOTIFICATION, CLAIM, POLICY, HR, CONTRACT, CUSTOMER 등): ");
|
|
||||||
if (group.isEmpty()) group = "COMMON";
|
|
||||||
String routingType = getOrAsk(args, 4, scanner, "5. 통신 프로토콜 (예: HTTP, TCP, MCI, EAI): ");
|
|
||||||
if (routingType.trim().isEmpty()) {
|
|
||||||
routingType = "HTTP";
|
|
||||||
}
|
|
||||||
String moduleName = getOrAsk(args, 5, scanner, "6. 코드를 생성할 모듈 (기본: dap-tool-other): ");
|
|
||||||
if (moduleName.trim().isEmpty()) {
|
|
||||||
moduleName = "dap-tool-other";
|
|
||||||
}
|
|
||||||
|
|
||||||
String defaultAuthor = System.getProperty("user.name");
|
|
||||||
String defaultDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy.MM.dd"));
|
|
||||||
|
|
||||||
String author = getOrAsk(args, 6, scanner, "7. 작성자 (엔터 입력 시 '" + defaultAuthor + "'): ");
|
|
||||||
if (author.trim().isEmpty()) author = defaultAuthor;
|
|
||||||
String createDate = getOrAsk(args, 7, scanner, "8. 작성일 (엔터 입력 시 '" + defaultDate + "'): ");
|
|
||||||
if (createDate.trim().isEmpty()) createDate = defaultDate;
|
|
||||||
|
|
||||||
String result = scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, createDate, true);
|
|
||||||
System.out.println(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getOrAsk(String[] args, int index, Scanner scanner, String prompt) {
|
|
||||||
if (args.length > index) {
|
|
||||||
return args[index];
|
|
||||||
}
|
|
||||||
System.out.print(prompt);
|
|
||||||
return scanner.nextLine().trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String scaffold(String baseName, String interfaceId, String description, String group, String routingType, String moduleName, String author, String createDate, boolean register) throws IOException {
|
|
||||||
baseName = toPascalCase(baseName);
|
|
||||||
String envSourceDir = System.getenv("AXHUB_SOURCE_DIR");
|
|
||||||
Path rootDir = envSourceDir != null ? Paths.get(envSourceDir) : Paths.get(".");
|
|
||||||
|
|
||||||
Path serviceDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "service"));
|
|
||||||
Path dtoDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "dto"));
|
|
||||||
|
|
||||||
String shortName = moduleName.replace("dap-tool-", "").replace("-", "");
|
|
||||||
Path legacyDtoDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, shortName, "dto"));
|
|
||||||
Path converterDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, shortName, "converter"));
|
|
||||||
|
|
||||||
Files.createDirectories(serviceDir);
|
|
||||||
Files.createDirectories(dtoDir);
|
|
||||||
Files.createDirectories(legacyDtoDir);
|
|
||||||
Files.createDirectories(converterDir);
|
|
||||||
|
|
||||||
StringBuilder log = new StringBuilder();
|
|
||||||
|
|
||||||
// Generate Req DTO
|
|
||||||
String reqContent = """
|
|
||||||
package %s.dto;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @package %s.dto
|
|
||||||
* @className %sReq
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author %s
|
|
||||||
* @create %s
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* %s %s 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
|
||||||
public class %sReq {
|
|
||||||
// TODO: Add request fields here
|
|
||||||
}
|
|
||||||
""".formatted(BASE_PACKAGE, BASE_PACKAGE, baseName, author, createDate, createDate, author, baseName);
|
|
||||||
Files.writeString(dtoDir.resolve(baseName + "Req.java"), reqContent);
|
|
||||||
|
|
||||||
// Generate Res DTO
|
|
||||||
String resContent = """
|
|
||||||
package %s.dto;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @package %s.dto
|
|
||||||
* @className %sRes
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author %s
|
|
||||||
* @create %s
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* %s %s 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
|
||||||
public class %sRes {
|
|
||||||
private String status;
|
|
||||||
private String message;
|
|
||||||
// TODO: Add response fields here
|
|
||||||
}
|
|
||||||
""".formatted(BASE_PACKAGE, BASE_PACKAGE, baseName, author, createDate, createDate, author, baseName);
|
|
||||||
Files.writeString(dtoDir.resolve(baseName + "Res.java"), resContent);
|
|
||||||
|
|
||||||
String toolName = baseName.isEmpty() ? baseName : Character.toLowerCase(baseName.charAt(0)) + baseName.substring(1);
|
|
||||||
|
|
||||||
// Generate Service
|
|
||||||
String serviceContent = """
|
|
||||||
package %s.service;
|
|
||||||
|
|
||||||
import %s.annotation.McpFunction;
|
|
||||||
import %s.annotation.McpTool;
|
|
||||||
import %s.dto.%sReq;
|
|
||||||
import %s.dto.%sRes;
|
|
||||||
import %s.%s.converter.%sLegacyConverter;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.mapstruct.factory.Mappers;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @package %s.service
|
|
||||||
* @className %sService
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author %s
|
|
||||||
* @create %s
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* %s %s 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
@Service
|
|
||||||
@McpTool(
|
|
||||||
routingType = "%s",
|
|
||||||
categoryKey = "%s"
|
|
||||||
)
|
|
||||||
public class %sService extends AbstractMcpToolService {
|
|
||||||
|
|
||||||
private final %sLegacyConverter converter = Mappers.getMapper(%sLegacyConverter.class);
|
|
||||||
|
|
||||||
@McpFunction(
|
|
||||||
displayName = "%s 툴",
|
|
||||||
name = "%s",
|
|
||||||
description = "%s",
|
|
||||||
prompt = "%s",
|
|
||||||
mappingId = "%s",
|
|
||||||
register = %s,
|
|
||||||
requiresApproval = false
|
|
||||||
)
|
|
||||||
public Object execute(%sReq req) {
|
|
||||||
// %sLegacyReq legacyReq = converter.toLegacyReq(req);
|
|
||||||
return executeLegacy("%s", "%s", req); // Or pass legacyReq
|
|
||||||
}
|
|
||||||
}
|
|
||||||
""".formatted(
|
|
||||||
BASE_PACKAGE,
|
|
||||||
BASE_PACKAGE,
|
|
||||||
BASE_PACKAGE,
|
|
||||||
BASE_PACKAGE, baseName,
|
|
||||||
BASE_PACKAGE, baseName,
|
|
||||||
BASE_PACKAGE, shortName, baseName,
|
|
||||||
BASE_PACKAGE,
|
|
||||||
baseName,
|
|
||||||
author,
|
|
||||||
createDate,
|
|
||||||
createDate, author,
|
|
||||||
routingType, group.toLowerCase(),
|
|
||||||
baseName,
|
|
||||||
baseName, baseName,
|
|
||||||
baseName, toolName, description, description + " 해줘.", interfaceId, register,
|
|
||||||
baseName,
|
|
||||||
baseName,
|
|
||||||
routingType, interfaceId
|
|
||||||
);
|
|
||||||
|
|
||||||
Files.writeString(serviceDir.resolve(baseName + "Service.java"), serviceContent);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Generate Legacy Req DTO
|
|
||||||
String legacyReqContent = """
|
|
||||||
package %s.%s.dto;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @package %s.%s.dto
|
|
||||||
* @className %sLegacyReq
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author %s
|
|
||||||
* @create %s
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* %s %s 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class %sLegacyReq {
|
|
||||||
// TODO: Add legacy request fields here
|
|
||||||
}
|
|
||||||
""".formatted(BASE_PACKAGE, shortName, BASE_PACKAGE, shortName, baseName, author, createDate, createDate, author, baseName);
|
|
||||||
Files.writeString(legacyDtoDir.resolve(baseName + "LegacyReq.java"), legacyReqContent);
|
|
||||||
|
|
||||||
// Generate Legacy Res DTO
|
|
||||||
String legacyResContent = """
|
|
||||||
package %s.%s.dto;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @package %s.%s.dto
|
|
||||||
* @className %sLegacyRes
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author %s
|
|
||||||
* @create %s
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* %s %s 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class %sLegacyRes {
|
|
||||||
// TODO: Add legacy response fields here
|
|
||||||
}
|
|
||||||
""".formatted(BASE_PACKAGE, shortName, BASE_PACKAGE, shortName, baseName, author, createDate, createDate, author, baseName);
|
|
||||||
Files.writeString(legacyDtoDir.resolve(baseName + "LegacyRes.java"), legacyResContent);
|
|
||||||
|
|
||||||
// Generate Legacy Converter
|
|
||||||
String converterContent = """
|
|
||||||
package %s.%s.converter;
|
|
||||||
|
|
||||||
import %s.dto.%sReq;
|
|
||||||
import %s.dto.%sRes;
|
|
||||||
import %s.%s.dto.%sLegacyReq;
|
|
||||||
import %s.%s.dto.%sLegacyRes;
|
|
||||||
import org.mapstruct.Mapper;
|
|
||||||
import org.mapstruct.Mapping;
|
|
||||||
import org.mapstruct.factory.Mappers;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @package %s.%s.converter
|
|
||||||
* @className %sLegacyConverter
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author %s
|
|
||||||
* @create %s
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* %s %s 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
@Mapper(componentModel = "spring")
|
|
||||||
public interface %sLegacyConverter {
|
|
||||||
|
|
||||||
// @Mapping(source = "sourceField", target = "targetField")
|
|
||||||
%sLegacyReq toLegacyReq(%sReq req);
|
|
||||||
|
|
||||||
%sRes toRes(%sLegacyRes legacyRes);
|
|
||||||
}
|
|
||||||
""".formatted(
|
|
||||||
BASE_PACKAGE, shortName,
|
|
||||||
BASE_PACKAGE, baseName,
|
|
||||||
BASE_PACKAGE, baseName,
|
|
||||||
BASE_PACKAGE, shortName, baseName,
|
|
||||||
BASE_PACKAGE, shortName, baseName,
|
|
||||||
BASE_PACKAGE, shortName, baseName, author, createDate, createDate, author,
|
|
||||||
baseName, baseName, baseName, baseName, baseName, baseName
|
|
||||||
);
|
|
||||||
Files.writeString(converterDir.resolve(baseName + "LegacyConverter.java"), converterContent);
|
|
||||||
|
|
||||||
log.append("\n=========================================\n");
|
|
||||||
log.append(" Scaffolding Complete!\n");
|
|
||||||
log.append("=========================================\n");
|
|
||||||
log.append("[Service] ").append(serviceDir.resolve(baseName + "Service.java")).append("\n");
|
|
||||||
log.append("[Req DTO] ").append(dtoDir.resolve(baseName + "Req.java")).append("\n");
|
|
||||||
log.append("[Res DTO] ").append(dtoDir.resolve(baseName + "Res.java")).append("\n");
|
|
||||||
log.append("[Legacy Req DTO] ").append(legacyDtoDir.resolve(baseName + "LegacyReq.java")).append("\n");
|
|
||||||
log.append("[Legacy Res DTO] ").append(legacyDtoDir.resolve(baseName + "LegacyRes.java")).append("\n");
|
|
||||||
log.append("[Legacy Converter] ").append(converterDir.resolve(baseName + "LegacyConverter.java")).append("\n");
|
|
||||||
log.append("\n Tip: ").append(interfaceId).append(" 목업 데이터를 mock-responses.json에 추가하세요.\n");
|
|
||||||
|
|
||||||
return log.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String toPascalCase(String str) {
|
|
||||||
if (str == null || str.isEmpty()) {
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
StringBuilder result = new StringBuilder();
|
|
||||||
boolean capitalizeNext = true;
|
|
||||||
for (char c : str.toCharArray()) {
|
|
||||||
if (c == '_' || c == '-' || c == ' ') {
|
|
||||||
capitalizeNext = true;
|
|
||||||
} else if (capitalizeNext) {
|
|
||||||
result.append(Character.toUpperCase(c));
|
|
||||||
capitalizeNext = false;
|
|
||||||
} else {
|
|
||||||
result.append(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (result.length() > 0) {
|
|
||||||
result.setCharAt(0, Character.toUpperCase(result.charAt(0)));
|
|
||||||
}
|
|
||||||
return result.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,8 @@ WORKDIR /app
|
|||||||
COPY gradlew .
|
COPY gradlew .
|
||||||
COPY gradle gradle
|
COPY gradle gradle
|
||||||
COPY build.gradle settings.gradle ./
|
COPY build.gradle settings.gradle ./
|
||||||
COPY dap-common dap-common
|
|
||||||
|
COPY dap-tool-core dap-tool-core
|
||||||
COPY dap-gateway dap-gateway
|
COPY dap-gateway dap-gateway
|
||||||
RUN chmod +x gradlew
|
RUN chmod +x gradlew
|
||||||
RUN ./gradlew :dap-gateway:build -x test
|
RUN ./gradlew :dap-gateway:build -x test
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':dap-common')
|
implementation project(':dap-tool-core')
|
||||||
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
|
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
|
||||||
|
|||||||
@@ -95,11 +95,13 @@ public class ScaffoldingController {
|
|||||||
File dir = new File(sourceDir);
|
File dir = new File(sourceDir);
|
||||||
File[] files = dir.listFiles(f -> f.isDirectory() && f.getName().startsWith("dap-tool-") && !f.getName().equals("dap-tool-core"));
|
File[] files = dir.listFiles(f -> f.isDirectory() && f.getName().startsWith("dap-tool-") && !f.getName().equals("dap-tool-core"));
|
||||||
|
|
||||||
if (files == null) return List.of("dap-tool-other");
|
if (files == null || files.length == 0) {
|
||||||
|
return List.of("dap-tool-other", "dap-tool-email", "dap-tool-hr", "dap-tool-payment", "dap-tool-sms");
|
||||||
|
}
|
||||||
|
|
||||||
return Arrays.stream(files).map(File::getName).sorted().collect(Collectors.toList());
|
return Arrays.stream(files).map(File::getName).sorted().collect(Collectors.toList());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return List.of("dap-tool-other");
|
return List.of("dap-tool-other", "dap-tool-email", "dap-tool-hr", "dap-tool-payment", "dap-tool-sms");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -162,22 +162,53 @@ public class ExecuteService {
|
|||||||
long elapsedMillis = elapsedMillis(startedAt);
|
long elapsedMillis = elapsedMillis(startedAt);
|
||||||
|
|
||||||
String responseText = "";
|
String responseText = "";
|
||||||
try { responseText = objectMapper.writeValueAsString(result); } catch (Exception ignore) {}
|
long originalSize = 0;
|
||||||
|
try {
|
||||||
|
responseText = objectMapper.writeValueAsString(result);
|
||||||
|
originalSize = responseText.getBytes(java.nio.charset.StandardCharsets.UTF_8).length;
|
||||||
|
} catch (Exception ignore) {}
|
||||||
|
|
||||||
|
Map<String, Object> finalResult = new java.util.LinkedHashMap<>();
|
||||||
|
finalResult.put("status", "ok");
|
||||||
|
finalResult.put("result", result);
|
||||||
|
finalResult.put("error_code", null);
|
||||||
|
finalResult.put("error_message", null);
|
||||||
|
finalResult.put("elapsed_ms", elapsedMillis);
|
||||||
|
finalResult.put("truncated", false);
|
||||||
|
finalResult.put("original_size", originalSize);
|
||||||
|
|
||||||
auditLogService.toolFinished(context, metadata.getName(), elapsedMillis, true, "");
|
auditLogService.toolFinished(context, metadata.getName(), elapsedMillis, true, "");
|
||||||
redisTrace.finished(context, metadata, argumentsNode, elapsedMillis, true, "", responseText);
|
redisTrace.finished(context, metadata, argumentsNode, elapsedMillis, true, "", responseText);
|
||||||
|
|
||||||
return result;
|
return finalResult;
|
||||||
} catch (ToolExecutionException error) {
|
} catch (ToolExecutionException error) {
|
||||||
long elapsedMillis = elapsedMillis(startedAt);
|
long elapsedMillis = elapsedMillis(startedAt);
|
||||||
auditLogService.toolFinished(context, toolName, elapsedMillis, false, error.failureType().name());
|
auditLogService.toolFinished(context, toolName, elapsedMillis, false, error.failureType().name());
|
||||||
redisTrace.finished(context, metadata, argumentsNode, elapsedMillis, false, error.failureType().name(), "");
|
redisTrace.finished(context, metadata, argumentsNode, elapsedMillis, false, error.failureType().name(), "");
|
||||||
throw error;
|
|
||||||
|
Map<String, Object> errorResult = new java.util.LinkedHashMap<>();
|
||||||
|
errorResult.put("status", "error");
|
||||||
|
errorResult.put("result", null);
|
||||||
|
errorResult.put("error_code", error.failureType().name());
|
||||||
|
errorResult.put("error_message", error.getMessage());
|
||||||
|
errorResult.put("elapsed_ms", elapsedMillis);
|
||||||
|
errorResult.put("truncated", false);
|
||||||
|
errorResult.put("original_size", 0);
|
||||||
|
return errorResult;
|
||||||
} catch (Exception error) {
|
} catch (Exception error) {
|
||||||
long elapsedMillis = elapsedMillis(startedAt);
|
long elapsedMillis = elapsedMillis(startedAt);
|
||||||
auditLogService.toolFinished(context, toolName, elapsedMillis, false, FailureType.INTERNAL_ERROR.name());
|
auditLogService.toolFinished(context, toolName, elapsedMillis, false, FailureType.INTERNAL_ERROR.name());
|
||||||
redisTrace.finished(context, metadata, argumentsNode, elapsedMillis, false, FailureType.INTERNAL_ERROR.name(), "");
|
redisTrace.finished(context, metadata, argumentsNode, elapsedMillis, false, FailureType.INTERNAL_ERROR.name(), "");
|
||||||
throw new ToolExecutionException(FailureType.INTERNAL_ERROR, "Tool execution failed: " + toolName, error);
|
|
||||||
|
Map<String, Object> errorResult = new java.util.LinkedHashMap<>();
|
||||||
|
errorResult.put("status", "error");
|
||||||
|
errorResult.put("result", null);
|
||||||
|
errorResult.put("error_code", FailureType.INTERNAL_ERROR.name());
|
||||||
|
errorResult.put("error_message", error.getMessage());
|
||||||
|
errorResult.put("elapsed_ms", elapsedMillis);
|
||||||
|
errorResult.put("truncated", false);
|
||||||
|
errorResult.put("original_size", 0);
|
||||||
|
return errorResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,20 +261,17 @@ public class ExecuteService {
|
|||||||
if (metadata.getPodUrl() != null && !metadata.getPodUrl().isEmpty()) {
|
if (metadata.getPodUrl() != null && !metadata.getPodUrl().isEmpty()) {
|
||||||
targetUrl = metadata.getPodUrl();
|
targetUrl = metadata.getPodUrl();
|
||||||
}
|
}
|
||||||
String executeApiUrl = targetUrl + "/mcp/api/v1/tools/call";
|
String executeApiUrl = targetUrl + "/mcp/" + metadata.getName();
|
||||||
|
|
||||||
|
Map<String, String> headers = new java.util.HashMap<>();
|
||||||
|
headers.put("trace-id", context.requestId());
|
||||||
|
headers.put("request-id", java.util.UUID.randomUUID().toString());
|
||||||
|
|
||||||
ObjectNode pageArguments = paginationValidator.normalize(arguments);
|
ObjectNode pageArguments = paginationValidator.normalize(arguments);
|
||||||
LargeToolResponseService.Collector collector = largeResponses.newCollector(metadata.getName(), context.requestId());
|
LargeToolResponseService.Collector collector = largeResponses.newCollector(metadata.getName(), context.requestId());
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
Map<String, Object> pagePayload = new HashMap<>(payload);
|
Map<String, Object> pagePayload = objectMapper.convertValue(pageArguments, Map.class);
|
||||||
if (pagePayload.containsKey("params")) {
|
|
||||||
Map<String, Object> params = new HashMap<>((Map<String, Object>) pagePayload.get("params"));
|
|
||||||
params.put("arguments", objectMapper.convertValue(pageArguments, Map.class));
|
|
||||||
pagePayload.put("params", params);
|
|
||||||
} else {
|
|
||||||
pagePayload.put("arguments", objectMapper.convertValue(pageArguments, Map.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
log.info(" [ExecuteService] 요청 페이로드(마스킹 적용): {}", objectMapper.writeValueAsString(dataMasker.mask(objectMapper.valueToTree(pagePayload))));
|
log.info(" [ExecuteService] 요청 페이로드(마스킹 적용): {}", objectMapper.writeValueAsString(dataMasker.mask(objectMapper.valueToTree(pagePayload))));
|
||||||
@@ -251,13 +279,16 @@ public class ExecuteService {
|
|||||||
|
|
||||||
JsonNode data = null;
|
JsonNode data = null;
|
||||||
try {
|
try {
|
||||||
data = toolInvoker.invoke(pagePayload, executeApiUrl);
|
data = toolInvoker.invoke(pagePayload, executeApiUrl, headers);
|
||||||
|
} catch (org.springframework.web.client.RestClientResponseException e) {
|
||||||
|
// HTTP 4xx, 5xx 에러는 연결 오류가 아니라 비즈니스 로직 오류이거나 검증 실패이므로 원본 에러를 그대로 반환
|
||||||
|
throw new ToolExecutionException(FailureType.SERVER_ERROR, "Tool Pod HTTP 에러 (" + e.getStatusCode() + "): " + e.getResponseBodyAsString());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (executeApiUrl.contains("http://tool-")) {
|
if (executeApiUrl.contains("http://tool-")) {
|
||||||
String fallbackUrl = executeApiUrl.replaceAll("http://tool-[a-zA-Z0-9-]+", "http://localhost");
|
String fallbackUrl = executeApiUrl.replaceAll("http://tool-[a-zA-Z0-9-]+", "http://localhost");
|
||||||
log.warn(" [ExecuteService] 호스트를 찾을 수 없어 localhost로 재시도합니다: {}", fallbackUrl);
|
log.warn(" [ExecuteService] 호스트를 찾을 수 없어 localhost로 재시도합니다: {}", fallbackUrl);
|
||||||
try {
|
try {
|
||||||
data = toolInvoker.invoke(pagePayload, fallbackUrl);
|
data = toolInvoker.invoke(pagePayload, fallbackUrl, headers);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
throw new ToolExecutionException(FailureType.SERVER_ERROR, "Tool Pod 호출 실패 (localhost 재시도 포함): " + ex.getMessage());
|
throw new ToolExecutionException(FailureType.SERVER_ERROR, "Tool Pod 호출 실패 (localhost 재시도 포함): " + ex.getMessage());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ public class DynamicMcpServerManager {
|
|||||||
getOrCreateServer("sms");
|
getOrCreateServer("sms");
|
||||||
getOrCreateServer("email");
|
getOrCreateServer("email");
|
||||||
getOrCreateServer("other");
|
getOrCreateServer("other");
|
||||||
|
getOrCreateServer("sample");
|
||||||
}
|
}
|
||||||
|
|
||||||
private McpSyncServer getOrCreateServer(String categoryKey) {
|
private McpSyncServer getOrCreateServer(String categoryKey) {
|
||||||
|
|||||||
@@ -40,14 +40,17 @@ public class HttpToolInvoker implements ToolInvoker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JsonNode invoke(Map<String, Object> payload, String targetUrl) {
|
public JsonNode invoke(Map<String, Object> payload, String targetUrl, Map<String, String> headers) {
|
||||||
try {
|
try {
|
||||||
Object httpResult = restClient.post()
|
RestClient.RequestBodySpec requestSpec = restClient.post()
|
||||||
.uri(targetUrl)
|
.uri(targetUrl)
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON);
|
||||||
// TODO: Use actual tenant's key
|
|
||||||
.header("X-Trace-Id", UUID.randomUUID().toString())
|
if (headers != null) {
|
||||||
.body(payload)
|
headers.forEach(requestSpec::header);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object httpResult = requestSpec.body(payload)
|
||||||
.retrieve()
|
.retrieve()
|
||||||
.body(Object.class);
|
.body(Object.class);
|
||||||
|
|
||||||
|
|||||||
@@ -22,5 +22,5 @@ import java.util.Map;
|
|||||||
* Tool 서버 호출 transport의 최소 공통 인터페이스입니다.
|
* Tool 서버 호출 transport의 최소 공통 인터페이스입니다.
|
||||||
*/
|
*/
|
||||||
public interface ToolInvoker {
|
public interface ToolInvoker {
|
||||||
JsonNode invoke(Map<String, Object> payload, String targetUrl);
|
JsonNode invoke(Map<String, Object> payload, String targetUrl, Map<String, String> headers);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,11 @@
|
|||||||
# spring.datasource.password=dev_password
|
# spring.datasource.password=dev_password
|
||||||
|
|
||||||
# Render 클라우드 환경 전용 Fallback 라우팅
|
# Render 클라우드 환경 전용 Fallback 라우팅
|
||||||
mcp.gateway.fallback.routes.sms=https://dap-tool-sms.onrender.com
|
mcp.gateway.fallback.routes.sms=https://axhub-tool-sms.onrender.com
|
||||||
mcp.gateway.fallback.routes.email=https://dap-tool-email.onrender.com
|
mcp.gateway.fallback.routes.email=https://axhub-tool-email.onrender.com
|
||||||
mcp.gateway.fallback.default-url=https://dap-tool-other.onrender.com
|
mcp.gateway.fallback.default-url=https://axhub-tool-other.onrender.com
|
||||||
mcp.gateway.fallback.routes.payment=https://dap-tool-payment.onrender.com
|
mcp.gateway.fallback.routes.payment=https://axhub-tool-payment.onrender.com
|
||||||
mcp.gateway.fallback.routes.hr=https://dap-tool-hr.onrender.com
|
mcp.gateway.fallback.routes.hr=https://axhub-tool-hr.onrender.com
|
||||||
|
|
||||||
# --- 신한라이프 EAI/MCI 연계 IP 정보 (개발 환경) ---
|
# --- 신한라이프 EAI/MCI 연계 IP 정보 (개발 환경) ---
|
||||||
shinhan.integration.envrTypeCd=D
|
shinhan.integration.envrTypeCd=D
|
||||||
|
|||||||
@@ -464,6 +464,7 @@
|
|||||||
<label class="form-label">Domain Category</label>
|
<label class="form-label">Domain Category</label>
|
||||||
<input type="text" class="form-control" name="categoryKey" pattern="^[a-z0-9][a-z0-9_-]*$" list="groupList" placeholder="e.g. common, hr, payment" oninput="this.value = this.value.toLowerCase()" required>
|
<input type="text" class="form-control" name="categoryKey" pattern="^[a-z0-9][a-z0-9_-]*$" list="groupList" placeholder="e.g. common, hr, payment" oninput="this.value = this.value.toLowerCase()" required>
|
||||||
<datalist id="groupList">
|
<datalist id="groupList">
|
||||||
|
<option value="sample">sample</option>
|
||||||
<option value="common">common</option>
|
<option value="common">common</option>
|
||||||
<option value="customer">customer</option>
|
<option value="customer">customer</option>
|
||||||
<option value="contract">contract</option>
|
<option value="contract">contract</option>
|
||||||
@@ -485,7 +486,11 @@
|
|||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label">Target Module</label>
|
<label class="form-label">Target Module</label>
|
||||||
<select class="form-select" name="moduleName" id="targetModuleSelect" required>
|
<select class="form-select" name="moduleName" id="targetModuleSelect" required>
|
||||||
<option value="axhub-tool-other">axhub-tool-other</option>
|
<option value="dap-tool-other">dap-tool-other</option>
|
||||||
|
<option value="dap-tool-email">dap-tool-email</option>
|
||||||
|
<option value="dap-tool-hr">dap-tool-hr</option>
|
||||||
|
<option value="dap-tool-payment">dap-tool-payment</option>
|
||||||
|
<option value="dap-tool-sms">dap-tool-sms</option>
|
||||||
</select>
|
</select>
|
||||||
<div class="input-hint">Destination Pod directory.</div>
|
<div class="input-hint">Destination Pod directory.</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -653,7 +658,7 @@
|
|||||||
.then(modules => {
|
.then(modules => {
|
||||||
const select = document.getElementById('targetModuleSelect');
|
const select = document.getElementById('targetModuleSelect');
|
||||||
if (modules && modules.length > 0) {
|
if (modules && modules.length > 0) {
|
||||||
select.innerHTML = modules.map(m => `<option value="${m}" ${m === 'axhub-tool-other' ? 'selected' : ''}>${m}</option>`).join('');
|
select.innerHTML = modules.map(m => `<option value="${m}" ${m === 'dap-tool-other' ? 'selected' : ''}>${m}</option>`).join('');
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(err => console.error('Failed to load modules:', err));
|
.catch(err => console.error('Failed to load modules:', err));
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package io.shinhanlife.dap.biz.mcp.gateway.sync;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
|
||||||
|
class DynamicMcpControllerRouteTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void exposesOnlySupportedCategoryAwareMcpRoutes() {
|
||||||
|
Set<String> getPaths = Arrays.stream(DynamicMcpController.class.getDeclaredMethods())
|
||||||
|
.flatMap(method -> Arrays.stream(method.getAnnotationsByType(GetMapping.class)))
|
||||||
|
.flatMap(mapping -> Arrays.stream(mapping.value()))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
Set<String> postPaths = Arrays.stream(DynamicMcpController.class.getDeclaredMethods())
|
||||||
|
.flatMap(method -> Arrays.stream(method.getAnnotationsByType(PostMapping.class)))
|
||||||
|
.flatMap(mapping -> Arrays.stream(mapping.value()))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
assertTrue(getPaths.contains("/mcp/sse/{category}"));
|
||||||
|
assertTrue(postPaths.contains("/mcp/message/{category}"));
|
||||||
|
assertTrue(postPaths.contains("/mcp/custom/{category}"));
|
||||||
|
assertFalse(postPaths.contains("/mcp"));
|
||||||
|
assertFalse(postPaths.contains("/mcp/initialize"));
|
||||||
|
assertFalse(postPaths.contains("/mcp/sse/initialize"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package io.shinhanlife.dap.biz.mcp.gateway.sync;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import io.modelcontextprotocol.spec.McpSchema;
|
||||||
|
import io.shinhanlife.dap.biz.mcp.gateway.dto.ToolMetadata;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class RegistryMcpToolSpecificationFactoryTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void exposesMetadataBehaviorHintsInMcpToolSpecification() {
|
||||||
|
ToolMetadata metadata = ToolMetadata.builder()
|
||||||
|
.name("customer_lookup")
|
||||||
|
.description("Looks up customer information")
|
||||||
|
.readOnlyHint(true)
|
||||||
|
.destructiveHint(false)
|
||||||
|
.idempotentHint(true)
|
||||||
|
.openWorldHint(false)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
RegistryMcpToolSpecificationFactory factory = new RegistryMcpToolSpecificationFactory(null, new ObjectMapper());
|
||||||
|
McpSchema.Tool tool = factory.create(metadata).tool();
|
||||||
|
|
||||||
|
assertTrue(tool.annotations().readOnlyHint());
|
||||||
|
assertFalse(tool.annotations().destructiveHint());
|
||||||
|
assertTrue(tool.annotations().idempotentHint());
|
||||||
|
assertFalse(tool.annotations().openWorldHint());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,30 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api project(':dap-common')
|
api 'org.springframework.boot:spring-boot-starter-web'
|
||||||
|
api 'org.springframework.boot:spring-boot-starter-validation'
|
||||||
|
api 'org.springframework.boot:spring-boot-starter-data-redis'
|
||||||
|
|
||||||
|
api 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0'
|
||||||
|
api 'io.github.resilience4j:resilience4j-core:2.2.0'
|
||||||
|
api 'io.github.resilience4j:resilience4j-circuitbreaker:2.2.0'
|
||||||
|
api 'io.github.resilience4j:resilience4j-ratelimiter'
|
||||||
|
api 'io.github.resilience4j:resilience4j-retry:2.2.0'
|
||||||
|
api 'org.springframework.boot:spring-boot-starter-aop:3.3.0'
|
||||||
|
|
||||||
|
api 'org.springframework.boot:spring-boot-starter-jdbc'
|
||||||
|
api 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3'
|
||||||
|
api 'com.h2database:h2'
|
||||||
|
api 'p6spy:p6spy:3.9.1'
|
||||||
|
api 'io.lettuce:lettuce-core:6.6.0.RELEASE'
|
||||||
|
|
||||||
|
api "org.mapstruct:mapstruct:1.5.5.Final"
|
||||||
|
|
||||||
|
api 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.17.1'
|
||||||
|
api 'com.fasterxml.jackson.core:jackson-databind:2.17.1'
|
||||||
|
api 'com.networknt:json-schema-validator:1.4.0'
|
||||||
|
api 'org.springframework.kafka:spring-kafka:3.2.0'
|
||||||
|
api 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package io.shinhanlife.dap.common.integration.mci.component;
|
||||||
|
|
||||||
|
import io.shinhanlife.glow.communication.dto.Transfer;
|
||||||
|
import io.shinhanlife.glow.communication.module.mci.component.GlowMciComponent;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 신한라이프 내부 Glow 표준 컴포넌트 어댑터
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AxhubMciComponent {
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
private final GlowMciComponent mci;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <O, I> Transfer<O> callTo(String itrfName, String rcvSvcId, I inputDto, Class<O> resBodyClass) {
|
||||||
|
log.info("[AxhubMciComponent] MCI 호출 준비 - 인터페이스: {}, 수신서비스: {}", itrfName, rcvSvcId);
|
||||||
|
|
||||||
|
// Header 세팅 로직 생략 (Mock)
|
||||||
|
|
||||||
|
Transfer<Object> request = Transfer.builder()
|
||||||
|
.body(inputDto)
|
||||||
|
.resBodyClass((Class<Object>) (Class<?>) resBodyClass)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return syncMci(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private <O> Transfer<O> syncMci(Transfer<Object> request) {
|
||||||
|
log.info("[AxhubMciComponent] GlowMciComponent.sync() 호출");
|
||||||
|
return (Transfer<O>) mci.sync(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package io.shinhanlife.dap.common.integration.mci.config;
|
||||||
|
|
||||||
|
import io.shinhanlife.glow.communication.module.mci.component.GlowMciComponent;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: 실제 Glow Framework 의존성이 추가되어 io.shinhanlife.glow 패키지가
|
||||||
|
* ComponentScan에 잡히게 되면 이 설정 클래스는 삭제하세요.
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class GlowMockConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
public GlowMciComponent glowMciComponent() {
|
||||||
|
return new GlowMciComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,7 +21,6 @@ import org.springframework.context.annotation.Configuration;
|
|||||||
*/
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@Configuration
|
|
||||||
@ConfigurationProperties(prefix = "shinhan.integration")
|
@ConfigurationProperties(prefix = "shinhan.integration")
|
||||||
public class ShinhanIntegrationProperties {
|
public class ShinhanIntegrationProperties {
|
||||||
|
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package io.shinhanlife.dap.common.integration.mci.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package io.shinhanlife.dap.common.integration.mci.dto
|
||||||
|
* @className OlCommonHeaderDto
|
||||||
|
* @description AX HUB 시스템 처리 클래스 - OL(구 오렌지라이프) 공통 헤더
|
||||||
|
* @author 김형식
|
||||||
|
* @create 2026.09.01
|
||||||
|
* <pre>
|
||||||
|
* ---------- 개정이력 ----------
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2026.09.01 김형식 최초생성
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class OlCommonHeaderDto {
|
||||||
|
private String custNm; // 고객명
|
||||||
|
private String custRrn; // 고객 주민등록번호
|
||||||
|
private String custNo; // 고객번호
|
||||||
|
private String rcevNo; // 접수번호
|
||||||
|
private String pono; // 증권번호
|
||||||
|
private String scrNm; // 화면명
|
||||||
|
private String scrId; // 화면ID
|
||||||
|
private String lginDttm; // 사용자가 로그인한 접속일시
|
||||||
|
private String lginIpAddr; // 사용자가 접속한 IP 주소
|
||||||
|
private String userNm; // 사용자 이름(한글)
|
||||||
|
private String userEngNm; // 사용자 영문이름
|
||||||
|
private String userId; // 사용자 ID(AD ID)
|
||||||
|
private String userNo; // 사용자번호
|
||||||
|
private String deptCd; // 사용자조직 코드
|
||||||
|
private String salsDvCd; // 영업본부코드
|
||||||
|
private String salsBoCd; // 영업지점코드
|
||||||
|
private String uppDeptCd; // 상위조직코드
|
||||||
|
private String prcsrUserId; // 처리자 ID(AD ID)
|
||||||
|
private String prcsrUserNo; // 처리지번호
|
||||||
|
private String prcsrDeptCd; // 처리지조직 코드
|
||||||
|
private String prcsrDvCd; // 처리지 영업본부코드
|
||||||
|
private String prcsrBoCd; // 처리지 영업지점코드
|
||||||
|
private String prcsrUppDeptCd; // 부서코드
|
||||||
|
private String sysCd; // 요청이 들어온 시스템을 표시
|
||||||
|
private String reqtSvcNm; // 요청하는 서비스 모듈명
|
||||||
|
private String reqtMthdNm; // 요청하는 메소드명
|
||||||
|
private String reqtVoNm; // 요청메소드에 전달할 값을 담는 VO명
|
||||||
|
private String scrButnFuncClssCd; // 화면에서 버튼 별 이벤트 구분을 위한 구분코드
|
||||||
|
private String scrGriCnt; // 화면 그리드 개수
|
||||||
|
private List<OlPageDto> pageList; // 페이징 리스트 (L2 반복)
|
||||||
|
private String reqtDttm; // 요청일시
|
||||||
|
private String crdtInfoIcluFlg; // 신용정보포함여부(Y,N)
|
||||||
|
private String crdtInfoDataChgTypCd; // 업무내역별 식별코드 부여
|
||||||
|
private String crdtInfoIdfInEngAbbrNm; // 신용정보식별영문약어명
|
||||||
|
private String crdtInfoIdfnSysCd; // 신용정보식별시스템코드
|
||||||
|
private String scrButnNm; // 화면버튼명
|
||||||
|
private String msgCnt; // 메시지 개수
|
||||||
|
private List<OlMsgDto> msgList; // 메시지 리스트 (L2 반복)
|
||||||
|
private String respDttm; // 응답일시
|
||||||
|
private String svcRunNm; // 거래별로 유일한 ServiceExecutionID
|
||||||
|
private String stdate; // 기준일자
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public static class OlPageDto {
|
||||||
|
private String pageSrno; // 페이지 인덱스값 (L3)
|
||||||
|
private String pageInqCnt; // 한페이지에 조회될 건수 (L3)
|
||||||
|
private String nxtButnNm; // 다음버튼ID (L3)
|
||||||
|
private String nxtButnEnbFlg; // 다음버튼 활성여부 (L3)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public static class OlMsgDto {
|
||||||
|
private String msgNo; // 서버 측에서 세팅한 정상/에러 메시지코드 (L3)
|
||||||
|
private String msgTypCd; // 메시지유형코드 (L3)
|
||||||
|
private String msgNm; // 메시지코드의 내용 (L3)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package io.shinhanlife.dap.common.integration.mci.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package io.shinhanlife.dap.common.integration.mci.dto
|
||||||
|
* @className ShinhanCommonHeaderDto
|
||||||
|
* @description AX HUB 시스템 처리 클래스
|
||||||
|
* @author 김형식
|
||||||
|
* @create 2026.09.01
|
||||||
|
* <pre>
|
||||||
|
* ---------- 개정이력 ----------
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2026.09.01 김형식 최초생성
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ShinhanCommonHeaderDto {
|
||||||
|
|
||||||
|
private String tgrmLencn; // 전문길이
|
||||||
|
private String glbId; // 글로벌ID (전사공통키)
|
||||||
|
private String pgrsSriaNo; // 진행일련번호
|
||||||
|
private String tgrmVrsnInfoValu; // 전문버전정보값
|
||||||
|
private String tgrmEncrYn; // 전문암호화여부
|
||||||
|
private String gpcpCd; // 그룹사코드
|
||||||
|
private String appliDutjCd; // 어플리케이션업무코드
|
||||||
|
private String appliDtptDutjCd; // 어플리케이션상세업무코드
|
||||||
|
private String frbuCd; // 대외기관코드
|
||||||
|
private String cmouDutjCd; // 대외업무코드
|
||||||
|
private String cmouCssfCd; // 대외종별코드
|
||||||
|
private String cmouTraCd; // 대외거래코드
|
||||||
|
private String rcvSvcId; // 수신서비스ID
|
||||||
|
private String rsltRcvSvcId; // 결과수신서비스ID
|
||||||
|
private String tgrmCreaChnnTypeCd; // 전문생성채널유형코드
|
||||||
|
|
||||||
|
private String lnggDvsnCd; // 언어구분코드
|
||||||
|
private String simulTraYn; // 시뮬레이션거래여부
|
||||||
|
private String itrIfId; // 인터페이스ID
|
||||||
|
private String reqRspnScCd; // 요청응답구분코드
|
||||||
|
private String tnsmTypeCd; // 전송유형코드
|
||||||
|
private String envrTypeCd; // 환경유형코드
|
||||||
|
private String inqrTraTypeCd; // 조회거래유형코드
|
||||||
|
private String reqTgrmTnsmDtptDt; // 요청전문전송상세일시
|
||||||
|
private String strYmd; // 기준일자
|
||||||
|
private String scrnId; // 화면ID
|
||||||
|
private String scrnBtnId; // 화면버튼ID
|
||||||
|
|
||||||
|
private String userIpAddr; // 사용자IP주소
|
||||||
|
private String drtmCd; // 부서코드
|
||||||
|
private String userId; // 사용자ID
|
||||||
|
private String indvCtinRoleCd; // 개인신용정보역할코드
|
||||||
|
private String acntOgnzNo; // 경리조직번호
|
||||||
|
private String rspnTgrmTnsmDtptDt; // 응답전문전송상세일시
|
||||||
|
private String tgrmDalRsltCd; // 전문처리결과코드
|
||||||
|
private String ognzAsrtCd; // 조직분류코드
|
||||||
|
private String ognzLeveCd; // 조직레벨코드
|
||||||
|
private String psmrAsrtCd; // 인사조직분류코드
|
||||||
|
private String sbsnRulpAsrtCd; // 영업규정분류코드
|
||||||
|
private String bsduCd; // 영업지국코드
|
||||||
|
private String bsquCd; // 영업자격코드
|
||||||
|
private String linkPrafDutyCd; // 연계인사직책코드
|
||||||
|
private String indvInfoLogWritYn; // 개인정보로그작성여부
|
||||||
|
private String prepImhdNm; // 예비항목명
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package io.shinhanlife.dap.common.integration.mci.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package io.shinhanlife.dap.common.integration.mci.dto
|
||||||
|
* @className ShinhanMessageDto
|
||||||
|
* @description AX HUB 시스템 처리 클래스 - MCI 전문 메시지부
|
||||||
|
* @author 김형식
|
||||||
|
* @create 2026.09.01
|
||||||
|
* <pre>
|
||||||
|
* ---------- 개정이력 ----------
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2026.09.01 김형식 최초생성
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ShinhanMessageDto {
|
||||||
|
|
||||||
|
private MsgHddvValu msgHddvValu; // 메시지헤더부값
|
||||||
|
private MsgDtdvValu msgDtdvValu; // 메시지데이터부값
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public static class MsgHddvValu {
|
||||||
|
private String msgTnsmTypeCd; // 메시지전송유형코드
|
||||||
|
private Integer msdvLencn; // 메시지부길이
|
||||||
|
private Integer msgRpttCc; // 메시지반복건수
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public static class MsgDtdvValu {
|
||||||
|
private String msgCd; // 메시지코드
|
||||||
|
private String msgPrnAttrCd; // 메시지출력속성코드
|
||||||
|
private String msgCt; // 메시지내용
|
||||||
|
private String anxMsgCt; // 부가메시지내용
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package io.shinhanlife.dap.common.integration.mci.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonUnwrapped;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package io.shinhanlife.dap.common.integration.mci.dto
|
||||||
|
* @className ShinhanTelegramWrapper
|
||||||
|
* @description AX HUB 시스템 처리 클래스 - MCI 전문 전체 래퍼 (공통헤더부 + 메시지부 + 데이터부)
|
||||||
|
* @author 김형식
|
||||||
|
* @create 2026.09.01
|
||||||
|
* <pre>
|
||||||
|
* ---------- 개정이력 ----------
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2026.09.01 김형식 최초생성
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ShinhanTelegramWrapper<T> {
|
||||||
|
|
||||||
|
// 1. 공통 헤더부
|
||||||
|
private ShinhanCommonHeaderDto tgrmCmnnhddValu;
|
||||||
|
|
||||||
|
// 2. 메시지부
|
||||||
|
private ShinhanMessageDto tgrmMsdvValu;
|
||||||
|
|
||||||
|
// 3. 데이터부 (비즈니스마다 다름, JsonUnwrapped로 평탄화하거나 객체 자체로 유지 가능. 여기서는 객체 유지)
|
||||||
|
private T tgrmDtdvValu;
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package io.shinhanlife.dap.common.integration.mci.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package io.shinhanlife.dap.common.integration.mci.dto
|
||||||
|
* @className SlCommonHeaderDto
|
||||||
|
* @description AX HUB 시스템 처리 클래스 - SL(신한라이프) 표준 헤더
|
||||||
|
* @author 김형식
|
||||||
|
* @create 2026.09.01
|
||||||
|
* <pre>
|
||||||
|
* ---------- 개정이력 ----------
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2026.09.01 김형식 최초생성
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class SlCommonHeaderDto {
|
||||||
|
private String length; // 전문길이
|
||||||
|
private SlGlobalId globalId; // 글로벌ID
|
||||||
|
private String headerVer; // 전문헤더버전
|
||||||
|
private String encodeFlag; // 전문암호화여부
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public static class SlGlobalId {
|
||||||
|
private String writeDate; // 전문작성일 (8)
|
||||||
|
private String sysCd; // 생성시스템명 (8)
|
||||||
|
private String typeCd; // 구분코드 (2)
|
||||||
|
private String detailCd; // 세부업무코드 (4)
|
||||||
|
private String seqNo; // 채번번호 (8)
|
||||||
|
private String step; // 진행상황 (2)
|
||||||
|
}
|
||||||
|
|
||||||
|
private String groupCoCd; // 그룹사코드
|
||||||
|
private String instCd; // 기관코드
|
||||||
|
private String applCd; // 업무코드
|
||||||
|
private String kindCd; // 종별코드
|
||||||
|
private String txCd; // 거래코드
|
||||||
|
private String pfmAppName; // 어플리케이션 명
|
||||||
|
private String pfmSvcName; // 서비스 명
|
||||||
|
private String pfmFnName; // 오퍼레이션 명
|
||||||
|
private String systemCd; // 생성시스템구분
|
||||||
|
private String trFlag; // 요청응답구분
|
||||||
|
private String syncFlag; // 동기구분
|
||||||
|
private String envrFlag; // 환경구분
|
||||||
|
private String crudFlag; // 조회거래구분
|
||||||
|
private String sendTime; // 전문전송일시
|
||||||
|
private String screenId; // 화면ID
|
||||||
|
private String clntIp; // Client IP
|
||||||
|
private String orgCd; // 부서(지점)코드
|
||||||
|
private String userId; // 사용자 사번(아이디)
|
||||||
|
private String indvCrdtInfo; // 개인신용정보역할코드
|
||||||
|
private String acntOgnzNo; // 경리조직번호
|
||||||
|
private String ttiFlag; // TimeOut사용
|
||||||
|
private String ttiStartTm; // 최초시작시간
|
||||||
|
private String ttiKeepTm; // 유지시간초수
|
||||||
|
private String outMsgTm; // 응답전문작성일시
|
||||||
|
private String resType; // 처리결과
|
||||||
|
private String resCode; // 응답코드
|
||||||
|
private String resBascMsg; // 응답기본내역
|
||||||
|
private String msgType; // 메시지 유형
|
||||||
|
private String rcvSvcCd; // 수신 서비스 Code
|
||||||
|
private String rsltRcvSvcCd; // 결과수신 서비스 Code
|
||||||
|
private String realSvcCd; // Real 서비스 Code
|
||||||
|
private String ognzAsrtCd; // 조직분류코드
|
||||||
|
private String ognzLeveCd; // 조직레벨구분코드
|
||||||
|
private String psmrAsrtCd; // 인사조직분류코드
|
||||||
|
private String sbsnRulpAsrtCd; // 영업규정분류코드
|
||||||
|
private String bsduCd; // 영업지국코드
|
||||||
|
private String bsquCd; // 영업자격코드
|
||||||
|
private String linkPrafDutyCd; // 직책코드
|
||||||
|
private String temp; // 예비 필드
|
||||||
|
}
|
||||||
@@ -106,8 +106,8 @@ public class PodScaffolder {
|
|||||||
*
|
*
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
@SpringBootApplication(scanBasePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.common.adapter", "io.shinhanlife.dap.common.mcp", "io.shinhanlife.dap.common.config"})
|
@SpringBootApplication(scanBasePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.common.adapter", "io.shinhanlife.dap.common.mcp", "io.shinhanlife.dap.common.config", "io.shinhanlife.dap.common.integration"})
|
||||||
@ConfigurationPropertiesScan(basePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.common.adapter", "io.shinhanlife.dap.common.mcp", "io.shinhanlife.dap.common.config"})
|
@ConfigurationPropertiesScan(basePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.common.adapter", "io.shinhanlife.dap.common.mcp", "io.shinhanlife.dap.common.config", "io.shinhanlife.dap.common.integration"})
|
||||||
@EnableCaching
|
@EnableCaching
|
||||||
public class DapTool%sApplication {
|
public class DapTool%sApplication {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
@@ -135,12 +135,12 @@ public class PodScaffolder {
|
|||||||
# Suppress Kafka Connection Logs
|
# Suppress Kafka Connection Logs
|
||||||
logging.level.org.apache.kafka=ERROR
|
logging.level.org.apache.kafka=ERROR
|
||||||
|
|
||||||
# Auto Prefix Namespace
|
# Auto Prefix Namespace (Disabled by default)
|
||||||
mcp.namespace=%s
|
mcp.namespace=
|
||||||
|
|
||||||
# API 보안 키 설정
|
# API 보안 키 설정
|
||||||
mcp.security.tenant-domains.TESTER-DEV=ALL
|
mcp.security.tenant-domains.TESTER-DEV=ALL
|
||||||
""".formatted(portStr, moduleName, shortName);
|
""".formatted(portStr, moduleName);
|
||||||
Files.writeString(resPath.resolve("application.properties"), applicationProperties);
|
Files.writeString(resPath.resolve("application.properties"), applicationProperties);
|
||||||
|
|
||||||
String applicationLocalProperties = """
|
String applicationLocalProperties = """
|
||||||
@@ -163,7 +163,7 @@ public class PodScaffolder {
|
|||||||
|
|
||||||
# Gateway/Tool URLs
|
# Gateway/Tool URLs
|
||||||
axhub.gateway.url=http://localhost:8081
|
axhub.gateway.url=http://localhost:8081
|
||||||
axhub.tool.url=http://localhost:${server.port}
|
axhub.tool.url=${AXHUB_TOOL_URL:http://localhost:${server.port}}
|
||||||
|
|
||||||
# Disable Kafka
|
# Disable Kafka
|
||||||
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration
|
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration
|
||||||
@@ -0,0 +1,448 @@
|
|||||||
|
package io.shinhanlife.dap.common.util;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MCP Tool 코드를 자동 생성(Scaffolding)하는 유틸리티 클래스
|
||||||
|
*
|
||||||
|
* [실행 방법]
|
||||||
|
* 방법 1. IDE(IntelliJ 등)에서 직접 실행 (대화형 모드 추천 ⭐)
|
||||||
|
* - 이 클래스(ToolScaffolder.java)를 열고 main 메서드를 직접 실행(Run)합니다.
|
||||||
|
* - 콘솔 창에 뜨는 질문에 차례대로 값을 입력하기만 하면 파일이 생성됩니다.
|
||||||
|
*
|
||||||
|
* 방법 2. 커맨드라인(터미널)에서 실행 (명령어 기반)
|
||||||
|
* - 컴파일: javac -encoding UTF-8 dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/util/ToolScaffolder.java
|
||||||
|
* - 실행: java -cp dap-tool-core/src/main/java io.shinhanlife.dap.mcc.util.ToolScaffolder [이름] [ID] "[설명]" "[그룹]" "[통신방식]" "[모듈명]"
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @package io.shinhanlife.dap.mcc.util
|
||||||
|
* @className ToolScaffolder
|
||||||
|
* @description AX HUB 시스템 처리 클래스
|
||||||
|
* @author 김형식
|
||||||
|
* @create 2026.09.01
|
||||||
|
* <pre>
|
||||||
|
* ---------- 개정이력 ----------
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2026.09.01 김형식 최초생성
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public class ToolScaffolder {
|
||||||
|
|
||||||
|
private static final String BASE_PACKAGE = "io.shinhanlife.dap.mcc";
|
||||||
|
private static final String BASE_PACKAGE_PATH = "src/main/java/io/shinhanlife/dap/mcc";
|
||||||
|
|
||||||
|
public static void main(String[] args) throws IOException {
|
||||||
|
Scanner scanner = new Scanner(System.in);
|
||||||
|
|
||||||
|
System.out.println("=========================================");
|
||||||
|
System.out.println(" MCP Tool Scaffolder (Java CLI) ");
|
||||||
|
System.out.println("=========================================\n");
|
||||||
|
|
||||||
|
String baseName = getOrAsk(args, 0, scanner, "1. 생성할 Tool의 기본 이름 (예: ExchangeRate) [영문 PascalCase]: ");
|
||||||
|
String interfaceId = getOrAsk(args, 1, scanner, "2. 레거시 API 인터페이스 ID (예: EXCH_001): ");
|
||||||
|
String description = getOrAsk(args, 2, scanner, "3. Tool 기능 설명 (예: 환율 조회): ");
|
||||||
|
String group = getOrAsk(args, 3, scanner, "4. Tool 소속 그룹 (예: SAMPLE, NOTIFICATION, CLAIM, POLICY, HR, CONTRACT, CUSTOMER 등): ");
|
||||||
|
if (group.isEmpty()) group = "COMMON";
|
||||||
|
String routingType = getOrAsk(args, 4, scanner, "5. 통신 프로토콜 (예: HTTP, TCP, MCI, EAI): ");
|
||||||
|
if (routingType.trim().isEmpty()) {
|
||||||
|
routingType = "HTTP";
|
||||||
|
}
|
||||||
|
String moduleName = getOrAsk(args, 5, scanner, "6. 코드를 생성할 모듈 (기본: dap-tool-other): ");
|
||||||
|
if (moduleName.trim().isEmpty()) {
|
||||||
|
moduleName = "dap-tool-other";
|
||||||
|
}
|
||||||
|
|
||||||
|
String defaultAuthor = System.getProperty("user.name");
|
||||||
|
String defaultDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy.MM.dd"));
|
||||||
|
|
||||||
|
String author = getOrAsk(args, 6, scanner, "7. 작성자 (엔터 입력 시 '" + defaultAuthor + "'): ");
|
||||||
|
if (author.trim().isEmpty()) author = defaultAuthor;
|
||||||
|
String createDate = getOrAsk(args, 7, scanner, "8. 작성일 (엔터 입력 시 '" + defaultDate + "'): ");
|
||||||
|
if (createDate.trim().isEmpty()) createDate = defaultDate;
|
||||||
|
|
||||||
|
String result = scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, createDate, true);
|
||||||
|
System.out.println(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getOrAsk(String[] args, int index, Scanner scanner, String prompt) {
|
||||||
|
if (args.length > index) {
|
||||||
|
return args[index];
|
||||||
|
}
|
||||||
|
System.out.print(prompt);
|
||||||
|
return scanner.nextLine().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String scaffold(String baseName, String interfaceId, String description, String group, String routingType, String moduleName, String author, String createDate, boolean register) throws IOException {
|
||||||
|
baseName = toPascalCase(baseName);
|
||||||
|
String envSourceDir = System.getenv("AXHUB_SOURCE_DIR");
|
||||||
|
Path rootDir = envSourceDir != null ? Paths.get(envSourceDir) : Paths.get(".");
|
||||||
|
|
||||||
|
Path serviceDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "service"));
|
||||||
|
Path dtoDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "dto"));
|
||||||
|
|
||||||
|
Path legacyDtoDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "legacy"));
|
||||||
|
Path converterDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "converter"));
|
||||||
|
|
||||||
|
Files.createDirectories(serviceDir);
|
||||||
|
Files.createDirectories(dtoDir);
|
||||||
|
Files.createDirectories(legacyDtoDir);
|
||||||
|
Files.createDirectories(converterDir);
|
||||||
|
|
||||||
|
StringBuilder log = new StringBuilder();
|
||||||
|
|
||||||
|
// Generate Req DTO
|
||||||
|
String reqContent = """
|
||||||
|
package %s.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package %s.dto
|
||||||
|
* @className %sReq
|
||||||
|
* @description AX HUB 시스템 처리 클래스
|
||||||
|
* @author %s
|
||||||
|
* @create %s
|
||||||
|
* <pre>
|
||||||
|
* ---------- 개정이력 ----------
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* %s %s 최초생성
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public class %sReq {
|
||||||
|
// TODO: Add request fields here
|
||||||
|
}
|
||||||
|
""".formatted(BASE_PACKAGE, BASE_PACKAGE, baseName, author, createDate, createDate, author, baseName);
|
||||||
|
Files.writeString(dtoDir.resolve(baseName + "Req.java"), reqContent);
|
||||||
|
|
||||||
|
// Generate Res DTO
|
||||||
|
String resContent = """
|
||||||
|
package %s.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package %s.dto
|
||||||
|
* @className %sRes
|
||||||
|
* @description AX HUB 시스템 처리 클래스
|
||||||
|
* @author %s
|
||||||
|
* @create %s
|
||||||
|
* <pre>
|
||||||
|
* ---------- 개정이력 ----------
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* %s %s 최초생성
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public class %sRes {
|
||||||
|
private String status;
|
||||||
|
private String message;
|
||||||
|
// TODO: Add response fields here
|
||||||
|
}
|
||||||
|
""".formatted(BASE_PACKAGE, BASE_PACKAGE, baseName, author, createDate, createDate, author, baseName);
|
||||||
|
Files.writeString(dtoDir.resolve(baseName + "Res.java"), resContent);
|
||||||
|
|
||||||
|
String toolName = baseName.isEmpty() ? baseName : Character.toLowerCase(baseName.charAt(0)) + baseName.substring(1);
|
||||||
|
|
||||||
|
boolean isMci = "MCI".equalsIgnoreCase(routingType);
|
||||||
|
String serviceContent;
|
||||||
|
|
||||||
|
if (isMci) {
|
||||||
|
serviceContent = """
|
||||||
|
package %s.service;
|
||||||
|
|
||||||
|
import %s.annotation.McpFunction;
|
||||||
|
import %s.annotation.McpTool;
|
||||||
|
import %s.dto.%sReq;
|
||||||
|
import %s.dto.%sRes;
|
||||||
|
import io.shinhanlife.dap.common.integration.mci.component.AxhubMciComponent;
|
||||||
|
import io.shinhanlife.glow.communication.dto.Transfer;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package %s.service
|
||||||
|
* @className %sService
|
||||||
|
* @description AX HUB 시스템 처리 클래스
|
||||||
|
* @author %s
|
||||||
|
* @create %s
|
||||||
|
* <pre>
|
||||||
|
* ---------- 개정이력 ----------
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* %s %s 최초생성
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@McpTool(
|
||||||
|
routingType = "%s",
|
||||||
|
categoryKey = "%s"
|
||||||
|
)
|
||||||
|
public class %sService {
|
||||||
|
|
||||||
|
private final AxhubMciComponent mci;
|
||||||
|
|
||||||
|
@McpFunction(
|
||||||
|
displayName = "%s 툴",
|
||||||
|
name = "%s",
|
||||||
|
description = "%s",
|
||||||
|
prompt = "%s",
|
||||||
|
mappingId = "%s",
|
||||||
|
register = %s,
|
||||||
|
requiresApproval = false,
|
||||||
|
openWorldHint = true
|
||||||
|
)
|
||||||
|
public Object execute(%sReq req) {
|
||||||
|
log.info("[MCI Tool] {} 요청 수신.", "%s");
|
||||||
|
try {
|
||||||
|
Transfer<Object> resTransfer = mci.callTo(
|
||||||
|
"%s",
|
||||||
|
null,
|
||||||
|
req,
|
||||||
|
Object.class
|
||||||
|
);
|
||||||
|
return resTransfer.getBody() != null ? resTransfer.getBody() : "{\"status\":\"SUCCESS\"}";
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[MCI Tool] 연동 중 오류 발생: {}", e.getMessage(), e);
|
||||||
|
return "{\"status\":\"ERROR\", \"message\":\"" + e.getMessage() + "\"}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".formatted(
|
||||||
|
BASE_PACKAGE,
|
||||||
|
BASE_PACKAGE,
|
||||||
|
BASE_PACKAGE,
|
||||||
|
BASE_PACKAGE, baseName,
|
||||||
|
BASE_PACKAGE, baseName,
|
||||||
|
BASE_PACKAGE, baseName,
|
||||||
|
author,
|
||||||
|
createDate,
|
||||||
|
createDate, author,
|
||||||
|
routingType, group.toLowerCase(),
|
||||||
|
baseName,
|
||||||
|
baseName, toolName, description, description + " 해줘.", interfaceId, register,
|
||||||
|
baseName,
|
||||||
|
baseName, interfaceId
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
serviceContent = """
|
||||||
|
package %s.service;
|
||||||
|
|
||||||
|
import %s.annotation.McpFunction;
|
||||||
|
import %s.annotation.McpTool;
|
||||||
|
import %s.dto.%sReq;
|
||||||
|
import %s.dto.%sRes;
|
||||||
|
import %s.converter.%sLegacyConverter;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package %s.service
|
||||||
|
* @className %sService
|
||||||
|
* @description AX HUB 시스템 처리 클래스
|
||||||
|
* @author %s
|
||||||
|
* @create %s
|
||||||
|
* <pre>
|
||||||
|
* ---------- 개정이력 ----------
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* %s %s 최초생성
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@McpTool(
|
||||||
|
routingType = "%s",
|
||||||
|
categoryKey = "%s"
|
||||||
|
)
|
||||||
|
public class %sService extends AbstractMcpToolService {
|
||||||
|
|
||||||
|
private final %sLegacyConverter converter = Mappers.getMapper(%sLegacyConverter.class);
|
||||||
|
|
||||||
|
@McpFunction(
|
||||||
|
displayName = "%s 툴",
|
||||||
|
name = "%s",
|
||||||
|
description = "%s",
|
||||||
|
prompt = "%s",
|
||||||
|
mappingId = "%s",
|
||||||
|
register = %s,
|
||||||
|
requiresApproval = false
|
||||||
|
)
|
||||||
|
public Object execute(%sReq req) {
|
||||||
|
// %sLegacyReq legacyReq = converter.toLegacyReq(req);
|
||||||
|
return executeLegacy("%s", "%s", req); // Or pass legacyReq
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".formatted(
|
||||||
|
BASE_PACKAGE,
|
||||||
|
BASE_PACKAGE,
|
||||||
|
BASE_PACKAGE,
|
||||||
|
BASE_PACKAGE, baseName,
|
||||||
|
BASE_PACKAGE, baseName,
|
||||||
|
BASE_PACKAGE, baseName,
|
||||||
|
BASE_PACKAGE, baseName,
|
||||||
|
author,
|
||||||
|
createDate,
|
||||||
|
createDate, author,
|
||||||
|
routingType, group.toLowerCase(),
|
||||||
|
baseName,
|
||||||
|
baseName, toolName, description, description + " 해줘.", interfaceId, register,
|
||||||
|
baseName,
|
||||||
|
baseName,
|
||||||
|
routingType, interfaceId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Files.writeString(serviceDir.resolve(baseName + "Service.java"), serviceContent);
|
||||||
|
|
||||||
|
String legacyReqContent = """
|
||||||
|
package %s.legacy;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package %s.legacy
|
||||||
|
* @className %sLegacyReq
|
||||||
|
* @description AX HUB 시스템 처리 클래스
|
||||||
|
* @author %s
|
||||||
|
* @create %s
|
||||||
|
* <pre>
|
||||||
|
* ---------- 개정이력 ----------
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* %s %s 최초생성
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class %sLegacyReq {
|
||||||
|
// TODO: Add legacy request fields here
|
||||||
|
}
|
||||||
|
""".formatted(BASE_PACKAGE, BASE_PACKAGE, baseName, author, createDate, createDate, author, baseName);
|
||||||
|
Files.writeString(legacyDtoDir.resolve(baseName + "LegacyReq.java"), legacyReqContent);
|
||||||
|
|
||||||
|
String legacyResContent = """
|
||||||
|
package %s.legacy;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package %s.legacy
|
||||||
|
* @className %sLegacyRes
|
||||||
|
* @description AX HUB 시스템 처리 클래스
|
||||||
|
* @author %s
|
||||||
|
* @create %s
|
||||||
|
* <pre>
|
||||||
|
* ---------- 개정이력 ----------
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* %s %s 최초생성
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class %sLegacyRes {
|
||||||
|
// TODO: Add legacy response fields here
|
||||||
|
}
|
||||||
|
""".formatted(BASE_PACKAGE, BASE_PACKAGE, baseName, author, createDate, createDate, author, baseName);
|
||||||
|
Files.writeString(legacyDtoDir.resolve(baseName + "LegacyRes.java"), legacyResContent);
|
||||||
|
|
||||||
|
String converterContent = """
|
||||||
|
package %s.converter;
|
||||||
|
|
||||||
|
import %s.dto.%sReq;
|
||||||
|
import %s.dto.%sRes;
|
||||||
|
import %s.legacy.%sLegacyReq;
|
||||||
|
import %s.legacy.%sLegacyRes;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
import org.mapstruct.Mapping;
|
||||||
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package %s.converter
|
||||||
|
* @className %sLegacyConverter
|
||||||
|
* @description AX HUB 시스템 처리 클래스
|
||||||
|
* @author %s
|
||||||
|
* @create %s
|
||||||
|
* <pre>
|
||||||
|
* ---------- 개정이력 ----------
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* %s %s 최초생성
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Mapper(componentModel = "spring")
|
||||||
|
public interface %sLegacyConverter {
|
||||||
|
|
||||||
|
%sLegacyReq toLegacyReq(%sReq req);
|
||||||
|
|
||||||
|
%sRes toRes(%sLegacyRes legacyRes);
|
||||||
|
}
|
||||||
|
""".formatted(
|
||||||
|
BASE_PACKAGE,
|
||||||
|
BASE_PACKAGE, baseName,
|
||||||
|
BASE_PACKAGE, baseName,
|
||||||
|
BASE_PACKAGE, baseName,
|
||||||
|
BASE_PACKAGE, baseName,
|
||||||
|
BASE_PACKAGE, baseName, author, createDate, createDate, author,
|
||||||
|
baseName, baseName, baseName, baseName, baseName, baseName
|
||||||
|
);
|
||||||
|
Files.writeString(converterDir.resolve(baseName + "LegacyConverter.java"), converterContent);
|
||||||
|
|
||||||
|
log.append("\n=========================================\n");
|
||||||
|
log.append(" Scaffolding Complete! (Routing: " + routingType + ")\n");
|
||||||
|
log.append("=========================================\n");
|
||||||
|
log.append("[Service] ").append(serviceDir.resolve(baseName + "Service.java")).append("\n");
|
||||||
|
log.append("[Req DTO] ").append(dtoDir.resolve(baseName + "Req.java")).append("\n");
|
||||||
|
log.append("[Res DTO] ").append(dtoDir.resolve(baseName + "Res.java")).append("\n");
|
||||||
|
log.append("[Legacy Req DTO] ").append(legacyDtoDir.resolve(baseName + "LegacyReq.java")).append("\n");
|
||||||
|
log.append("[Legacy Res DTO] ").append(legacyDtoDir.resolve(baseName + "LegacyRes.java")).append("\n");
|
||||||
|
log.append("[Legacy Converter] ").append(converterDir.resolve(baseName + "LegacyConverter.java")).append("\n");
|
||||||
|
log.append("\n Tip: ").append(interfaceId).append(" 목업 데이터를 mock-responses.json에 추가하세요.\n");
|
||||||
|
|
||||||
|
return log.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String toPascalCase(String str) {
|
||||||
|
if (str == null || str.isEmpty()) {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
boolean capitalizeNext = true;
|
||||||
|
for (char c : str.toCharArray()) {
|
||||||
|
if (c == '_' || c == '-' || c == ' ') {
|
||||||
|
capitalizeNext = true;
|
||||||
|
} else if (capitalizeNext) {
|
||||||
|
result.append(Character.toUpperCase(c));
|
||||||
|
capitalizeNext = false;
|
||||||
|
} else {
|
||||||
|
result.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result.length() > 0) {
|
||||||
|
result.setCharAt(0, Character.toUpperCase(result.charAt(0)));
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,7 +31,7 @@ public @interface McpFunction {
|
|||||||
String inputSchema() default "{}";
|
String inputSchema() default "{}";
|
||||||
|
|
||||||
// 추가: Redis 자동 등록 및 Heartbeat 대상 여부 제어
|
// 추가: Redis 자동 등록 및 Heartbeat 대상 여부 제어
|
||||||
boolean register() default true;
|
boolean register() default false;
|
||||||
|
|
||||||
// 추가: 툴 목록 노출 여부 제어 (false 시 라우팅은 되나 목록에서 숨김)
|
// 추가: 툴 목록 노출 여부 제어 (false 시 라우팅은 되나 목록에서 숨김)
|
||||||
boolean visible() default true;
|
boolean visible() default true;
|
||||||
|
|||||||
@@ -65,26 +65,26 @@ public class BusinessToolController {
|
|||||||
return toolRegistryHeartbeatSender.getAllScannedTools();
|
return toolRegistryHeartbeatSender.getAllScannedTools();
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSON RPC 기반 단일 라우팅 엔드포인트
|
// 순수 REST 기반 동적 라우팅 엔드포인트
|
||||||
@PostMapping("/mcp/api/v1/tools/call")
|
@PostMapping("/mcp/{name}")
|
||||||
public ResponseEntity<Map<String, Object>> executeDynamicTool(
|
public ResponseEntity<?> executeDynamicTool(
|
||||||
|
@PathVariable("name") String functionName,
|
||||||
@RequestHeader(value = "X-Request-Id", required = false) String headerRequestId,
|
@RequestHeader(value = "X-Request-Id", required = false) String headerRequestId,
|
||||||
@RequestBody(required = false) Map<String, Object> payload) {
|
@RequestHeader(value = "trace-id", required = false) String traceId,
|
||||||
|
@RequestHeader(value = "request-id", required = false) String requestId,
|
||||||
|
@RequestBody(required = false) Map<String, Object> arguments) {
|
||||||
|
|
||||||
String finalRequestId = headerRequestId != null ? headerRequestId : (payload != null && payload.get("id") != null ? String.valueOf(payload.get("id")) : null);
|
String finalRequestId = headerRequestId;
|
||||||
Map<String, Object> params = payload != null ? (Map<String, Object>) payload.get("params") : null;
|
|
||||||
String functionName = params != null ? (String) params.get("name") : null;
|
|
||||||
|
|
||||||
log.info("\n [Tool] 동적 툴 실행 요청 수신 (함수명): {}", functionName);
|
log.info(" [Tool] IN - trace-id: {}, request-id: {}", traceId, requestId);
|
||||||
if (payload != null) {
|
log.info(" [Tool] 동적 툴 실행 요청 수신 (함수명): {}", functionName);
|
||||||
|
if (arguments != null) {
|
||||||
try {
|
try {
|
||||||
log.info(" [Tool] 호출 파라미터: {}", objectMapper.writeValueAsString(payload));
|
log.info(" [Tool] 호출 파라미터: {}", objectMapper.writeValueAsString(arguments));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.info(" [Tool] 호출 파라미터: {}", payload);
|
log.info(" [Tool] 호출 파라미터: {}", arguments);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, Object> arguments = params != null ? (Map<String, Object>) params.get("arguments") : null;
|
|
||||||
Object targetBean = null;
|
Object targetBean = null;
|
||||||
Method targetMethod = null;
|
Method targetMethod = null;
|
||||||
McpFunction targetFunctionAnnotation = null;
|
McpFunction targetFunctionAnnotation = null;
|
||||||
@@ -135,11 +135,7 @@ public class BusinessToolController {
|
|||||||
errorBody.put("details", errorDetails);
|
errorBody.put("details", errorDetails);
|
||||||
if (finalRequestId != null) errorBody.put("request_id", finalRequestId);
|
if (finalRequestId != null) errorBody.put("request_id", finalRequestId);
|
||||||
|
|
||||||
Map<String, Object> error = new HashMap<>();
|
return ResponseEntity.status(404).body(errorBody);
|
||||||
error.put("jsonrpc", "2.0");
|
|
||||||
error.put("error", errorBody);
|
|
||||||
error.put("id", payload != null ? payload.get("id") : null);
|
|
||||||
return ResponseEntity.status(404).body(error);
|
|
||||||
}
|
}
|
||||||
// (기존 차단 로직 제거됨)
|
// (기존 차단 로직 제거됨)
|
||||||
|
|
||||||
@@ -148,8 +144,8 @@ public class BusinessToolController {
|
|||||||
Class<?> paramType = targetMethod.getParameterTypes()[0];
|
Class<?> paramType = targetMethod.getParameterTypes()[0];
|
||||||
if (!Map.class.isAssignableFrom(paramType)) {
|
if (!Map.class.isAssignableFrom(paramType)) {
|
||||||
try {
|
try {
|
||||||
Map<String, Object> autoSchema = JsonSchemaGenerator.generatePropertiesSchema(paramType);
|
Map<String, Object> autoSchema = JsonSchemaGenerator.generateSchema(paramType);
|
||||||
String fullSchemaJson = "{\"type\":\"object\", \"properties\":" + objectMapper.writeValueAsString(autoSchema) + "}";
|
String fullSchemaJson = objectMapper.writeValueAsString(autoSchema);
|
||||||
|
|
||||||
JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
|
JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
|
||||||
JsonSchema schema = factory.getSchema(fullSchemaJson);
|
JsonSchema schema = factory.getSchema(fullSchemaJson);
|
||||||
@@ -169,11 +165,7 @@ public class BusinessToolController {
|
|||||||
errorBody.put("details", errorDetails);
|
errorBody.put("details", errorDetails);
|
||||||
if (finalRequestId != null) errorBody.put("request_id", finalRequestId);
|
if (finalRequestId != null) errorBody.put("request_id", finalRequestId);
|
||||||
|
|
||||||
Map<String, Object> error = new HashMap<>();
|
return ResponseEntity.status(422).body(errorBody);
|
||||||
error.put("jsonrpc", "2.0");
|
|
||||||
error.put("error", errorBody);
|
|
||||||
error.put("id", payload != null ? payload.get("id") : null);
|
|
||||||
return ResponseEntity.status(422).body(error);
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("[Tool] 스키마 검증 중 오류 발생: {}", e.getMessage());
|
log.error("[Tool] 스키마 검증 중 오류 발생: {}", e.getMessage());
|
||||||
@@ -202,60 +194,20 @@ public class BusinessToolController {
|
|||||||
|
|
||||||
long elapsed = System.currentTimeMillis() - startTime;
|
long elapsed = System.currentTimeMillis() - startTime;
|
||||||
|
|
||||||
// 5. 결과 조립 (JSON-RPC 응답 - Agent Builder 규격 적용)
|
// 5. 결과 반환 (순수 REST 응답)
|
||||||
Map<String, Object> innerResult = new HashMap<>();
|
|
||||||
if (methodResult instanceof Map && ((Map<?, ?>) methodResult).containsKey("contracts")) {
|
|
||||||
innerResult.putAll((Map<String, Object>) methodResult);
|
|
||||||
} else {
|
|
||||||
List<Object> contracts = new ArrayList<>();
|
|
||||||
if (methodResult != null) {
|
|
||||||
contracts.add(methodResult);
|
|
||||||
}
|
|
||||||
innerResult.put("contracts", contracts);
|
|
||||||
|
|
||||||
if (methodResult instanceof Map && ((Map<?, ?>) methodResult).containsKey("status")) {
|
|
||||||
innerResult.put("status", ((Map<?, ?>) methodResult).get("status"));
|
|
||||||
} else {
|
|
||||||
innerResult.put("status", "SUCCESS");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, Object> resultPayload = new HashMap<>();
|
|
||||||
resultPayload.put("status", "ok");
|
|
||||||
resultPayload.put("result", innerResult);
|
|
||||||
resultPayload.put("error_code", null);
|
|
||||||
resultPayload.put("error_message", null);
|
|
||||||
resultPayload.put("elapsed_ms", elapsed);
|
|
||||||
resultPayload.put("truncated", false);
|
|
||||||
int originalSize = 0;
|
|
||||||
try {
|
try {
|
||||||
if (methodResult instanceof Map && ((Map<?, ?>) methodResult).containsKey("legacy_response")) {
|
log.info("[Tool -> MCP Gateway] 동적 툴 실행 결과 반환: {}", objectMapper.writeValueAsString(methodResult));
|
||||||
Object legacyResp = ((Map<?, ?>) methodResult).get("legacy_response");
|
|
||||||
if (legacyResp instanceof String) {
|
|
||||||
originalSize = ((String) legacyResp).getBytes(java.nio.charset.StandardCharsets.UTF_8).length;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
String jsonStr = objectMapper.writeValueAsString(innerResult);
|
|
||||||
originalSize = jsonStr.getBytes(java.nio.charset.StandardCharsets.UTF_8).length;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("Failed to calculate original_size", e);
|
log.info("[Tool -> MCP Gateway] 동적 툴 실행 결과 반환: {}", methodResult);
|
||||||
}
|
|
||||||
resultPayload.put("original_size", originalSize);
|
|
||||||
|
|
||||||
Map<String, Object> rpcResponse = new LinkedHashMap<>(); // 순서 보장을 위해 LinkedHashMap 사용
|
|
||||||
rpcResponse.put("jsonrpc", "2.0");
|
|
||||||
rpcResponse.put("result", resultPayload);
|
|
||||||
rpcResponse.put("id", payload != null ? payload.get("id") : null);
|
|
||||||
|
|
||||||
try {
|
|
||||||
log.info("[Tool -> MCP Gateway] 동적 툴 실행 결과 반환: {}", objectMapper.writeValueAsString(rpcResponse));
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.info("[Tool -> MCP Gateway] 동적 툴 실행 결과 반환: {}", rpcResponse);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.info(" [Tool] OUT - trace-id: {}, request-id: {}", traceId, requestId);
|
||||||
|
|
||||||
return ResponseEntity.ok(rpcResponse);
|
ResponseEntity.BodyBuilder responseBuilder = ResponseEntity.ok();
|
||||||
|
if (traceId != null) responseBuilder.header("trace-id", traceId);
|
||||||
|
if (requestId != null) responseBuilder.header("request-id", requestId);
|
||||||
|
|
||||||
|
return responseBuilder.body(methodResult);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("[Tool] 리플렉션 실행 중 예외 발생: {}", e.getMessage());
|
log.error("[Tool] 리플렉션 실행 중 예외 발생: {}", e.getMessage());
|
||||||
@@ -268,11 +220,7 @@ public class BusinessToolController {
|
|||||||
errorBody.put("details", errorDetails);
|
errorBody.put("details", errorDetails);
|
||||||
if (finalRequestId != null) errorBody.put("request_id", finalRequestId);
|
if (finalRequestId != null) errorBody.put("request_id", finalRequestId);
|
||||||
|
|
||||||
Map<String, Object> error = new HashMap<>();
|
return ResponseEntity.status(502).body(errorBody);
|
||||||
error.put("jsonrpc", "2.0");
|
|
||||||
error.put("error", errorBody);
|
|
||||||
error.put("id", payload != null ? payload.get("id") : null);
|
|
||||||
return ResponseEntity.status(502).body(error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,10 +120,7 @@ public class ToolRegistryHeartbeatSender {
|
|||||||
if (method.getParameterCount() > 0) {
|
if (method.getParameterCount() > 0) {
|
||||||
try {
|
try {
|
||||||
Class<?> paramType = method.getParameterTypes()[0];
|
Class<?> paramType = method.getParameterTypes()[0];
|
||||||
Map<String, Object> schema = JsonSchemaGenerator.generatePropertiesSchema(paramType);
|
Map<String, Object> finalSchema = JsonSchemaGenerator.generateSchema(paramType);
|
||||||
Map<String, Object> finalSchema = new HashMap<>();
|
|
||||||
finalSchema.put("type", "object");
|
|
||||||
finalSchema.put("properties", schema);
|
|
||||||
meta.setParametersSchema(finalSchema);
|
meta.setParametersSchema(finalSchema);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Failed to generate schema for {}", subToolName, e);
|
log.error("Failed to generate schema for {}", subToolName, e);
|
||||||
|
|||||||
@@ -1,62 +1,68 @@
|
|||||||
package io.shinhanlife.dap.mcc.util;
|
package io.shinhanlife.dap.mcc.util;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
/**
|
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
|
||||||
* @package io.shinhanlife.dap.mcc.util
|
import io.shinhanlife.dap.mcc.annotation.McpParameter;
|
||||||
* @className JsonSchemaGenerator
|
import java.lang.reflect.Field;
|
||||||
* @description AX HUB 시스템 처리 클래스
|
import java.util.ArrayList;
|
||||||
* @author 김형식
|
import java.util.HashMap;
|
||||||
* @create 2026.09.01
|
import java.util.List;
|
||||||
* <pre>
|
import java.util.Map;
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
public class JsonSchemaGenerator {
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 김형식 최초생성
|
/**
|
||||||
*
|
* Java DTO 클래스를 분석하여 MCP 규격의 완전한 JSON Schema를 생성합니다.
|
||||||
* </pre>
|
*/
|
||||||
*/
|
public static Map<String, Object> generateSchema(Class<?> clazz) {
|
||||||
import io.shinhanlife.dap.mcc.annotation.McpParameter;
|
Map<String, Object> schema = new HashMap<>();
|
||||||
import java.lang.reflect.Field;
|
schema.put("type", "object");
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
Map<String, Object> properties = new HashMap<>();
|
||||||
import java.util.Map;
|
List<String> requiredList = new ArrayList<>();
|
||||||
|
|
||||||
public class JsonSchemaGenerator {
|
for (Field field : clazz.getDeclaredFields()) {
|
||||||
|
Map<String, Object> fieldSchema = new HashMap<>();
|
||||||
/**
|
|
||||||
* Java DTO 클래스를 분석하여 MCP 규격의 JSON Schema (Properties)를 생성합니다.
|
// 1. 타입 매핑
|
||||||
*/
|
fieldSchema.put("type", mapJavaTypeToJsonType(field.getType()));
|
||||||
public static Map<String, Object> generatePropertiesSchema(Class<?> clazz) {
|
|
||||||
Map<String, Object> properties = new HashMap<>();
|
// 2. 어노테이션 기반 설명 추출
|
||||||
|
McpParameter paramAnnotation = field.getAnnotation(McpParameter.class);
|
||||||
for (Field field : clazz.getDeclaredFields()) {
|
JsonPropertyDescription descAnnotation = field.getAnnotation(JsonPropertyDescription.class);
|
||||||
Map<String, Object> fieldSchema = new HashMap<>();
|
if (paramAnnotation != null && !paramAnnotation.description().isEmpty()) {
|
||||||
|
fieldSchema.put("description", paramAnnotation.description());
|
||||||
// 1. 타입 매핑
|
} else if (descAnnotation != null && !descAnnotation.value().isEmpty()) {
|
||||||
fieldSchema.put("type", mapJavaTypeToJsonType(field.getType()));
|
fieldSchema.put("description", descAnnotation.value());
|
||||||
|
} else {
|
||||||
// 2. 어노테이션 기반 설명 추출
|
fieldSchema.put("description", field.getName()); // 기본값
|
||||||
McpParameter paramAnnotation = field.getAnnotation(McpParameter.class);
|
}
|
||||||
if (paramAnnotation != null && !paramAnnotation.description().isEmpty()) {
|
|
||||||
fieldSchema.put("description", paramAnnotation.description());
|
// 3. 필수 여부 판단
|
||||||
} else {
|
JsonProperty jsonProp = field.getAnnotation(JsonProperty.class);
|
||||||
fieldSchema.put("description", field.getName()); // 기본값
|
if ((jsonProp != null && jsonProp.required()) || (paramAnnotation != null && paramAnnotation.required())) {
|
||||||
}
|
requiredList.add(field.getName());
|
||||||
|
}
|
||||||
properties.put(field.getName(), fieldSchema);
|
|
||||||
}
|
properties.put(field.getName(), fieldSchema);
|
||||||
|
}
|
||||||
return properties;
|
|
||||||
}
|
schema.put("properties", properties);
|
||||||
|
if (!requiredList.isEmpty()) {
|
||||||
private static String mapJavaTypeToJsonType(Class<?> clazz) {
|
schema.put("required", requiredList);
|
||||||
if (clazz == String.class) return "string";
|
}
|
||||||
if (clazz == Integer.class || clazz == int.class) return "integer";
|
|
||||||
if (clazz == Long.class || clazz == long.class) return "integer";
|
return schema;
|
||||||
if (clazz == Double.class || clazz == double.class) return "number";
|
}
|
||||||
if (clazz == Float.class || clazz == float.class) return "number";
|
|
||||||
if (clazz == Boolean.class || clazz == boolean.class) return "boolean";
|
private static String mapJavaTypeToJsonType(Class<?> clazz) {
|
||||||
if (List.class.isAssignableFrom(clazz)) return "array";
|
if (clazz == String.class) return "string";
|
||||||
return "object";
|
if (clazz == Integer.class || clazz == int.class) return "integer";
|
||||||
}
|
if (clazz == Long.class || clazz == long.class) return "integer";
|
||||||
}
|
if (clazz == Double.class || clazz == double.class) return "number";
|
||||||
|
if (clazz == Float.class || clazz == float.class) return "number";
|
||||||
|
if (clazz == Boolean.class || clazz == boolean.class) return "boolean";
|
||||||
|
if (List.class.isAssignableFrom(clazz)) return "array";
|
||||||
|
return "object";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package io.shinhanlife.glow.communication.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: 실제 Glow Framework 의존성(JAR)이 추가되면 이 Mock 클래스를 삭제하세요.
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class Transfer<T> {
|
||||||
|
private Object header;
|
||||||
|
private T body;
|
||||||
|
private Class<T> resBodyClass;
|
||||||
|
private Object message;
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package io.shinhanlife.glow.communication.module.mci.component;
|
||||||
|
|
||||||
|
import io.shinhanlife.glow.communication.dto.Transfer;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: 실제 Glow Framework 의존성(JAR)이 추가되면 이 Mock 클래스를 삭제하세요.
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class GlowMciComponent<S, R> {
|
||||||
|
|
||||||
|
public Transfer<R> sync(Transfer<S> request) {
|
||||||
|
// 실제 Glow HTTP 통신 (GlowHttpHeaderUtil, HttpClient 등) 수행 시뮬레이션
|
||||||
|
return (Transfer<R>) Transfer.builder()
|
||||||
|
.body(new Object())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package io.shinhanlife.dap.biz.mcp.tool.annotation;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class McpFunctionAnnotationsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void usesConservativeDefaultsForMcpBehaviorHints() throws NoSuchMethodException {
|
||||||
|
Method method = SampleTool.class.getDeclaredMethod("execute");
|
||||||
|
McpFunction annotation = method.getAnnotation(McpFunction.class);
|
||||||
|
|
||||||
|
assertFalse(annotation.readOnlyHint());
|
||||||
|
assertFalse(annotation.destructiveHint());
|
||||||
|
assertFalse(annotation.idempotentHint());
|
||||||
|
assertFalse(annotation.openWorldHint());
|
||||||
|
}
|
||||||
|
|
||||||
|
static class SampleTool {
|
||||||
|
@McpFunction(displayName = "sample", name = "sample", description = "sample")
|
||||||
|
void execute() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ WORKDIR /app
|
|||||||
COPY gradlew .
|
COPY gradlew .
|
||||||
COPY gradle gradle
|
COPY gradle gradle
|
||||||
COPY build.gradle settings.gradle ./
|
COPY build.gradle settings.gradle ./
|
||||||
COPY dap-common dap-common
|
|
||||||
COPY dap-tool-core dap-tool-core
|
COPY dap-tool-core dap-tool-core
|
||||||
COPY dap-tool-email dap-tool-email
|
COPY dap-tool-email dap-tool-email
|
||||||
RUN chmod +x gradlew
|
RUN chmod +x gradlew
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|||||||
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
|
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
|
||||||
import org.springframework.cache.annotation.EnableCaching;
|
import org.springframework.cache.annotation.EnableCaching;
|
||||||
|
|
||||||
@SpringBootApplication(scanBasePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.common.adapter", "io.shinhanlife.dap.common.mcp", "io.shinhanlife.dap.common.config"})
|
@SpringBootApplication(scanBasePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.common.adapter", "io.shinhanlife.dap.common.mcp", "io.shinhanlife.dap.common.config", "io.shinhanlife.dap.common.integration"})
|
||||||
@ConfigurationPropertiesScan(basePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.common.adapter", "io.shinhanlife.dap.common.mcp", "io.shinhanlife.dap.common.config"})
|
@ConfigurationPropertiesScan(basePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.common.adapter", "io.shinhanlife.dap.common.mcp", "io.shinhanlife.dap.common.config", "io.shinhanlife.dap.common.integration"})
|
||||||
@EnableCaching
|
@EnableCaching
|
||||||
public class DapToolEmailApplication {
|
public class DapToolEmailApplication {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import java.util.Map;
|
|||||||
@McpTool(routingType = "EAI", categoryKey = "notification")
|
@McpTool(routingType = "EAI", categoryKey = "notification")
|
||||||
public class EmailToolService extends AbstractMcpToolService {
|
public class EmailToolService extends AbstractMcpToolService {
|
||||||
|
|
||||||
@McpFunction(displayName = "send_email 툴", name = "send_email", description = "이메일 발송", prompt = "고객에게 이메일을 발송해줘.", mappingId = "EMAIL_SEND_001")
|
@McpFunction(register = false, displayName = "send_email 툴", name = "send_email", description = "이메일 발송", prompt = "고객에게 이메일을 발송해줘.", mappingId = "EMAIL_SEND_001")
|
||||||
public Object sendEmail(EmailSendReq req) {
|
public Object sendEmail(EmailSendReq req) {
|
||||||
log.info("[Email] 이메일 발송 요청 수신. 수신자: {}", req.getEmailAddress());
|
log.info("[Email] 이메일 발송 요청 수신. 수신자: {}", req.getEmailAddress());
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
# Render 클라우드 환경 전용 설정
|
# Render ?\uFFFD\uB77C?\uFFFD\uB4DC ?\uFFFD\uACBD ?\uFFFD\uC6A9 ?\uFFFD\uC815
|
||||||
server.port=${PORT:8083}
|
server.port=${PORT:8083}
|
||||||
axhub.gateway.url=https://dap-gateway.onrender.com
|
axhub.gateway.url=https://axhub-gateway.onrender.com
|
||||||
axhub.tool.url=https://dap-tool-email.onrender.com
|
axhub.tool.url=https://axhub-tool-sms.onrender.com
|
||||||
|
|
||||||
# EIMS 동적 라우팅 접속 정보 (Mock)
|
# EIMS ?\uFFFD\uC801 ?\uFFFD\uC6B0???\uFFFD\uC18D ?\uFFFD\uBCF4 (Mock)
|
||||||
eims.http.url=http://localhost:${server.port}/api/gateway
|
eims.http.url=http://localhost:${server.port}/api/gateway
|
||||||
eims.tcp.host=127.0.0.1
|
eims.tcp.host=127.0.0.1
|
||||||
eims.tcp.port=8090
|
eims.tcp.port=8090
|
||||||
@@ -13,7 +13,7 @@ eims.jsp.json.url=http://localhost:${server.port}/mock/jsp-json
|
|||||||
eims.mci.url=http://localhost:${server.port}/api/mock/esb/api
|
eims.mci.url=http://localhost:${server.port}/api/mock/esb/api
|
||||||
eims.mcistring.url=http://localhost:${server.port}/api/mock/esb/string
|
eims.mcistring.url=http://localhost:${server.port}/api/mock/esb/string
|
||||||
|
|
||||||
# --- 신한라이프 EAI/MCI 연계 IP 정보 (개발 환경) ---
|
# --- ?\uFFFD\uD55C?\uFFFD\uC774??EAI/MCI ?\uFFFD\uACC4 IP ?\uFFFD\uBCF4 (\uAC1C\uBC1C ?\uFFFD\uACBD) ---
|
||||||
shinhan.integration.envrTypeCd=D
|
shinhan.integration.envrTypeCd=D
|
||||||
shinhan.integration.eai.url=http://10.176.32.181
|
shinhan.integration.eai.url=http://10.176.32.181
|
||||||
shinhan.integration.internalMci.url=http://10.176.32.173
|
shinhan.integration.internalMci.url=http://10.176.32.173
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ eims.mcistring.url=http://localhost:${server.port}/api/mock/esb/string
|
|||||||
|
|
||||||
# Gateway/Tool URLs
|
# Gateway/Tool URLs
|
||||||
axhub.gateway.url=http://localhost:8081
|
axhub.gateway.url=http://localhost:8081
|
||||||
axhub.tool.url=http://localhost:${server.port}
|
axhub.tool.url=${AXHUB_TOOL_URL:http://localhost:${server.port}}
|
||||||
|
|
||||||
# Disable Kafka
|
# Disable Kafka
|
||||||
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration
|
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ spring.profiles.active=local
|
|||||||
logging.level.org.apache.kafka=ERROR
|
logging.level.org.apache.kafka=ERROR
|
||||||
|
|
||||||
# Auto Prefix Namespace
|
# Auto Prefix Namespace
|
||||||
mcp.namespace=email
|
mcp.namespace=
|
||||||
|
|
||||||
# API 보안 키 설정
|
# API 보안 키 설정
|
||||||
mcp.security.tenant-domains.TESTER-DEV=ALL
|
mcp.security.tenant-domains.TESTER-DEV=ALL
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ WORKDIR /app
|
|||||||
COPY gradlew .
|
COPY gradlew .
|
||||||
COPY gradle gradle
|
COPY gradle gradle
|
||||||
COPY build.gradle settings.gradle ./
|
COPY build.gradle settings.gradle ./
|
||||||
COPY dap-common dap-common
|
|
||||||
COPY dap-tool-core dap-tool-core
|
COPY dap-tool-core dap-tool-core
|
||||||
COPY dap-tool-other dap-tool-other
|
COPY dap-tool-other dap-tool-other
|
||||||
RUN chmod +x gradlew
|
RUN chmod +x gradlew
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
package io.shinhanlife.dap.mcc.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package io.shinhanlife.dap.mcc.dto
|
||||||
|
* @className Onnba3011ReqDto
|
||||||
|
* @description 보종By가입설계한도계산조회 MCI 요청 전문 (ONNBA3011_I)
|
||||||
|
* @author 김형식
|
||||||
|
* @create 2026.09.01
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class Onnba3011Req {
|
||||||
|
|
||||||
|
@JsonPropertyDescription("통합기계약보험 (유형: gs, 길이: 72)")
|
||||||
|
@JsonProperty("unfcPrbuIrcoAddu")
|
||||||
|
private UnfcPrbuIrcoAdduDto unfcPrbuIrcoAddu;
|
||||||
|
|
||||||
|
@JsonPropertyDescription("처리구분코드 (길이: 1)")
|
||||||
|
@JsonProperty("dalScCd")
|
||||||
|
private String dalScCd;
|
||||||
|
|
||||||
|
@JsonPropertyDescription("고객청약관계 (길이: 2)")
|
||||||
|
@JsonProperty("cstSucoRltyCd")
|
||||||
|
private String cstSucoRltyCd;
|
||||||
|
|
||||||
|
@JsonPropertyDescription("고객번호 (길이: 12)")
|
||||||
|
@JsonProperty("csNo")
|
||||||
|
private String csNo;
|
||||||
|
|
||||||
|
@JsonPropertyDescription("주민등록번호 (길이: 50)")
|
||||||
|
@JsonProperty("rdreNo")
|
||||||
|
private String rdreNo;
|
||||||
|
|
||||||
|
@JsonPropertyDescription("통합급부계산 (길이: 1)")
|
||||||
|
@JsonProperty("unfcPvsCalReqYn")
|
||||||
|
private String unfcPvsCalReqYn;
|
||||||
|
|
||||||
|
@JsonPropertyDescription("한국신용정보 (길이: 1)")
|
||||||
|
@JsonProperty("kcisPymmTnnrReq")
|
||||||
|
private String kcisPymmTnnrReq;
|
||||||
|
|
||||||
|
@JsonPropertyDescription("한도초과여부 (길이: 1)")
|
||||||
|
@JsonProperty("lmovYn")
|
||||||
|
private String lmovYn;
|
||||||
|
|
||||||
|
@JsonPropertyDescription("일반경유승인 (길이: 1)")
|
||||||
|
@JsonProperty("genPsthApvTrgtYn")
|
||||||
|
private String genPsthApvTrgtYn;
|
||||||
|
|
||||||
|
@JsonPropertyDescription("보험사한도초과 (길이: 1)")
|
||||||
|
@JsonProperty("ircoLmovEcpbTrgtYn")
|
||||||
|
private String ircoLmovEcpbTrgtYn;
|
||||||
|
|
||||||
|
@JsonPropertyDescription("진단계산여부 (길이: 1)")
|
||||||
|
@JsonProperty("digCalYn")
|
||||||
|
private String digCalYn;
|
||||||
|
|
||||||
|
@JsonPropertyDescription("기계약포함진단 (길이: 1)")
|
||||||
|
@JsonProperty("prbuIciDigCalYn")
|
||||||
|
private String prbuIciDigCalYn;
|
||||||
|
|
||||||
|
@JsonPropertyDescription("청약심사기본Dto (유형: gs, 길이: 2532)")
|
||||||
|
@JsonProperty("sucoIspaBasDto")
|
||||||
|
private SucoIspaBasDto sucoIspaBasDto;
|
||||||
|
|
||||||
|
// ----- Nested DTO Classes -----
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class UnfcPrbuIrcoAdduDto {
|
||||||
|
// 실제 필요한 하위 필드들 추가 (사진 생략부분)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class SucoIspaBasDto {
|
||||||
|
|
||||||
|
@JsonPropertyDescription("계약처리유형 (길이: 2)")
|
||||||
|
@JsonProperty("ccnDalTypCd")
|
||||||
|
private String ccnDalTypCd;
|
||||||
|
|
||||||
|
@JsonPropertyDescription("신계약입력경로 (길이: 2)")
|
||||||
|
@JsonProperty("nwcnptCursCd")
|
||||||
|
private String nwcnptCursCd;
|
||||||
|
|
||||||
|
@JsonPropertyDescription("개인단체계약 (길이: 2)")
|
||||||
|
@JsonProperty("induAsctScCd")
|
||||||
|
private String induAsctScCd;
|
||||||
|
|
||||||
|
@JsonPropertyDescription("모집조직번호 (길이: 7)")
|
||||||
|
@JsonProperty("cepeOgnzNo")
|
||||||
|
private String cepeOgnzNo;
|
||||||
|
|
||||||
|
@JsonPropertyDescription("모집자사번번호 (길이: 8)")
|
||||||
|
@JsonProperty("cepePrafNo")
|
||||||
|
private String cepePrafNo;
|
||||||
|
|
||||||
|
@JsonPropertyDescription("수금조직번호 (길이: 7)")
|
||||||
|
@JsonProperty("clmoOgnzNo")
|
||||||
|
private String clmoOgnzNo;
|
||||||
|
|
||||||
|
@JsonPropertyDescription("수금자사번번호 (길이: 8)")
|
||||||
|
@JsonProperty("clmoPrafNo")
|
||||||
|
private String clmoPrafNo;
|
||||||
|
|
||||||
|
@JsonPropertyDescription("청약일자 (길이: 20)")
|
||||||
|
@JsonProperty("sucoYmd")
|
||||||
|
private String sucoYmd;
|
||||||
|
|
||||||
|
@JsonPropertyDescription("발행일자 (길이: 20)")
|
||||||
|
@JsonProperty("ispDt")
|
||||||
|
private String ispDt;
|
||||||
|
|
||||||
|
@JsonPropertyDescription("계약일자 (길이: 20)")
|
||||||
|
@JsonProperty("contYmd")
|
||||||
|
private String contYmd;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,8 +20,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|||||||
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
|
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
|
||||||
import org.springframework.cache.annotation.EnableCaching;
|
import org.springframework.cache.annotation.EnableCaching;
|
||||||
|
|
||||||
@SpringBootApplication(scanBasePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.common.adapter", "io.shinhanlife.dap.common.mcp", "io.shinhanlife.dap.common.config"})
|
@SpringBootApplication(scanBasePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.common.adapter", "io.shinhanlife.dap.common.mcp", "io.shinhanlife.dap.common.config", "io.shinhanlife.dap.common.integration", "io.shinhanlife.dap.other"})
|
||||||
@ConfigurationPropertiesScan(basePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.common.adapter", "io.shinhanlife.dap.common.mcp", "io.shinhanlife.dap.common.config"})
|
@ConfigurationPropertiesScan(basePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.common.adapter", "io.shinhanlife.dap.common.mcp", "io.shinhanlife.dap.common.config", "io.shinhanlife.dap.common.integration", "io.shinhanlife.dap.other"})
|
||||||
@EnableCaching
|
@EnableCaching
|
||||||
public class DapToolOtherApplication {
|
public class DapToolOtherApplication {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
package io.shinhanlife.dap.mcc.other.presentation;
|
|
||||||
|
|
||||||
import io.shinhanlife.dap.common.adapter.sender.ShinhanMciSender;
|
|
||||||
import io.shinhanlife.dap.common.integration.mci.dto.MciRequestWrapper;
|
|
||||||
import io.shinhanlife.dap.common.integration.mci.dto.ShinhanCommonHeaderDto;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.mcc.other.presentation
|
|
||||||
* @className MciTestController
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 김형식
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 김형식 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/test/mci")
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class MciTestController {
|
|
||||||
|
|
||||||
private final ShinhanMciSender shinhanMciSender;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public static class SampleBizData {
|
|
||||||
private String customerId;
|
|
||||||
private String amount;
|
|
||||||
private String message;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/send")
|
|
||||||
public String testSendMci(HttpServletRequest request, @RequestBody SampleBizData bizData) {
|
|
||||||
try {
|
|
||||||
MciRequestWrapper<SampleBizData> wrapper = new MciRequestWrapper<>();
|
|
||||||
|
|
||||||
ShinhanCommonHeaderDto header = new ShinhanCommonHeaderDto();
|
|
||||||
header.setItrIfId("NCSBACO00001");
|
|
||||||
header.setRcvSvcId("OBACA9040");
|
|
||||||
wrapper.setTgrmCmnnhddValu(header);
|
|
||||||
|
|
||||||
wrapper.setBody(bizData);
|
|
||||||
|
|
||||||
String hostUrl = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort();
|
|
||||||
String targetUrl = hostUrl + "/api/mock/esb/api"; // 로컬 Mock 서버 URL 호출
|
|
||||||
|
|
||||||
return shinhanMciSender.send(targetUrl, wrapper);
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
return "MCI 전송 실패: " + e.getMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -18,7 +18,6 @@ package io.shinhanlife.dap.mcc.service;
|
|||||||
import io.shinhanlife.dap.mcc.annotation.McpFunction;
|
import io.shinhanlife.dap.mcc.annotation.McpFunction;
|
||||||
import io.shinhanlife.dap.mcc.annotation.McpTool;
|
import io.shinhanlife.dap.mcc.annotation.McpTool;
|
||||||
import io.shinhanlife.dap.mcc.dto.BalanceReq;
|
import io.shinhanlife.dap.mcc.dto.BalanceReq;
|
||||||
import io.shinhanlife.dap.mcc.service.AbstractMcpToolService;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@@ -29,7 +28,7 @@ import java.util.Map;
|
|||||||
@McpTool(routingType = "MCI", categoryKey = "common")
|
@McpTool(routingType = "MCI", categoryKey = "common")
|
||||||
public class BalanceService extends AbstractMcpToolService {
|
public class BalanceService extends AbstractMcpToolService {
|
||||||
|
|
||||||
@McpFunction(displayName = "balance 툴", name = "balance",
|
@McpFunction(register = false, displayName = "balance 툴", name = "balance",
|
||||||
description = "고객의 계좌 잔액을 조회합니다.",
|
description = "고객의 계좌 잔액을 조회합니다.",
|
||||||
prompt = "고객 계좌 잔액을 조회해줘.",
|
prompt = "고객 계좌 잔액을 조회해줘.",
|
||||||
mappingId = "ACC_001"
|
mappingId = "ACC_001"
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
package io.shinhanlife.dap.mcc.service;
|
package io.shinhanlife.dap.mcc.service;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import io.shinhanlife.dap.mcc.annotation.McpFunction;
|
import io.shinhanlife.dap.mcc.annotation.McpFunction;
|
||||||
import io.shinhanlife.dap.mcc.annotation.McpTool;
|
import io.shinhanlife.dap.mcc.annotation.McpTool;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
|
||||||
import io.shinhanlife.dap.mcc.dto.BillingStatusReq;
|
|
||||||
import io.shinhanlife.dap.mcc.dto.BillingProcessReq;
|
import io.shinhanlife.dap.mcc.dto.BillingProcessReq;
|
||||||
|
import io.shinhanlife.dap.mcc.dto.BillingStatusReq;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@McpTool(
|
@McpTool(
|
||||||
routingType = "MCI",
|
routingType = "MCI",
|
||||||
@@ -30,12 +31,12 @@ import io.shinhanlife.dap.mcc.dto.BillingProcessReq;
|
|||||||
@lombok.extern.slf4j.Slf4j
|
@lombok.extern.slf4j.Slf4j
|
||||||
public class BillingProcessService extends AbstractMcpToolService {
|
public class BillingProcessService extends AbstractMcpToolService {
|
||||||
|
|
||||||
@McpFunction(displayName = "status 툴", name = "status", description = "청구심사 상태 조회", prompt = "현재 접수된 청구건 상태를 알려줘.", mappingId = "BILL_001")
|
@McpFunction(register = false, displayName = "status 툴", name = "status", description = "청구심사 상태 조회", prompt = "현재 접수된 청구건 상태를 알려줘.", mappingId = "BILL_001")
|
||||||
public Object getStatus(BillingStatusReq data) {
|
public Object getStatus(BillingStatusReq data) {
|
||||||
return executeBillingLogic("BILL_001", data);
|
return executeBillingLogic("BILL_001", data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@McpFunction(displayName = "process 툴", name = "process", description = "청구 처리", prompt = "현재 접수된 청구건에 대한 심사 처리를 진행해.", mappingId = "BILL_002")
|
@McpFunction(register = false, displayName = "process 툴", name = "process", description = "청구 처리", prompt = "현재 접수된 청구건에 대한 심사 처리를 진행해.", mappingId = "BILL_002")
|
||||||
public Object processBilling(BillingProcessReq data) {
|
public Object processBilling(BillingProcessReq data) {
|
||||||
return executeBillingLogic("BILL_002", data);
|
return executeBillingLogic("BILL_002", data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,12 +25,12 @@ import io.shinhanlife.dap.mcc.dto.BondIssueReq;
|
|||||||
*/
|
*/
|
||||||
public class BondIssueService extends AbstractMcpToolService {
|
public class BondIssueService extends AbstractMcpToolService {
|
||||||
|
|
||||||
@McpFunction(displayName = "check 툴", name = "check", register = true, description = "발행 가능 여부 조회 테스트", prompt = "디지털 증권 발행 한도가 충분한지 확인해줘.", mappingId = "BOND_001")
|
@McpFunction(displayName = "check 툴", name = "check", register = false, description = "발행 가능 여부 조회 테스트", prompt = "디지털 증권 발행 한도가 충분한지 확인해줘.", mappingId = "BOND_001")
|
||||||
public Object check(BondCheckReq data) {
|
public Object check(BondCheckReq data) {
|
||||||
return executeLegacy("EAI", "BOND_001", data);
|
return executeLegacy("EAI", "BOND_001", data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@McpFunction(displayName = "issue 툴", name = "issue", register = true, description = "증권 발행 테스트1", prompt = "디지털 증권 발행 프로세스를 실행해.", mappingId = "BOND_002")
|
@McpFunction(displayName = "issue 툴", name = "issue", register = false, description = "증권 발행 테스트1", prompt = "디지털 증권 발행 프로세스를 실행해.", mappingId = "BOND_002")
|
||||||
public Object issue(BondIssueReq data) {
|
public Object issue(BondIssueReq data) {
|
||||||
return executeLegacy("EAI", "BOND_002", data);
|
return executeLegacy("EAI", "BOND_002", data);
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user