Compare commits
38 Commits
ba39a23b09
...
feature/me
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a667431de1 | ||
|
|
b448e68f45 | ||
|
|
b68fbb9726 | ||
| c6a2f82455 | |||
|
|
2f5dad05df | ||
|
|
b0bf4a9c68 | ||
|
|
752aae4dde | ||
|
|
5cb37cfda6 | ||
|
|
2a4b3e68e9 | ||
|
|
ca1082f1c2 | ||
|
|
7b1d3131e6 | ||
|
|
139ddbc6ca | ||
|
|
870ebb723f | ||
|
|
a08a7d25df | ||
|
|
60acc5df81 | ||
|
|
f5be5b2514 | ||
|
|
aae4898d18 | ||
|
|
06b724459e | ||
|
|
4133b63595 | ||
|
|
1e18d2bc47 | ||
|
|
0c89a09ccf | ||
|
|
3ce9201cd5 | ||
|
|
be81c095c9 | ||
|
|
d4554ce34f | ||
|
|
b6d4ab1bec | ||
|
|
2c8497ebee | ||
|
|
8a93d5d6dc | ||
|
|
c0de1f4edb | ||
|
|
1ee9f9e802 | ||
|
|
25f0d2bab5 | ||
|
|
9b9159a605 | ||
|
|
c8dd43221e | ||
|
|
7b8c38489e | ||
|
|
63187429b4 | ||
|
|
d4953d60ec | ||
|
|
72891dc2db | ||
|
|
82c27bd468 | ||
|
|
ff5ca4fc7a |
@@ -1,8 +1,7 @@
|
||||
.git
|
||||
.git
|
||||
.gradle
|
||||
.idea
|
||||
build/
|
||||
*/build/
|
||||
/build/
|
||||
target/
|
||||
*/target/
|
||||
bin/
|
||||
|
||||
@@ -12,26 +12,36 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Prepare Environment (Install Node.js & Git & Java 21)
|
||||
run: |
|
||||
if command -v apt-get &> /dev/null; then
|
||||
apt-get update
|
||||
apt-get install -y nodejs git openjdk-21-jdk rsync
|
||||
elif command -v apk &> /dev/null; then
|
||||
apk add --no-cache nodejs git openjdk21 rsync
|
||||
fi
|
||||
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Sync Code to Host Volume
|
||||
run: |
|
||||
echo "Copying latest code to /app (Host Volume)..."
|
||||
cp -a . /app/
|
||||
echo "Copying latest code to /app (Host Volume) and removing stale files..."
|
||||
if command -v rsync &> /dev/null; then
|
||||
rsync -a --delete --exclude='.git' . /app/
|
||||
else
|
||||
rm -rf /app/dap-* /app/src /app/build.gradle /app/settings.gradle
|
||||
cp -a . /app/
|
||||
fi
|
||||
|
||||
- name: Deploy Task on Host
|
||||
run: |
|
||||
echo "Starting Standard CI/CD Deploy pipeline..."
|
||||
|
||||
# 1. gradle 공식 이미지로 바로 컴파일 빌드!
|
||||
docker run --rm \
|
||||
-v /home/ubuntu/app/ax_hub_mcp_tool:/app \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v /home/ubuntu/.gradle:/home/gradle/.gradle \
|
||||
-w /app \
|
||||
gradle:8-jdk21 \
|
||||
./gradlew bootJar -x test
|
||||
# 1. runner에 설치된 java로 직접 빌드! (격리된 환경 문제 해결)
|
||||
cd /app
|
||||
chmod +x gradlew
|
||||
./gradlew bootJar -x test
|
||||
|
||||
# 2. 빌드 결과 plain jar 아카이브 정리
|
||||
find /app -name "*-plain.jar" -delete
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# DAP Backend
|
||||
|
||||
Spring Boot 기반 DAP 관리자 백엔드 API 서버 및 MCP(Model Context Protocol) Gateway / Tool 분산 서버 프로젝트입니다.
|
||||
Spring Boot 기반 DAP 관리자 백엔드 API 서버 및 MCP(Model Context Protocol) Gateway / Tool 분산 서버 프로젝트 입니다.
|
||||
|
||||
---
|
||||
|
||||
@@ -114,11 +114,13 @@ MSA 및 외부 시스템(MCI) 연동 환경의 안정성을 위해 완벽한 3-T
|
||||
새로운 도메인의 기능을 추가할 때 발생하는 반복적인 설정(보일러플레이트, 설정 파일 복사 등)을 1초 만에 자동화하기 위해 **DAP Developer Portal (Web UI)** 및 **CLI 스캐폴더 2종**을 제공합니다.
|
||||
|
||||
### 1. DAP Developer Portal (Web UI) - 가장 추천하는 방식!
|
||||
이제 더 이상 터미널에서 명령어를 칠 필요가 없습니다. Gateway 모듈에 내장된 웹 화면에서 빈칸만 채우면 코드가 마법처럼 찍혀 나옵니다.
|
||||
이제 더 이상 터미널에서 명령어를 칠 필요가 없습니다. Gateway 모듈에 내장된 웹 화면에서 빈칸만 채우면 신한라이프 패키지 개발 가이드에 맞춘 코드가 마법처럼 찍혀 나옵니다.
|
||||
|
||||
1. **접속 방법**: Gateway 서버 기동 후 브라우저에서 `http://localhost:8081/admin/scaffold.html` 접속
|
||||
2. **Pod (모듈) 생성 탭**: 모듈명(예: hr)과 포트만 입력하면 독립적인 Spring Boot 모듈이 디렉토리부터 빌드 스크립트까지 완벽히 생성됩니다.
|
||||
3. **Tool (기능) 생성 탭**: 생성된 모듈에 새로운 툴(서비스/DTO) 코드를 자동으로 주입합니다.
|
||||
3. **Tool (기능) 생성 탭**: 생성된 모듈에 새로운 툴 코드를 자동으로 주입합니다.
|
||||
- **MCI 연동 기반 툴 생성**: 4자리 시스템 코드(예: `nclg`)를 기반으로 알맞은 패키지에 `MciNclgClient`, `Converter`, `_I`, `_O` 파일이 정확하게 생성됩니다.
|
||||
- **완벽한 보일러플레이트 자동화**: `UseCaseImpl` 내부에 컴포넌트(`Client`, `Converter`)가 자동으로 의존성 주입되며, Java 15 Text Block을 활용해 들여쓰기(Indentation)까지 완벽히 정렬된 코드를 제공합니다.
|
||||
|
||||
### 2. CLI 스캐폴더 (기존 터미널 방식)
|
||||
웹 화면을 사용할 수 없는 환경이거나 터미널이 익숙한 경우, 아래 명령어를 통해 CLI 마법사를 사용할 수 있습니다.
|
||||
|
||||
40
cleanup.py
40
cleanup.py
@@ -1,40 +0,0 @@
|
||||
import re
|
||||
|
||||
# 1. Clean docker-compose.yml
|
||||
with open('docker-compose.yml', 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Remove tool-email and tool-payment blocks
|
||||
content = re.sub(r'^\s*tool-(email|payment)-(blue|green):.*?^\s*- SPRING_PROFILES_ACTIVE=\$\{ACTIVE_PROFILE:-local\}\n\n?', '', content, flags=re.MULTILINE | re.DOTALL)
|
||||
# Remove MCP fallback routes
|
||||
content = re.sub(r'^\s*- MCP_GATEWAY_FALLBACK_ROUTES_(EMAIL|PAYMENT)=.*?\n', '', content, flags=re.MULTILINE)
|
||||
|
||||
with open('docker-compose.yml', 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
# 2. Clean deploy.yml
|
||||
with open('.gitea/workflows/deploy.yml', 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
new_lines = []
|
||||
for line in lines:
|
||||
if 'tool-email' in line or 'tool-payment' in line:
|
||||
# For lines that have multiple tools (like docker compose stop), just remove the specific ones
|
||||
if 'docker compose stop' in line:
|
||||
line = re.sub(r' tool-(email|payment)-\$[A-Z_]+', '', line)
|
||||
new_lines.append(line)
|
||||
else:
|
||||
new_lines.append(line)
|
||||
|
||||
with open('.gitea/workflows/deploy.yml', 'w', encoding='utf-8') as f:
|
||||
f.writelines(new_lines)
|
||||
|
||||
# 3. Clean nginx/conf.d/default.conf
|
||||
with open('nginx/conf.d/default.conf', 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
content = re.sub(r'upstream tool-(email|payment) \{.*?\n\}\n\n?', '', content, flags=re.MULTILINE | re.DOTALL)
|
||||
content = re.sub(r'server \{\n\s+listen 828[35];.*?\n\}\n\n?', '', content, flags=re.MULTILINE | re.DOTALL)
|
||||
|
||||
with open('nginx/conf.d/default.conf', 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
@@ -1,19 +1,8 @@
|
||||
FROM eclipse-temurin:21-jdk-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY gradlew .
|
||||
COPY gradle gradle
|
||||
COPY build.gradle settings.gradle ./
|
||||
|
||||
COPY dap-tool-core dap-tool-core
|
||||
COPY dap-gateway dap-gateway
|
||||
RUN chmod +x gradlew
|
||||
RUN ./gradlew clean :dap-gateway:build -x test
|
||||
|
||||
FROM eclipse-temurin:21-jre-alpine
|
||||
WORKDIR /app
|
||||
RUN apk add --no-cache tzdata
|
||||
ENV TZ=Asia/Seoul
|
||||
COPY --from=builder /app/dap-gateway/build/libs/*-SNAPSHOT.jar app.jar
|
||||
COPY dap-gateway/build/libs/*-SNAPSHOT.jar app.jar
|
||||
EXPOSE 8081
|
||||
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||
|
||||
|
||||
@@ -112,9 +112,7 @@ public class McpRouterController {
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/tools/list")
|
||||
public ResponseEntity<JsonRpcResponse> listTools(
|
||||
@RequestParam(value = "categoryKey", required = false) String categoryKey) {
|
||||
private List<ToolMetadata> fetchAllActiveTools() {
|
||||
List<ToolMetadata> activeTools = redisRegistryService.getAllTools()
|
||||
.stream()
|
||||
.filter(ToolMetadata::getVisible)
|
||||
@@ -148,6 +146,14 @@ public class McpRouterController {
|
||||
log.warn("Failed to fetch local tools from fallback URL: {}", url);
|
||||
}
|
||||
}
|
||||
return activeTools;
|
||||
}
|
||||
|
||||
@GetMapping("/tools/list")
|
||||
public ResponseEntity<JsonRpcResponse> listTools(
|
||||
@RequestParam(value = "categoryKey", required = false) String categoryKey) {
|
||||
|
||||
List<ToolMetadata> activeTools = fetchAllActiveTools();
|
||||
|
||||
if (categoryKey != null && !categoryKey.trim().isEmpty()) {
|
||||
activeTools = activeTools.stream()
|
||||
@@ -161,12 +167,10 @@ public class McpRouterController {
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
@GetMapping(value = "/tools/docs/markdown", produces = "text/markdown;charset=UTF-8")
|
||||
public ResponseEntity<String> generateToolsMarkdown() {
|
||||
List<ToolMetadata> tools = redisRegistryService.getAllTools()
|
||||
.stream()
|
||||
.filter(ToolMetadata::getVisible)
|
||||
.collect(Collectors.toList());
|
||||
List<ToolMetadata> tools = fetchAllActiveTools();
|
||||
|
||||
StringBuilder md = new StringBuilder();
|
||||
md.append("# \uD83E\uDD16 Shinhan AI Tool Catalog\n\n");
|
||||
|
||||
@@ -63,8 +63,9 @@ public class ScaffoldingController {
|
||||
String date = req.get("date");
|
||||
if (date == null || date.trim().isEmpty()) date = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy.MM.dd"));
|
||||
boolean register = Boolean.parseBoolean(req.getOrDefault("register", "true"));
|
||||
String clientSystemCode = req.get("clientSystemCode");
|
||||
|
||||
return ToolScaffolder.scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, date, register);
|
||||
return ToolScaffolder.scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, date, register, clientSystemCode);
|
||||
} catch (Exception e) {
|
||||
return "오류 발생: " + e.getMessage();
|
||||
}
|
||||
|
||||
@@ -380,6 +380,7 @@
|
||||
<a href="/admin/scaffold.html" style="color:#ffffff;" class="font-semibold">Scaffold</a>
|
||||
<a href="/catalog.html" style="color:#a1a1aa;" class="hover:text-white transition-colors">Catalog</a>
|
||||
<a href="/playground.html" style="color:#a1a1aa;" class="hover:text-white transition-colors">Playground</a>
|
||||
<a href="/chat.html" style="color:#a1a1aa;" class="hover:text-white transition-colors">Chat</a>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
@@ -499,6 +500,14 @@
|
||||
<div class="input-hint">Controls dynamic routing exposure.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Target System Code (MCI Only)</label>
|
||||
<input type="text" class="form-control" name="clientSystemCode" pattern="^[a-zA-Z]{4}$" maxlength="4" minlength="4" placeholder="e.g. cfpa" oninput="this.value = this.value.toLowerCase()">
|
||||
<div class="input-hint">Required for MCI Protocol (4 letters).</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
<a href="/admin/scaffold.html" style="color:#a1a1aa;" class="hover:text-white transition-colors">Scaffold</a>
|
||||
<a href="/catalog.html" style="color:#ffffff;" class="font-semibold">Catalog</a>
|
||||
<a href="/playground.html" style="color:#a1a1aa;" class="hover:text-white transition-colors">Playground</a>
|
||||
<a href="/chat.html" style="color:#a1a1aa;" class="hover:text-white transition-colors">Chat</a>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
@@ -79,12 +80,24 @@
|
||||
|
||||
<main class="max-w-6xl mx-auto px-6 py-10">
|
||||
<div class="flex flex-col md:flex-row justify-between items-start md:items-end mb-8">
|
||||
<div class="max-w-2xl">
|
||||
<h1 class="text-3xl font-bold tracking-tight mb-2" style="color:#f4f4f5;">Registry Catalog</h1>
|
||||
<p class="text-sm" style="color:#a1a1aa;">Currently integrated AI function tools across all modules. View parameters, descriptions, and integration types.</p>
|
||||
<div class="max-w-2xl w-full">
|
||||
<div class="flex items-center space-x-3 mb-2">
|
||||
<h1 class="text-3xl font-bold tracking-tight" style="color:#f4f4f5;">Registry Catalog</h1>
|
||||
<button onclick="openGuideModal()" class="w-6 h-6 rounded-full flex items-center justify-center text-sm font-bold bg-zinc-800 text-zinc-400 hover:bg-zinc-700 hover:text-white transition-colors" title="Connection Guide">?</button>
|
||||
</div>
|
||||
<p class="text-sm mb-6" style="color:#a1a1aa;">Currently integrated AI function tools across all modules. View parameters, descriptions, and integration types.</p>
|
||||
|
||||
<div class="relative max-w-md w-full">
|
||||
<input type="text" id="searchInput" placeholder="Search tools by name, description, or category..." class="w-full bg-zinc-900 border border-zinc-700 rounded-lg px-4 py-2.5 text-sm text-zinc-200 placeholder-zinc-500 focus:outline-none focus:border-blue-500 transition-colors">
|
||||
<svg class="w-4 h-4 absolute right-3 top-3 text-zinc-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 md:mt-0">
|
||||
<button id="copyMdBtn" class="btn-primary flex items-center">
|
||||
<div class="mt-4 md:mt-0 flex space-x-3 w-full md:w-auto">
|
||||
<button id="copyJsonBtn" class="btn-primary flex items-center justify-center flex-1 md:flex-none" style="background: linear-gradient(135deg, #10b981, #059669); box-shadow: 0 0 20px rgba(16, 185, 129, 0.2);">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg>
|
||||
<span id="copyJsonText">Copy Client Config</span>
|
||||
</button>
|
||||
<button id="copyMdBtn" class="btn-primary flex items-center justify-center flex-1 md:flex-none">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"></path></svg>
|
||||
<span id="copyBtnText">Copy Markdown Docs</span>
|
||||
</button>
|
||||
@@ -99,13 +112,52 @@
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Guide Modal -->
|
||||
<div id="guideModal" class="fixed inset-0 z-[100] hidden flex items-center justify-center">
|
||||
<div class="absolute inset-0 bg-black/60 backdrop-blur-sm" onclick="closeGuideModal()"></div>
|
||||
<div class="relative bg-zinc-900 border border-zinc-700 rounded-xl shadow-2xl w-full max-w-2xl mx-4 overflow-hidden">
|
||||
<div class="p-5 border-b border-zinc-800 flex justify-between items-center bg-zinc-900/50">
|
||||
<h3 class="text-lg font-semibold text-zinc-100 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
||||
AI Agent Connection Guide
|
||||
</h3>
|
||||
<button onclick="closeGuideModal()" class="text-zinc-400 hover:text-white transition-colors">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="p-6 space-y-6 max-h-[70vh] overflow-y-auto text-sm text-zinc-300">
|
||||
<div>
|
||||
<h4 class="font-semibold text-blue-400 mb-3 flex items-center"><span class="w-5 h-5 rounded bg-blue-500/20 flex items-center justify-center mr-2 text-xs">1</span> Cursor IDE 연동</h4>
|
||||
<p class="mb-2">1. Cursor 설정 창 열기 (<kbd class="px-1.5 py-0.5 bg-zinc-800 border border-zinc-700 rounded text-xs text-zinc-300">Cmd/Ctrl + ,</kbd>)</p>
|
||||
<p class="mb-2">2. 사이드바에서 <strong>Features</strong> > <strong>MCP</strong> 메뉴로 이동</p>
|
||||
<p>3. <strong>+ Add New MCP Server</strong>를 클릭하고, 툴 카테고리의 [Copy SSE URL] 버튼으로 복사한 주소를 `SSE` 타입으로 붙여넣기 하세요.</p>
|
||||
</div>
|
||||
<div class="border-t border-zinc-800 pt-6">
|
||||
<h4 class="font-semibold text-orange-400 mb-3 flex items-center"><span class="w-5 h-5 rounded bg-orange-500/20 flex items-center justify-center mr-2 text-xs">2</span> Claude Desktop 연동</h4>
|
||||
<p class="mb-2">1. 아래 설정 파일을 텍스트 에디터로 엽니다:</p>
|
||||
<div class="bg-black/40 p-3 rounded-lg border border-zinc-800/80 font-mono text-xs mb-3 text-zinc-400">
|
||||
<div class="mb-1"><strong>Mac:</strong> ~/Library/Application Support/Claude/claude_desktop_config.json</div>
|
||||
<div><strong>Win:</strong> %APPDATA%\Claude\claude_desktop_config.json</div>
|
||||
</div>
|
||||
<p>2. 화면 우측 상단의 <strong>[Copy Client Config]</strong> 버튼을 누른 후, 복사된 JSON 코드를 이 파일 안에 그대로 붙여넣습니다.</p>
|
||||
</div>
|
||||
<div class="border-t border-zinc-800 pt-6">
|
||||
<h4 class="font-semibold text-emerald-400 mb-3 flex items-center"><span class="w-5 h-5 rounded bg-emerald-500/20 flex items-center justify-center mr-2 text-xs">3</span> 커스텀 Python/Node Agent 연동</h4>
|
||||
<p class="mb-2">1. MCP 공식 SDK를 설치합니다. <code class="text-xs bg-zinc-800 px-1.5 py-0.5 rounded text-zinc-300 border border-zinc-700">npm i @modelcontextprotocol/sdk</code></p>
|
||||
<p>2. SSE Client Transport를 사용하여 복사한 카테고리별 URL로 연결합니다.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let allTools = []; // 글로벌 변수로 툴 데이터 보관
|
||||
async function fetchTools() {
|
||||
try {
|
||||
const response = await fetch('/mcp/api/v1/tools/list');
|
||||
const data = await response.json();
|
||||
const tools = data.result?.tools || [];
|
||||
renderCatalog(tools);
|
||||
allTools = data.result?.tools || [];
|
||||
renderCatalog(allTools);
|
||||
} catch (err) {
|
||||
document.getElementById('catalogContainer').innerHTML = `
|
||||
<div style="padding:16px; border:1px solid #7f1d1d; background:rgba(127,29,29,0.15); color:#fca5a5; font-size:14px; border-radius:8px;">
|
||||
@@ -134,7 +186,12 @@
|
||||
header.style.cssText = 'margin-bottom:16px; padding-bottom:8px; border-bottom:1px solid #27272a; display:flex; align-items:baseline; justify-content:space-between;';
|
||||
header.innerHTML = `
|
||||
<h2 style="font-size:20px; font-weight:600; color:#f4f4f5; text-transform:capitalize;">${group}</h2>
|
||||
<span class="geist-mono" style="font-size:12px; color:#71717a;">${grouped[group].length} items</span>
|
||||
<div style="display:flex; align-items:center; gap:8px;">
|
||||
<span class="geist-mono" style="font-size:12px; color:#71717a;">${grouped[group].length} items</span>
|
||||
<button onclick="copySseUrl('${group.toLowerCase()}', this)" class="hover:text-blue-400 text-zinc-500 transition-colors p-1.5 rounded hover:bg-zinc-800/50" title="Copy SSE URL">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
section.appendChild(header);
|
||||
const grid = document.createElement('div');
|
||||
@@ -227,6 +284,60 @@
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('searchInput').addEventListener('input', (e) => {
|
||||
const query = e.target.value.toLowerCase();
|
||||
const filtered = allTools.filter(t =>
|
||||
(t.name && t.name.toLowerCase().includes(query)) ||
|
||||
(t.displayName && t.displayName.toLowerCase().includes(query)) ||
|
||||
(t.description && t.description.toLowerCase().includes(query)) ||
|
||||
(t.categoryKey && t.categoryKey.toLowerCase().includes(query))
|
||||
);
|
||||
renderCatalog(filtered);
|
||||
});
|
||||
|
||||
function copySseUrl(category, btnElement) {
|
||||
const url = `${window.location.origin}/mcp/sse/${category}`;
|
||||
navigator.clipboard.writeText(url).then(() => {
|
||||
const originalSvg = btnElement.innerHTML;
|
||||
btnElement.innerHTML = `<svg class="w-4 h-4 text-emerald-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg>`;
|
||||
setTimeout(() => { btnElement.innerHTML = originalSvg; }, 2000);
|
||||
}).catch(() => alert('Failed to copy.'));
|
||||
}
|
||||
|
||||
function openGuideModal() {
|
||||
document.getElementById('guideModal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function closeGuideModal() {
|
||||
document.getElementById('guideModal').classList.add('hidden');
|
||||
}
|
||||
|
||||
document.getElementById('copyJsonBtn').addEventListener('click', () => {
|
||||
const groups = [...new Set(allTools.map(t => t.categoryKey || 'Others'))];
|
||||
const config = { mcpServers: {} };
|
||||
groups.forEach(g => {
|
||||
const safeKey = g.toLowerCase().replace(/[^a-z0-9]/g, '-');
|
||||
config.mcpServers[`axhub-${safeKey}`] = {
|
||||
"command": "node",
|
||||
"args": [],
|
||||
"env": {},
|
||||
"type": "sse",
|
||||
"url": `${window.location.origin}/mcp/sse/${g.toLowerCase()}`
|
||||
};
|
||||
// Note: Claude desktop expects command/args for stdio, but for SSE it usually accepts just url/type or requires specialized settings.
|
||||
// Adjusted to basic config.
|
||||
delete config.mcpServers[`axhub-${safeKey}`].command;
|
||||
delete config.mcpServers[`axhub-${safeKey}`].args;
|
||||
delete config.mcpServers[`axhub-${safeKey}`].env;
|
||||
});
|
||||
const textSpan = document.getElementById('copyJsonText');
|
||||
const originalText = textSpan.textContent;
|
||||
navigator.clipboard.writeText(JSON.stringify(config, null, 2)).then(() => {
|
||||
textSpan.textContent = 'Copied JSON!';
|
||||
setTimeout(() => { textSpan.textContent = originalText; }, 2000);
|
||||
}).catch(() => alert('Failed to copy JSON.'));
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', fetchTools);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -173,6 +173,10 @@
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
function removeMarkdownEmphasis(text) {
|
||||
return text.replace(/\*\*/g, '');
|
||||
}
|
||||
|
||||
async function sendMessage() {
|
||||
const text = chatInput.value.trim();
|
||||
if (!text || isLoading) return;
|
||||
@@ -205,6 +209,7 @@
|
||||
// 5. 스트림 읽기
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder("utf-8");
|
||||
let responseText = '';
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
@@ -215,7 +220,8 @@
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data:')) {
|
||||
const data = line.substring(5);
|
||||
textContainer.innerHTML += escapeHtml(data);
|
||||
responseText += data;
|
||||
textContainer.textContent = removeMarkdownEmphasis(responseText);
|
||||
scrollToBottom();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +111,7 @@
|
||||
<a href="/admin/scaffold.html" style="color:#a1a1aa;" class="hover:text-white transition-colors">Scaffold</a>
|
||||
<a href="/catalog.html" style="color:#a1a1aa;" class="hover:text-white transition-colors">Catalog</a>
|
||||
<a href="/playground.html" style="color:#ffffff;" class="font-semibold">Playground</a>
|
||||
<a href="/chat.html" style="color:#a1a1aa;" class="hover:text-white transition-colors">Chat</a>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="flex items-center space-x-3">
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.shinhanlife.dap.mcg.sync.DynamicMcpController;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
|
||||
@@ -5,7 +5,8 @@ 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 io.shinhanlife.dap.mcg.dto.ToolMetadata;
|
||||
import io.shinhanlife.dap.mcg.sync.RegistryMcpToolSpecificationFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class RegistryMcpToolSpecificationFactoryTest {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package io.shinhanlife.dap.lib.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.lib.annotation
|
||||
* @className McpValidation
|
||||
* @description Declares JSON Schema validation constraints for MCP tool input fields
|
||||
* @author 0986406
|
||||
* @create 2026.07.27
|
||||
* <pre>
|
||||
* ---------- revision history ----------
|
||||
* date author description
|
||||
* ---------- --------- ---------------------------
|
||||
* 2026.07.27 0986406 initial creation
|
||||
* </pre>
|
||||
*/
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface McpValidation {
|
||||
boolean required() default false;
|
||||
String pattern() default "";
|
||||
long minimum() default Long.MIN_VALUE;
|
||||
String[] allowedValues() default {};
|
||||
}
|
||||
@@ -3,11 +3,16 @@ package io.shinhanlife.dap.lib.util;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
|
||||
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
||||
import io.shinhanlife.dap.lib.annotation.McpValidation;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
/**
|
||||
@@ -30,17 +35,23 @@ public class JsonSchemaGenerator {
|
||||
* Java DTO 클래스를 분석하여 MCP 규격의 완전한 JSON Schema를 생성합니다.
|
||||
*/
|
||||
public static Map<String, Object> generateSchema(Class<?> clazz) {
|
||||
return generateSchema(clazz, new HashSet<>());
|
||||
}
|
||||
|
||||
private static Map<String, Object> generateSchema(Class<?> clazz, Set<Class<?>> visiting) {
|
||||
Map<String, Object> schema = new HashMap<>();
|
||||
schema.put("type", "object");
|
||||
if (!visiting.add(clazz)) {
|
||||
return schema;
|
||||
}
|
||||
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
List<String> requiredList = new ArrayList<>();
|
||||
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
Map<String, Object> fieldSchema = new HashMap<>();
|
||||
Map<String, Object> fieldSchema = createFieldSchema(field, visiting);
|
||||
|
||||
// 1. 타입 매핑
|
||||
fieldSchema.put("type", mapJavaTypeToJsonType(field.getType()));
|
||||
|
||||
// 2. 어노테이션 기반 설명 추출
|
||||
McpParameter paramAnnotation = field.getAnnotation(McpParameter.class);
|
||||
@@ -58,6 +69,20 @@ public class JsonSchemaGenerator {
|
||||
if ((jsonProp != null && jsonProp.required()) || (paramAnnotation != null && paramAnnotation.required())) {
|
||||
requiredList.add(field.getName());
|
||||
}
|
||||
|
||||
McpValidation validation = field.getAnnotation(McpValidation.class);
|
||||
if (validation != null && validation.required() && !requiredList.contains(field.getName())) {
|
||||
requiredList.add(field.getName());
|
||||
}
|
||||
if (validation != null && !validation.pattern().isEmpty()) {
|
||||
fieldSchema.put("pattern", validation.pattern());
|
||||
}
|
||||
if (validation != null && validation.minimum() != Long.MIN_VALUE) {
|
||||
fieldSchema.put("minimum", validation.minimum());
|
||||
}
|
||||
if (validation != null && validation.allowedValues().length > 0) {
|
||||
fieldSchema.put("enum", List.of(validation.allowedValues()));
|
||||
}
|
||||
|
||||
properties.put(field.getName(), fieldSchema);
|
||||
}
|
||||
@@ -67,9 +92,47 @@ public class JsonSchemaGenerator {
|
||||
schema.put("required", requiredList);
|
||||
}
|
||||
|
||||
visiting.remove(clazz);
|
||||
return schema;
|
||||
}
|
||||
|
||||
private static Map<String, Object> createFieldSchema(Field field, Set<Class<?>> visiting) {
|
||||
Class<?> fieldType = field.getType();
|
||||
if (isSimpleType(fieldType)) {
|
||||
return new HashMap<>(Map.of("type", mapJavaTypeToJsonType(fieldType)));
|
||||
}
|
||||
if (List.class.isAssignableFrom(fieldType)) {
|
||||
Map<String, Object> fieldSchema = new HashMap<>();
|
||||
fieldSchema.put("type", "array");
|
||||
fieldSchema.put("items", generateItemsSchema(field, visiting));
|
||||
return fieldSchema;
|
||||
}
|
||||
return generateSchema(fieldType, visiting);
|
||||
}
|
||||
|
||||
private static Map<String, Object> generateItemsSchema(Field field, Set<Class<?>> visiting) {
|
||||
Type genericType = field.getGenericType();
|
||||
if (genericType instanceof ParameterizedType parameterizedType) {
|
||||
Type itemType = parameterizedType.getActualTypeArguments()[0];
|
||||
if (itemType instanceof Class<?> itemClass) {
|
||||
if (isSimpleType(itemClass)) {
|
||||
return new HashMap<>(Map.of("type", mapJavaTypeToJsonType(itemClass)));
|
||||
}
|
||||
return generateSchema(itemClass, visiting);
|
||||
}
|
||||
}
|
||||
return new HashMap<>(Map.of("type", "object"));
|
||||
}
|
||||
|
||||
private static boolean isSimpleType(Class<?> clazz) {
|
||||
return clazz == String.class
|
||||
|| clazz == Integer.class || clazz == int.class
|
||||
|| clazz == Long.class || clazz == long.class
|
||||
|| clazz == Double.class || clazz == double.class
|
||||
|| clazz == Float.class || clazz == float.class
|
||||
|| clazz == Boolean.class || clazz == boolean.class;
|
||||
}
|
||||
|
||||
private static String mapJavaTypeToJsonType(Class<?> clazz) {
|
||||
if (clazz == String.class) return "string";
|
||||
if (clazz == Integer.class || clazz == int.class) return "integer";
|
||||
|
||||
@@ -68,7 +68,7 @@ public class ToolScaffolder {
|
||||
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);
|
||||
String result = scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, createDate, true, null);
|
||||
System.out.println(result);
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ public class ToolScaffolder {
|
||||
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 {
|
||||
public static String scaffold(String baseName, String interfaceId, String description, String group, String routingType, String moduleName, String author, String createDate, boolean register, String clientSystemCode) throws IOException {
|
||||
baseName = toPascalCase(baseName);
|
||||
String envSourceDir = System.getenv("AXHUB_SOURCE_DIR");
|
||||
Path rootDir = envSourceDir != null ? Paths.get(envSourceDir) : Paths.get(".");
|
||||
@@ -95,6 +95,18 @@ public class ToolScaffolder {
|
||||
String bizPackage = BASE_PACKAGE + ".biz." + group.toLowerCase();
|
||||
boolean isMci = "MCI".equalsIgnoreCase(routingType);
|
||||
String mciGroupPath = "infra/itrf/mci/" + group.toLowerCase();
|
||||
String clientPkgSuffix = "";
|
||||
String clientPrefixCap = "";
|
||||
Path mciClientDir = null;
|
||||
|
||||
if (isMci && clientSystemCode != null && clientSystemCode.length() == 4) {
|
||||
String clientPrefix = clientSystemCode.toLowerCase();
|
||||
clientPkgSuffix = clientPrefix.substring(0, 3) + "." + clientPrefix.substring(3, 4);
|
||||
clientPrefixCap = toPascalCase(clientSystemCode);
|
||||
mciGroupPath = "infra/itrf/mci/" + clientPrefix.substring(0, 3) + "/" + clientPrefix.substring(3, 4);
|
||||
mciClientDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, mciGroupPath));
|
||||
}
|
||||
|
||||
Path mciIoDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, mciGroupPath, "io"));
|
||||
|
||||
Files.createDirectories(usecaseDir);
|
||||
@@ -102,6 +114,9 @@ public class ToolScaffolder {
|
||||
Files.createDirectories(dtoDir);
|
||||
if (isMci) {
|
||||
Files.createDirectories(mciIoDir);
|
||||
if (mciClientDir != null) {
|
||||
Files.createDirectories(mciClientDir);
|
||||
}
|
||||
} else {
|
||||
Files.createDirectories(legacyDtoDir);
|
||||
}
|
||||
@@ -205,7 +220,7 @@ public class ToolScaffolder {
|
||||
bizPackage, baseName,
|
||||
routingType, group.toLowerCase(),
|
||||
baseName,
|
||||
baseName, toolName, description, description + " 해줘.", interfaceId, register,
|
||||
baseName, toolName, description, description + " ?줘.", interfaceId, register,
|
||||
baseName
|
||||
);
|
||||
|
||||
@@ -226,8 +241,9 @@ public class ToolScaffolder {
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import java.util.Map;
|
||||
import %s.converter.%sLegacyConverter;
|
||||
import %s.infra.itrf.mci.%s.io.%s_I;
|
||||
import %s.converter.%sConverter;
|
||||
import %s.%s.io.%s_I;
|
||||
%s
|
||||
|
||||
/**
|
||||
* @package %s.usecase.impl
|
||||
@@ -248,8 +264,8 @@ public class ToolScaffolder {
|
||||
@RequiredArgsConstructor
|
||||
public class %sUseCaseImpl implements %sUseCase {
|
||||
|
||||
private final AxhubMciComponent mci;
|
||||
private final %sLegacyConverter converter;
|
||||
%s
|
||||
private final %sConverter converter;
|
||||
|
||||
@Override
|
||||
public Object execute(%sRequest req) {
|
||||
@@ -276,14 +292,20 @@ public class ToolScaffolder {
|
||||
bizPackage, baseName,
|
||||
bizPackage, baseName,
|
||||
bizPackage, baseName,
|
||||
BASE_PACKAGE, group.toLowerCase(), interfaceId,
|
||||
bizPackage, baseName,
|
||||
BASE_PACKAGE, mciGroupPath.replace("/", "."), interfaceId,
|
||||
(clientPrefixCap.isEmpty() ? "" : "import " + BASE_PACKAGE + "." + mciGroupPath.replace("/", ".") + ".Mci" + clientPrefixCap + "Client;\n"),
|
||||
bizPackage,
|
||||
baseName,
|
||||
author,
|
||||
createDate,
|
||||
createDate, author,
|
||||
baseName, baseName,
|
||||
baseName,
|
||||
baseName, toolName,
|
||||
baseName,
|
||||
(clientPrefixCap.isEmpty() ? "private final AxhubMciComponent mci;" : "private final Mci" + clientPrefixCap + "Client mci;"),
|
||||
baseName,
|
||||
baseName,
|
||||
toolName,
|
||||
interfaceId,
|
||||
interfaceId
|
||||
);
|
||||
@@ -295,7 +317,7 @@ public class ToolScaffolder {
|
||||
import %s.dto.%sResponse;
|
||||
import %s.usecase.%sUseCase;
|
||||
import io.shinhanlife.dap.mcc.usecase.AbstractMcpToolUseCase;
|
||||
import %s.converter.%sLegacyConverter;
|
||||
import %s.converter.%sConverter;
|
||||
import org.springframework.stereotype.Service;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -319,7 +341,7 @@ public class ToolScaffolder {
|
||||
@RequiredArgsConstructor
|
||||
public class %sUseCaseImpl extends AbstractMcpToolUseCase implements %sUseCase {
|
||||
|
||||
private final %sLegacyConverter converter;
|
||||
private final %sConverter converter;
|
||||
|
||||
@Override
|
||||
public Object execute(%sRequest req) {
|
||||
@@ -333,6 +355,7 @@ public class ToolScaffolder {
|
||||
bizPackage, baseName,
|
||||
bizPackage, baseName,
|
||||
bizPackage, baseName,
|
||||
bizPackage, baseName,
|
||||
author,
|
||||
createDate,
|
||||
createDate, author,
|
||||
@@ -348,12 +371,12 @@ public class ToolScaffolder {
|
||||
|
||||
if (isMci) {
|
||||
String mciReqContent = """
|
||||
package %s.infra.itrf.mci.%s.io;
|
||||
package %s.%s.io;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @package %s.infra.itrf.mci.%s.io
|
||||
* @package %s.%s.io
|
||||
* @className %s_I
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author %s
|
||||
@@ -378,16 +401,16 @@ public class ToolScaffolder {
|
||||
*/
|
||||
private String content;
|
||||
}
|
||||
""".formatted(BASE_PACKAGE, group.toLowerCase(), BASE_PACKAGE, group.toLowerCase(), interfaceId, author, createDate, createDate, author, interfaceId);
|
||||
""".formatted(BASE_PACKAGE, mciGroupPath.replace("/", "."), BASE_PACKAGE, mciGroupPath.replace("/", "."), interfaceId, author, createDate, createDate, author, interfaceId);
|
||||
Files.writeString(mciIoDir.resolve(interfaceId + "_I.java"), mciReqContent);
|
||||
|
||||
String mciResContent = """
|
||||
package %s.infra.itrf.mci.%s.io;
|
||||
package %s.%s.io;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @package %s.infra.itrf.mci.%s.io
|
||||
* @package %s.%s.io
|
||||
* @className %s_O
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author %s
|
||||
@@ -404,7 +427,7 @@ public class ToolScaffolder {
|
||||
public class %s_O {
|
||||
// TODO: Add response fields here
|
||||
}
|
||||
""".formatted(BASE_PACKAGE, group.toLowerCase(), BASE_PACKAGE, group.toLowerCase(), interfaceId, author, createDate, createDate, author, interfaceId);
|
||||
""".formatted(BASE_PACKAGE, mciGroupPath.replace("/", "."), BASE_PACKAGE, mciGroupPath.replace("/", "."), interfaceId, author, createDate, createDate, author, interfaceId);
|
||||
Files.writeString(mciIoDir.resolve(interfaceId + "_O.java"), mciResContent);
|
||||
|
||||
String converterContent = """
|
||||
@@ -412,15 +435,15 @@ public class ToolScaffolder {
|
||||
|
||||
import %s.dto.%sRequest;
|
||||
import %s.dto.%sResponse;
|
||||
import %s.infra.itrf.mci.%s.io.%s_I;
|
||||
import %s.infra.itrf.mci.%s.io.%s_O;
|
||||
import %s.%s.io.%s_I;
|
||||
import %s.%s.io.%s_O;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/**
|
||||
* @package %s.converter
|
||||
* @className %sLegacyConverter
|
||||
* @className %sConverter
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author %s
|
||||
* @create %s
|
||||
@@ -433,7 +456,7 @@ public class ToolScaffolder {
|
||||
* </pre>
|
||||
*/
|
||||
@Mapper(componentModel = "spring")
|
||||
public interface %sLegacyConverter {
|
||||
public interface %sConverter {
|
||||
|
||||
@Mapping(source = "phoneNumber", target = "phone")
|
||||
@Mapping(source = "message", target = "content")
|
||||
@@ -449,14 +472,14 @@ public class ToolScaffolder {
|
||||
bizPackage,
|
||||
bizPackage, baseName,
|
||||
bizPackage, baseName,
|
||||
BASE_PACKAGE, group.toLowerCase(), interfaceId,
|
||||
BASE_PACKAGE, group.toLowerCase(), interfaceId,
|
||||
BASE_PACKAGE, mciGroupPath.replace("/", "."), interfaceId,
|
||||
BASE_PACKAGE, mciGroupPath.replace("/", "."), interfaceId,
|
||||
bizPackage, baseName, author, createDate, createDate, author,
|
||||
baseName, interfaceId, baseName,
|
||||
baseName, interfaceId,
|
||||
baseName, interfaceId
|
||||
);
|
||||
Files.writeString(converterDir.resolve(baseName + "LegacyConverter.java"), converterContent);
|
||||
Files.writeString(converterDir.resolve(baseName + "Converter.java"), converterContent);
|
||||
|
||||
log.append("\n=========================================\n");
|
||||
log.append(" Scaffolding Complete! (Routing: " + routingType + ")\n");
|
||||
@@ -467,7 +490,47 @@ public class ToolScaffolder {
|
||||
log.append("[Response DTO] ").append(dtoDir.resolve(baseName + "Response.java")).append("\n");
|
||||
log.append("[MCI Request IO] ").append(mciIoDir.resolve(interfaceId + "_I.java")).append("\n");
|
||||
log.append("[MCI Response IO] ").append(mciIoDir.resolve(interfaceId + "_O.java")).append("\n");
|
||||
log.append("[MCI Converter] ").append(converterDir.resolve(baseName + "LegacyConverter.java")).append("\n");
|
||||
log.append("[MCI Converter] ").append(converterDir.resolve(baseName + "Converter.java")).append("\n");
|
||||
|
||||
if (!clientPrefixCap.isEmpty()) {
|
||||
String mciClientContent = """
|
||||
package %s.%s;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import io.shinhanlife.dap.lib.integration.mci.component.AxhubMciComponent;
|
||||
import io.shinhanlife.glow.communication.dto.Transfer;
|
||||
|
||||
/**
|
||||
* @package %s.%s
|
||||
* @className Mci%sClient
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author %s
|
||||
* @create %s
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* %s %s 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class Mci%sClient {
|
||||
private final AxhubMciComponent mci;
|
||||
|
||||
public Transfer<Object> callTo(String interfaceId, String dummy, Object mciReq, Class<Object> resType) throws Exception {
|
||||
return mci.callTo(interfaceId, dummy, mciReq, resType);
|
||||
}
|
||||
}
|
||||
""".formatted(
|
||||
BASE_PACKAGE, mciGroupPath.replace("/", "."),
|
||||
BASE_PACKAGE, mciGroupPath.replace("/", "."), clientPrefixCap, author, createDate, createDate, author, clientPrefixCap
|
||||
);
|
||||
Files.writeString(mciClientDir.resolve("Mci" + clientPrefixCap + "Client.java"), mciClientContent);
|
||||
log.append("[MCI Client] ").append(mciClientDir.resolve("Mci" + clientPrefixCap + "Client.java")).append("\n");
|
||||
}
|
||||
|
||||
} else {
|
||||
String legacyReqContent = """
|
||||
@@ -543,7 +606,7 @@ public class ToolScaffolder {
|
||||
|
||||
/**
|
||||
* @package %s.converter
|
||||
* @className %sLegacyConverter
|
||||
* @className %sConverter
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author %s
|
||||
* @create %s
|
||||
@@ -556,7 +619,7 @@ public class ToolScaffolder {
|
||||
* </pre>
|
||||
*/
|
||||
@Mapper(componentModel = "spring")
|
||||
public interface %sLegacyConverter {
|
||||
public interface %sConverter {
|
||||
|
||||
@Mapping(source = "phoneNumber", target = "phone")
|
||||
@Mapping(source = "message", target = "content")
|
||||
@@ -577,7 +640,7 @@ public class ToolScaffolder {
|
||||
bizPackage, baseName, author, createDate, createDate, author,
|
||||
baseName, baseName, baseName, baseName, baseName, baseName, baseName
|
||||
);
|
||||
Files.writeString(converterDir.resolve(baseName + "LegacyConverter.java"), converterContent);
|
||||
Files.writeString(converterDir.resolve(baseName + "Converter.java"), converterContent);
|
||||
|
||||
log.append("\n=========================================\n");
|
||||
log.append(" Scaffolding Complete! (Routing: " + routingType + ")\n");
|
||||
@@ -588,7 +651,7 @@ public class ToolScaffolder {
|
||||
log.append("[Response DTO] ").append(dtoDir.resolve(baseName + "Response.java")).append("\n");
|
||||
log.append("[Legacy Request DTO] ").append(legacyDtoDir.resolve(baseName + "LegacyRequest.java")).append("\n");
|
||||
log.append("[Legacy Response DTO] ").append(legacyDtoDir.resolve(baseName + "LegacyResponse.java")).append("\n");
|
||||
log.append("[Legacy Converter] ").append(converterDir.resolve(baseName + "LegacyConverter.java")).append("\n");
|
||||
log.append("[Legacy Converter] ").append(converterDir.resolve(baseName + "Converter.java")).append("\n");
|
||||
}
|
||||
log.append("\n Tip: ").append(interfaceId).append(" 목업 데이터를 mock-responses.json에 추가하세요.\n");
|
||||
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
package io.shinhanlife.dap.lib.validation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.lib.validation
|
||||
* @className McpToolNameValidator
|
||||
* @description Validates unique MCP function names across tool modules
|
||||
* @author 0986406
|
||||
* @create 2026.07.27
|
||||
* <pre>
|
||||
* ---------- revision history ----------
|
||||
* date author description
|
||||
* ---------- --------- ---------------------------
|
||||
* 2026.07.27 0986406 initial creation
|
||||
* </pre>
|
||||
*/
|
||||
public final class McpToolNameValidator {
|
||||
|
||||
private static final Pattern TOOL_NAME_PATTERN = Pattern.compile("\\bname\\s*=\\s*\\\"([^\\\"]+)\\\"");
|
||||
|
||||
private McpToolNameValidator() {
|
||||
}
|
||||
|
||||
public static void assertUnique(Path projectRoot) {
|
||||
Map<String, List<ToolDeclaration>> declarationsByName = new LinkedHashMap<>();
|
||||
|
||||
try (var modules = Files.list(projectRoot)) {
|
||||
modules.filter(Files::isDirectory)
|
||||
.filter(path -> path.getFileName().toString().startsWith("dap-tool-"))
|
||||
.filter(path -> !path.getFileName().toString().equals("dap-tool-core"))
|
||||
.sorted()
|
||||
.forEach(module -> collectDeclarations(module, declarationsByName));
|
||||
} catch (IOException exception) {
|
||||
throw new UncheckedIOException("Failed to scan MCP tool modules", exception);
|
||||
}
|
||||
|
||||
List<Map.Entry<String, List<ToolDeclaration>>> duplicates = declarationsByName.entrySet().stream()
|
||||
.filter(entry -> entry.getValue().size() > 1)
|
||||
.sorted(Map.Entry.comparingByKey())
|
||||
.toList();
|
||||
|
||||
if (!duplicates.isEmpty()) {
|
||||
throw new IllegalStateException(buildDuplicateMessage(duplicates));
|
||||
}
|
||||
}
|
||||
|
||||
private static void collectDeclarations(Path module, Map<String, List<ToolDeclaration>> declarationsByName) {
|
||||
Path sourceDirectory = module.resolve("src/main/java");
|
||||
if (!Files.isDirectory(sourceDirectory)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try (var sources = Files.walk(sourceDirectory)) {
|
||||
sources.filter(path -> path.toString().endsWith(".java"))
|
||||
.sorted()
|
||||
.forEach(source -> collectDeclarations(module.getFileName().toString(), source, declarationsByName));
|
||||
} catch (IOException exception) {
|
||||
throw new UncheckedIOException("Failed to scan module " + module.getFileName(), exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static void collectDeclarations(String moduleName, Path source,
|
||||
Map<String, List<ToolDeclaration>> declarationsByName) {
|
||||
String content;
|
||||
try {
|
||||
content = Files.readString(source);
|
||||
} catch (IOException exception) {
|
||||
throw new UncheckedIOException("Failed to read " + source, exception);
|
||||
}
|
||||
|
||||
int annotationOffset = content.indexOf("@McpFunction");
|
||||
while (annotationOffset >= 0) {
|
||||
int openingParenthesis = content.indexOf('(', annotationOffset);
|
||||
int closingParenthesis = findAnnotationEnd(content, openingParenthesis);
|
||||
if (openingParenthesis < 0 || closingParenthesis < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
Matcher matcher = TOOL_NAME_PATTERN.matcher(content.substring(openingParenthesis + 1, closingParenthesis));
|
||||
if (matcher.find()) {
|
||||
String toolName = matcher.group(1);
|
||||
int line = 1 + (int) content.substring(0, annotationOffset).chars().filter(character -> character == '\n').count();
|
||||
declarationsByName.computeIfAbsent(toolName, ignored -> new ArrayList<>())
|
||||
.add(new ToolDeclaration(moduleName, source, line));
|
||||
}
|
||||
annotationOffset = content.indexOf("@McpFunction", closingParenthesis + 1);
|
||||
}
|
||||
}
|
||||
|
||||
private static int findAnnotationEnd(String content, int openingParenthesis) {
|
||||
if (openingParenthesis < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int depth = 0;
|
||||
boolean inString = false;
|
||||
boolean escaped = false;
|
||||
for (int index = openingParenthesis; index < content.length(); index++) {
|
||||
char character = content.charAt(index);
|
||||
if (inString) {
|
||||
if (escaped) {
|
||||
escaped = false;
|
||||
} else if (character == '\\') {
|
||||
escaped = true;
|
||||
} else if (character == '\"') {
|
||||
inString = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (character == '\"') {
|
||||
inString = true;
|
||||
} else if (character == '(') {
|
||||
depth++;
|
||||
} else if (character == ')' && --depth == 0) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static String buildDuplicateMessage(List<Map.Entry<String, List<ToolDeclaration>>> duplicates) {
|
||||
StringBuilder message = new StringBuilder("Duplicate MCP tool name(s):");
|
||||
for (Map.Entry<String, List<ToolDeclaration>> duplicate : duplicates) {
|
||||
message.append("\n\n").append(duplicate.getKey());
|
||||
duplicate.getValue().stream()
|
||||
.sorted(Comparator.comparing(ToolDeclaration::moduleName).thenComparing(declaration -> declaration.source().toString()))
|
||||
.forEach(declaration -> message.append("\n- ")
|
||||
.append(declaration.moduleName())
|
||||
.append(": ")
|
||||
.append(declaration.source())
|
||||
.append(':').append(declaration.line()));
|
||||
}
|
||||
return message.toString();
|
||||
}
|
||||
|
||||
private record ToolDeclaration(String moduleName, Path source, int line) {
|
||||
}
|
||||
}
|
||||
@@ -190,11 +190,13 @@ public class BusinessToolController {
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 메서드 실행
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
Object methodResult = null;
|
||||
methodResult = targetMethod.invoke(targetBean, invokeArgument);
|
||||
if (targetMethod.getParameterCount() == 0) {
|
||||
methodResult = targetMethod.invoke(targetBean);
|
||||
} else {
|
||||
methodResult = targetMethod.invoke(targetBean, invokeArgument);
|
||||
}
|
||||
|
||||
long elapsed = System.currentTimeMillis() - startTime;
|
||||
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
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() {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package io.shinhanlife.dap.lib.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.networknt.schema.JsonSchema;
|
||||
import com.networknt.schema.JsonSchemaFactory;
|
||||
import com.networknt.schema.SpecVersion;
|
||||
import com.networknt.schema.ValidationMessage;
|
||||
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
||||
import io.shinhanlife.dap.lib.annotation.McpValidation;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.lib.util
|
||||
* @className JsonSchemaGeneratorTest
|
||||
* @description JSON Schema constraint generation test
|
||||
* @author 0986406
|
||||
* @create 2026.07.27
|
||||
* <pre>
|
||||
* ---------- revision history ----------
|
||||
* date author description
|
||||
* ---------- --------- ---------------------------
|
||||
* 2026.07.27 0986406 initial creation
|
||||
* </pre>
|
||||
*/
|
||||
class JsonSchemaGeneratorTest {
|
||||
|
||||
@Test
|
||||
void includesMcpParameterConstraintsInGeneratedSchema() {
|
||||
Map<String, Object> schema = JsonSchemaGenerator.generateSchema(ValidatedRequest.class);
|
||||
Map<String, Map<String, Object>> properties = properties(schema);
|
||||
|
||||
assertTrue(((List<String>) schema.get("required")).contains("phoneNumber"));
|
||||
assertTrue(((List<String>) schema.get("required")).contains("approvalStatus"));
|
||||
assertEquals("^01[0-9]{8,9}$", properties.get("phoneNumber").get("pattern"));
|
||||
assertEquals(1L, properties.get("amount").get("minimum"));
|
||||
assertEquals(List.of("APPROVE", "REJECT"), properties.get("approvalStatus").get("enum"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void includesNestedDtoConstraintsInGeneratedSchema() {
|
||||
Map<String, Object> schema = JsonSchemaGenerator.generateSchema(NestedRequest.class);
|
||||
Map<String, Object> childSchema = property(schema, "child");
|
||||
|
||||
assertEquals("object", childSchema.get("type"));
|
||||
assertTrue(required(childSchema).contains("businessDate"));
|
||||
assertEquals("^\\d{8}$", property(childSchema, "businessDate").get("pattern"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void includesNestedDtoSchemaForListItems() {
|
||||
Map<String, Object> schema = JsonSchemaGenerator.generateSchema(ListRequest.class);
|
||||
Map<String, Object> itemSchema = map(property(schema, "items").get("items"));
|
||||
|
||||
assertEquals("object", itemSchema.get("type"));
|
||||
assertTrue(required(itemSchema).contains("businessDate"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validatorRejectsInvalidNestedValue() throws Exception {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
JsonSchema schema = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7)
|
||||
.getSchema(objectMapper.writeValueAsString(JsonSchemaGenerator.generateSchema(NestedRequest.class)));
|
||||
Set<ValidationMessage> errors = schema.validate(objectMapper.valueToTree(Map.of(
|
||||
"child", Map.of("businessDate", "2026-07-28"))));
|
||||
|
||||
assertFalse(errors.isEmpty());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, Map<String, Object>> properties(Map<String, Object> schema) {
|
||||
return (Map<String, Map<String, Object>>) schema.get("properties");
|
||||
}
|
||||
|
||||
private Map<String, Object> property(Map<String, Object> schema, String name) {
|
||||
return properties(schema).get(name);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, Object> map(Object value) {
|
||||
return (Map<String, Object>) value;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<String> required(Map<String, Object> schema) {
|
||||
return (List<String>) schema.get("required");
|
||||
}
|
||||
|
||||
private static class ValidatedRequest {
|
||||
@McpParameter(description = "recipient phone number", required = true)
|
||||
@McpValidation(pattern = "^01[0-9]{8,9}$")
|
||||
private String phoneNumber;
|
||||
|
||||
@McpParameter(description = "issue amount", required = true)
|
||||
@McpValidation(minimum = 1)
|
||||
private Long amount;
|
||||
|
||||
@McpParameter(description = "approval result")
|
||||
@McpValidation(required = true, allowedValues = {"APPROVE", "REJECT"})
|
||||
private String approvalStatus;
|
||||
}
|
||||
|
||||
private static class NestedRequest {
|
||||
private NestedChild child;
|
||||
}
|
||||
|
||||
private static class ListRequest {
|
||||
private List<NestedChild> items;
|
||||
}
|
||||
|
||||
private static class NestedChild {
|
||||
@McpValidation(required = true, pattern = "^\\d{8}$")
|
||||
private String businessDate;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package io.shinhanlife.dap.lib.validation;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.lib.validation
|
||||
* @className McpToolNameValidatorTest
|
||||
* @description MCP tool name uniqueness validation test
|
||||
* @author 0986406
|
||||
* @create 2026.07.27
|
||||
* <pre>
|
||||
* ---------- revision history ----------
|
||||
* date author description
|
||||
* ---------- --------- ---------------------------
|
||||
* 2026.07.27 0986406 initial creation
|
||||
* </pre>
|
||||
*/
|
||||
class McpToolNameValidatorTest {
|
||||
|
||||
@TempDir
|
||||
Path temporaryRoot;
|
||||
|
||||
@Test
|
||||
void rejectsDuplicateMcpFunctionNamesAcrossToolModules() throws IOException {
|
||||
writeToolSource("dap-tool-first", "FirstTool.java", "first", "send_sms");
|
||||
writeToolSource("dap-tool-second", "SecondTool.java", "second", "send_sms");
|
||||
|
||||
IllegalStateException exception = assertThrows(IllegalStateException.class,
|
||||
() -> McpToolNameValidator.assertUnique(temporaryRoot));
|
||||
|
||||
assertTrue(exception.getMessage().contains("send_sms"));
|
||||
assertTrue(exception.getMessage().contains("dap-tool-first"));
|
||||
assertTrue(exception.getMessage().contains("dap-tool-second"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void acceptsCurrentProjectToolNames() {
|
||||
assertDoesNotThrow(() -> McpToolNameValidator.assertUnique(findProjectRoot()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void usesDomainSpecificNamesForCustomerBillingAndBondTools() throws IOException {
|
||||
Path root = findProjectRoot();
|
||||
|
||||
assertToolName(root, "dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/CustomerInfoUseCase.java",
|
||||
"customer_detail", "detail");
|
||||
assertToolName(root, "dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/BillingProcessUseCase.java",
|
||||
"billing_process", "process");
|
||||
assertToolName(root, "dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/BondIssueUseCase.java",
|
||||
"bond_issue", "issue");
|
||||
}
|
||||
|
||||
private void assertToolName(Path root, String relativePath, String expectedName, String legacyName) throws IOException {
|
||||
String source = Files.readString(root.resolve(relativePath));
|
||||
assertTrue(source.contains("name = \"" + expectedName + "\""));
|
||||
assertFalse(source.contains("name = \"" + legacyName + "\""));
|
||||
}
|
||||
|
||||
private void writeToolSource(String moduleName, String fileName, String className, String toolName) throws IOException {
|
||||
Path source = temporaryRoot.resolve(moduleName).resolve("src/main/java/example").resolve(fileName);
|
||||
Files.createDirectories(source.getParent());
|
||||
Files.writeString(source, """
|
||||
package example;
|
||||
|
||||
class %s {
|
||||
@McpFunction(displayName = "%s", name = "%s", description = "test")
|
||||
void execute() { }
|
||||
}
|
||||
""".formatted(className, className, toolName));
|
||||
}
|
||||
|
||||
private Path findProjectRoot() {
|
||||
Path current = Path.of("").toAbsolutePath();
|
||||
while (current != null && !Files.exists(current.resolve("settings.gradle"))) {
|
||||
current = current.getParent();
|
||||
}
|
||||
if (current == null) {
|
||||
throw new IllegalStateException("Could not locate Gradle project root");
|
||||
}
|
||||
return current;
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package io.shinhanlife.dap.mcc.sample.converter;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.mcc.sample.converter
|
||||
* @className SampleLegacyConverterTest
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 0986406 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
import io.shinhanlife.dap.mcc.sample.dto.SampleAiReqDto;
|
||||
import io.shinhanlife.dap.mcc.sample.dto.SampleLegacyReqDto;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
public class SampleLegacyConverterTest {
|
||||
|
||||
// MapStruct가 자동 생성한 구현체를 가져와서 테스트합니다. (IDE 에러 방지를 위해 INSTANCE 참조)
|
||||
private final SampleLegacyConverter sampleLegacyConverter = SampleLegacyConverter.INSTANCE;
|
||||
|
||||
@Test
|
||||
@DisplayName("AI 파라미터 DTO가 MCI DTO로 정확히 매핑되는지 테스트")
|
||||
public void testAiToMciMapping() {
|
||||
// given: AI Agent가 준 깔끔한 형태의 파라미터 생성
|
||||
SampleAiReqDto aiDto = SampleAiReqDto.builder()
|
||||
.userId("HONG_GILDONG")
|
||||
.actionType("SOATM0100R")
|
||||
.extraInfo("휴가신청내역 조회")
|
||||
.build();
|
||||
|
||||
// when: MapStruct 자동 생성 매퍼를 통해 1줄로 변환
|
||||
SampleLegacyReqDto mciDto = sampleLegacyConverter.toLegacyRequest(aiDto);
|
||||
|
||||
// then: 콘솔에 결과 출력 및 값 검증
|
||||
System.out.println("====== MapStruct 변환 테스트 결과 ======");
|
||||
System.out.println(" [변환 전] AI DTO : " + aiDto);
|
||||
System.out.println(" [변환 후] Legacy DTO: " + mciDto);
|
||||
System.out.println("===============================================");
|
||||
|
||||
assertNotNull(mciDto, "변환된 객체는 null이 아니어야 합니다.");
|
||||
assertEquals("HONG_GILDONG", mciDto.getCustomerId(), "userId -> customerId 매핑 성공");
|
||||
assertEquals("SOATM0100R", mciDto.getInterfaceId(), "actionType -> interfaceId 매핑 성공");
|
||||
assertEquals("휴가신청내역 조회", mciDto.getRequestDetails(), "extraInfo -> requestDetails 매핑 성공");
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,8 @@
|
||||
FROM eclipse-temurin:21-jdk-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY gradlew .
|
||||
COPY gradle gradle
|
||||
COPY build.gradle settings.gradle ./
|
||||
|
||||
COPY dap-tool-core dap-tool-core
|
||||
COPY dap-tool-oth dap-tool-oth
|
||||
RUN chmod +x gradlew
|
||||
RUN ./gradlew clean :dap-tool-oth:build -x test
|
||||
|
||||
FROM eclipse-temurin:21-jre-alpine
|
||||
WORKDIR /app
|
||||
RUN apk add --no-cache tzdata
|
||||
ENV TZ=Asia/Seoul
|
||||
COPY --from=builder /app/dap-tool-oth/build/libs/*-SNAPSHOT.jar app.jar
|
||||
COPY dap-tool-oth/build/libs/*-SNAPSHOT.jar app.jar
|
||||
EXPOSE 8084
|
||||
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package io.shinhanlife.dap.mcc.biz.cmm.converter;
|
||||
|
||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeRequest;
|
||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeResponse;
|
||||
import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_I;
|
||||
import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_O;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.mcc.biz.cmm.converter
|
||||
* @className MetaCommonCodeConverter
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 09863409
|
||||
* @create 2026.07.29
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.07.29 09863409 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Mapper(componentModel = "spring")
|
||||
public interface MetaCommonCodeConverter {
|
||||
@Mapping(target = "csNo", source = "groupCode", defaultValue = "GRP_COMM_CD")
|
||||
CLCNNB00001_I toLegacyRequest(MetaCommonCodeRequest req);
|
||||
|
||||
@Mapping(target = "codeList", ignore = true)
|
||||
MetaCommonCodeResponse toResponse(CLCNNB00001_O mciRes);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package io.shinhanlife.dap.mcc.biz.cmm.converter;
|
||||
|
||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableRequest;
|
||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableResponse;
|
||||
import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_I;
|
||||
import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_O;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.mcc.biz.cmm.converter
|
||||
* @className MetaTableConverter
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 09863409
|
||||
* @create 2026.07.29
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.07.29 09863409 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Mapper(componentModel = "spring")
|
||||
public interface MetaTableConverter {
|
||||
@Mapping(target = "csNo", source = "tableName", defaultValue = "TB_META_BAS")
|
||||
CLCNNB00001_I toLegacyRequest(MetaTableRequest req);
|
||||
|
||||
@Mapping(target = "tableList", ignore = true)
|
||||
MetaTableResponse toResponse(CLCNNB00001_O mciRes);
|
||||
}
|
||||
@@ -16,10 +16,12 @@ package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
||||
* </pre>
|
||||
*/
|
||||
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
||||
import io.shinhanlife.dap.lib.annotation.McpValidation;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class BalanceRequest {
|
||||
@McpParameter(description = "고객의 계좌번호 (- 제외)", required = true)
|
||||
@McpValidation(pattern = "\\S")
|
||||
private String accountNumber;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
||||
import io.shinhanlife.dap.lib.annotation.McpValidation;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@@ -30,8 +31,10 @@ import lombok.Setter;
|
||||
public class BillingProcessRequest {
|
||||
|
||||
@McpParameter(description = "처리할 청구 접수 번호", required = true)
|
||||
@McpValidation(pattern = "\\S")
|
||||
private String billingId;
|
||||
|
||||
@McpParameter(description = "심사 승인 여부 (예: APPROVE, REJECT)")
|
||||
@McpValidation(required = true, allowedValues = {"APPROVE", "REJECT"})
|
||||
private String approvalStatus;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
||||
import io.shinhanlife.dap.lib.annotation.McpValidation;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@@ -30,8 +31,10 @@ import lombok.Setter;
|
||||
public class BondIssueRequest {
|
||||
|
||||
@McpParameter(description = "발행할 디지털 증권 금액", required = true)
|
||||
@McpValidation(minimum = 1)
|
||||
private Long amount;
|
||||
|
||||
@McpParameter(description = "발행 대상 계좌 번호", required = true)
|
||||
@McpValidation(pattern = "\\S")
|
||||
private String targetAccount;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.mcc.biz.cmm.dto
|
||||
* @className MetaCommonCodeRequest
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 09863409
|
||||
* @create 2026.07.29
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.07.29 09863409 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class MetaCommonCodeRequest {
|
||||
@McpParameter(description = "통합코드 그룹 ID (예: GRP_SYS_01, GRP_COMM_CD)", required = false)
|
||||
private String groupCode;
|
||||
|
||||
@McpParameter(description = "코드명 검색 키워드 (예: 사용, 상태)", required = false)
|
||||
private String codeName;
|
||||
|
||||
@McpParameter(description = "사용여부 (예: Y, N)", required = false)
|
||||
private String useYn;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.mcc.biz.cmm.dto
|
||||
* @className MetaCommonCodeResponse
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 09863409
|
||||
* @create 2026.07.29
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.07.29 09863409 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class MetaCommonCodeResponse {
|
||||
private List<MetaCommonCodeItem> codeList;
|
||||
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public static class MetaCommonCodeItem {
|
||||
private String groupCode;
|
||||
private String code;
|
||||
private String codeName;
|
||||
private String codeDesc;
|
||||
private Integer sortSeq;
|
||||
private String useYn;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.mcc.biz.cmm.dto
|
||||
* @className MetaTableRequest
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 09863409
|
||||
* @create 2026.07.29
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.07.29 09863409 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class MetaTableRequest {
|
||||
@McpParameter(description = "테이블 물리명 키워드 (예: TB_CUST_BAS, TB_CONT)", required = false)
|
||||
private String tableName;
|
||||
|
||||
@McpParameter(description = "테이블 논리명(한글) 키워드 (예: 고객기본, 계약)", required = false)
|
||||
private String tableLogicalName;
|
||||
|
||||
@McpParameter(description = "스키마/소유자명 (예: DAPADM, SHLOWN)", required = false)
|
||||
private String owner;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.mcc.biz.cmm.dto
|
||||
* @className MetaTableResponse
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 09863409
|
||||
* @create 2026.07.29
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.07.29 09863409 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class MetaTableResponse {
|
||||
private List<MetaTableItem> tableList;
|
||||
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public static class MetaTableItem {
|
||||
private String owner;
|
||||
private String tableName;
|
||||
private String tableLogicalName;
|
||||
private String tableDesc;
|
||||
private Integer columnCount;
|
||||
private Long rowCount;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
||||
import io.shinhanlife.dap.lib.annotation.McpValidation;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@@ -30,8 +31,10 @@ import lombok.Setter;
|
||||
public class VacationRegisterRequest {
|
||||
|
||||
@McpParameter(description = "연차를 등록할 사원 번호", required = true)
|
||||
@McpValidation(pattern = "\\S")
|
||||
private String employeeId;
|
||||
|
||||
@McpParameter(description = "휴가 일자 (YYYY-MM-DD 형식)", required = true)
|
||||
@McpValidation(pattern = "^\\d{4}-\\d{2}-\\d{2}$")
|
||||
private String date;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,6 @@ import io.shinhanlife.dap.mcc.biz.cmm.dto.*;
|
||||
public interface BillingProcessUseCase {
|
||||
Object getStatus(BillingStatusRequest req);
|
||||
|
||||
@McpFunction(register = false, displayName = "process 툴", name = "process", description = "청구 처리", prompt = "현재 접수된 청구건에 대한 심사 처리를 진행해.", mappingId = "BILL_002")
|
||||
@McpFunction(register = false, displayName = "process 툴", name = "billing_process", description = "청구 처리", prompt = "현재 접수된 청구건에 대한 심사 처리를 진행해.", mappingId = "BILL_002")
|
||||
Object processBilling(BillingProcessRequest data);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,6 @@ import io.shinhanlife.dap.mcc.biz.cmm.dto.*;
|
||||
public interface BondIssueUseCase {
|
||||
Object check(BondCheckRequest req);
|
||||
|
||||
@McpFunction(displayName = "issue 툴", name = "issue", register = false, description = "증권 발행 테스트1", prompt = "디지털 증권 발행 프로세스를 실행해.", mappingId = "BOND_002")
|
||||
@McpFunction(displayName = "issue 툴", name = "bond_issue", register = false, description = "증권 발행 테스트1", prompt = "디지털 증권 발행 프로세스를 실행해.", mappingId = "BOND_002")
|
||||
Object issue(BondIssueRequest data);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,6 @@ import io.shinhanlife.dap.mcc.biz.cmm.dto.*;
|
||||
public interface CustomerInfoUseCase {
|
||||
Object getGrade(CustomerGradeRequest req);
|
||||
|
||||
@McpFunction(register = false, displayName = "detail 툴", name = "detail", description = "고객상세 정보 조회", prompt = "이 고객의 상세 기본정보(주소, 연락처 등)를 알려줘.", mappingId = "CRM_002")
|
||||
@McpFunction(register = false, displayName = "detail 툴", name = "customer_detail", description = "고객상세 정보 조회", prompt = "이 고객의 상세 기본정보(주소, 연락처 등)를 알려줘.", mappingId = "CRM_002")
|
||||
Object getDetail(CustomerDetailRequest data);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
||||
|
||||
import io.shinhanlife.dap.lib.annotation.McpFunction;
|
||||
import io.shinhanlife.dap.lib.annotation.McpTool;
|
||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeRequest;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.mcc.biz.cmm.usecase
|
||||
* @className MetaCommonCodeUseCase
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 09863409
|
||||
* @create 2026.07.29
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.07.29 09863409 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@McpTool(
|
||||
routingType = "MCI",
|
||||
categoryKey = "cmm"
|
||||
)
|
||||
public interface MetaCommonCodeUseCase {
|
||||
@McpFunction(
|
||||
displayName = "메타 통합코드 조회 툴",
|
||||
name = "metaCommonCode",
|
||||
description = "메타 통합코드 목록을 조회해줘",
|
||||
prompt = "메타 통합코드 목록을 조회해줘",
|
||||
mappingId = "CLCNNB00001",
|
||||
register = false,
|
||||
requiresApproval = false,
|
||||
openWorldHint = true
|
||||
)
|
||||
Object execute(MetaCommonCodeRequest req);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
||||
|
||||
import io.shinhanlife.dap.lib.annotation.McpFunction;
|
||||
import io.shinhanlife.dap.lib.annotation.McpTool;
|
||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableRequest;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.mcc.biz.cmm.usecase
|
||||
* @className MetaTableUseCase
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 09863409
|
||||
* @create 2026.07.29
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.07.29 09863409 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@McpTool(
|
||||
routingType = "MCI",
|
||||
categoryKey = "cmm"
|
||||
)
|
||||
public interface MetaTableUseCase {
|
||||
@McpFunction(
|
||||
displayName = "메타 테이블 조회 툴",
|
||||
name = "metaTable",
|
||||
description = "메타 테이블 정보 목록을 조회해줘",
|
||||
prompt = "메타 테이블 정보 목록을 조회해줘",
|
||||
mappingId = "CLCNNB00001",
|
||||
register = false,
|
||||
requiresApproval = false,
|
||||
openWorldHint = true
|
||||
)
|
||||
Object execute(MetaTableRequest req);
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import io.shinhanlife.dap.lib.annotation.McpTool;
|
||||
import io.shinhanlife.dap.lib.annotation.McpFunction;
|
||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@McpTool(
|
||||
routingType = "HTTP",
|
||||
categoryKey = "cmm"
|
||||
@@ -15,5 +17,5 @@ public interface TemplateUtilityUseCase {
|
||||
description = "요청한 템플릿(엑셀, 워드 등) 파일(양식)을 다운로드 받을 수 있는 시스템 URL을 반환합니다. AI는 이 URL을 사용자에게 마크다운 링크 형태로 제공해야 합니다.",
|
||||
prompt = "요청하신 템플릿 양식 파일 다운로드 URL은 다음과 같습니다. 클릭하여 다운로드하세요:"
|
||||
)
|
||||
java.util.Map<String, Object> getTemplateFileUrl(TemplateDownloadRequest req);
|
||||
Map<String, Object> getTemplateFileUrl(TemplateDownloadRequest req);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl;
|
||||
|
||||
import io.shinhanlife.dap.mcc.biz.cmm.converter.MetaCommonCodeConverter;
|
||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeRequest;
|
||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeResponse;
|
||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeResponse.MetaCommonCodeItem;
|
||||
import io.shinhanlife.dap.mcc.biz.cmm.usecase.MetaCommonCodeUseCase;
|
||||
import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.MciCfpaClient;
|
||||
import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_I;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl
|
||||
* @className MetaCommonCodeUseCaseImpl
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 09863409
|
||||
* @create 2026.07.29
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.07.29 09863409 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MetaCommonCodeUseCaseImpl implements MetaCommonCodeUseCase {
|
||||
|
||||
private final MciCfpaClient mci;
|
||||
private final MetaCommonCodeConverter converter;
|
||||
|
||||
@Override
|
||||
public Object execute(MetaCommonCodeRequest req) {
|
||||
log.info("[MCI Tool] {} 요청 수신. 파라미터: {}", "metaCommonCode", req);
|
||||
try {
|
||||
CLCNNB00001_I mciRequest = converter.toLegacyRequest(req);
|
||||
|
||||
Object mciResponse = mci.callCfpa0001(mciRequest);
|
||||
log.info("[MCI Tool] CLCNNB00001 MCI call completed. Returning response status: {}",
|
||||
mciResponse != null);
|
||||
|
||||
// 현업 테스트용으로 MCI 통신을 바이패스하고 하드코딩된 메타 통합코드 샘플 결과를 반환합니다.
|
||||
MetaCommonCodeResponse res = new MetaCommonCodeResponse();
|
||||
List<MetaCommonCodeItem> list = new ArrayList<>();
|
||||
|
||||
MetaCommonCodeItem item1 = new MetaCommonCodeItem();
|
||||
item1.setGroupCode(req.getGroupCode() != null ? req.getGroupCode() : "GRP_COMM_CD");
|
||||
item1.setCode("CD001");
|
||||
item1.setCodeName("진행중");
|
||||
item1.setCodeDesc("SR 요청 처리 진행 중 상태");
|
||||
item1.setSortSeq(1);
|
||||
item1.setUseYn("Y");
|
||||
list.add(item1);
|
||||
|
||||
MetaCommonCodeItem item2 = new MetaCommonCodeItem();
|
||||
item2.setGroupCode(req.getGroupCode() != null ? req.getGroupCode() : "GRP_COMM_CD");
|
||||
item2.setCode("CD002");
|
||||
item2.setCodeName("완료");
|
||||
item2.setCodeDesc("SR 요청 처리 완료 상태");
|
||||
item2.setSortSeq(2);
|
||||
item2.setUseYn("Y");
|
||||
list.add(item2);
|
||||
|
||||
MetaCommonCodeItem item3 = new MetaCommonCodeItem();
|
||||
item3.setGroupCode(req.getGroupCode() != null ? req.getGroupCode() : "GRP_COMM_CD");
|
||||
item3.setCode("CD003");
|
||||
item3.setCodeName("보류");
|
||||
item3.setCodeDesc("SR 요청 처리 일시 보류 상태");
|
||||
item3.setSortSeq(3);
|
||||
item3.setUseYn("N");
|
||||
list.add(item3);
|
||||
|
||||
res.setCodeList(list);
|
||||
|
||||
return res;
|
||||
} catch (Exception e) {
|
||||
log.error("[MCI Tool] 연동 중 오류 발생: {}", e.getMessage(), e);
|
||||
return Map.of("status", "ERROR", "message", e.getMessage() != null ? e.getMessage() : "Unknown error");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl;
|
||||
|
||||
import io.shinhanlife.dap.mcc.biz.cmm.converter.MetaTableConverter;
|
||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableRequest;
|
||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableResponse;
|
||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableResponse.MetaTableItem;
|
||||
import io.shinhanlife.dap.mcc.biz.cmm.usecase.MetaTableUseCase;
|
||||
import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.MciCfpaClient;
|
||||
import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_I;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl
|
||||
* @className MetaTableUseCaseImpl
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 09863409
|
||||
* @create 2026.07.29
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.07.29 09863409 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MetaTableUseCaseImpl implements MetaTableUseCase {
|
||||
|
||||
private final MciCfpaClient mci;
|
||||
private final MetaTableConverter converter;
|
||||
|
||||
@Override
|
||||
public Object execute(MetaTableRequest req) {
|
||||
log.info("[MCI Tool] {} 요청 수신. 파라미터: {}", "metaTable", req);
|
||||
try {
|
||||
CLCNNB00001_I mciRequest = converter.toLegacyRequest(req);
|
||||
|
||||
Object mciResponse = mci.callCfpa0001(mciRequest);
|
||||
log.info("[MCI Tool] CLCNNB00001 MCI call completed. Returning response status: {}",
|
||||
mciResponse != null);
|
||||
|
||||
// 현업 테스트용으로 MCI 통신을 바이패스하고 하드코딩된 메타 테이블 샘플 결과를 반환합니다.
|
||||
MetaTableResponse res = new MetaTableResponse();
|
||||
List<MetaTableItem> list = new ArrayList<>();
|
||||
|
||||
MetaTableItem item1 = new MetaTableItem();
|
||||
item1.setOwner(req.getOwner() != null ? req.getOwner() : "DAPADM");
|
||||
item1.setTableName(req.getTableName() != null ? req.getTableName() : "TB_CUST_BAS");
|
||||
item1.setTableLogicalName("고객기본정보");
|
||||
item1.setTableDesc("고객 기본 프로필 및 인적사항 관리 테이블");
|
||||
item1.setColumnCount(35);
|
||||
item1.setRowCount(1250000L);
|
||||
list.add(item1);
|
||||
|
||||
MetaTableItem item2 = new MetaTableItem();
|
||||
item2.setOwner(req.getOwner() != null ? req.getOwner() : "DAPADM");
|
||||
item2.setTableName("TB_CONT_MCD");
|
||||
item2.setTableLogicalName("계약주계약정보");
|
||||
item2.setTableDesc("보험 계약 주계약 상세 원장 테이블");
|
||||
item2.setColumnCount(58);
|
||||
item2.setRowCount(3400000L);
|
||||
list.add(item2);
|
||||
|
||||
MetaTableItem item3 = new MetaTableItem();
|
||||
item3.setOwner(req.getOwner() != null ? req.getOwner() : "DAPADM");
|
||||
item3.setTableName("TB_CLAIM_DTL");
|
||||
item3.setTableLogicalName("청구접수상세");
|
||||
item3.setTableDesc("보험금 청구 접수 건별 내역 테이블");
|
||||
item3.setColumnCount(42);
|
||||
item3.setRowCount(890000L);
|
||||
list.add(item3);
|
||||
|
||||
res.setTableList(list);
|
||||
|
||||
return res;
|
||||
} catch (Exception e) {
|
||||
log.error("[MCI Tool] 연동 중 오류 발생: {}", e.getMessage(), e);
|
||||
return Map.of("status", "ERROR", "message", e.getMessage() != null ? e.getMessage() : "Unknown error");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package io.shinhanlife.dap.mcc.biz.oth.converter;
|
||||
|
||||
import io.shinhanlife.dap.mcc.biz.oth.dto.Onnba3011Request;
|
||||
import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_I;
|
||||
import org.mapstruct.Mapper;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.converter
|
||||
* @className Onnba3011Converter
|
||||
* @description Converts ONNBA tool input into the CLCNNB00001 MCI payload
|
||||
* @author 0986406
|
||||
* @create 2026.07.27
|
||||
* <pre>
|
||||
* ---------- revision history ----------
|
||||
* date author description
|
||||
* ---------- --------- ---------------------------
|
||||
* 2026.07.27 0986406 initial creation
|
||||
* </pre>
|
||||
*/
|
||||
@Mapper(componentModel = "spring")
|
||||
public interface Onnba3011Converter {
|
||||
|
||||
CLCNNB00001_I toMciRequest(Onnba3011Request source);
|
||||
|
||||
CLCNNB00001_I.UnfcPrbuIrcoAdduDto toUnfcPrbuIrcoAddu(
|
||||
Onnba3011Request.UnfcPrbuIrcoAdduDto source);
|
||||
|
||||
CLCNNB00001_I.SucoIspaBasDto toSucoIspaBas(Onnba3011Request.SucoIspaBasDto source);
|
||||
}
|
||||
@@ -5,6 +5,6 @@ import io.shinhanlife.dap.lib.annotation.McpFunction;
|
||||
import io.shinhanlife.dap.mcc.biz.oth.dto.*;
|
||||
|
||||
@McpTool(routingType = "MCI", categoryKey = "oth")
|
||||
public interface OnnbaMciToolUseCase {
|
||||
public interface Onnba3011UseCase {
|
||||
Object callOnnba3011(Onnba3011Request req);
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
package io.shinhanlife.dap.mcc.biz.oth.usecase.impl;
|
||||
|
||||
import io.shinhanlife.dap.mcc.biz.oth.converter.Onnba3011Converter;
|
||||
import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.MciCfpaClient;
|
||||
import io.shinhanlife.dap.mcc.biz.oth.usecase.OnnbaMciToolUseCase;
|
||||
import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_I;
|
||||
import io.shinhanlife.dap.mcc.biz.oth.usecase.Onnba3011UseCase;
|
||||
import io.shinhanlife.dap.mcc.biz.oth.dto.Onnba3011Request;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -24,13 +26,13 @@ import org.springframework.stereotype.Service;
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class OnnbaMciToolUseCaseImpl implements OnnbaMciToolUseCase {
|
||||
public class Onnba3011UseCaseImpl implements Onnba3011UseCase {
|
||||
|
||||
private static final String INTERFACE_CODE_3011 = "CLCNNB00001";
|
||||
|
||||
// 공통 MCI Client 주입
|
||||
private final MciCfpaClient mciCfpaClient;
|
||||
|
||||
private final Onnba3011Converter onnba3011Converter;
|
||||
/**
|
||||
* AI Agent가 호출하게 될 메서드입니다.
|
||||
* @McpFunction 어노테이션 하나로 AI 도구로 자동 노출 및 라우팅됩니다.
|
||||
@@ -42,7 +44,8 @@ public class OnnbaMciToolUseCaseImpl implements OnnbaMciToolUseCase {
|
||||
|
||||
try {
|
||||
// MciCfpaClient를 통한 호출
|
||||
Object response = mciCfpaClient.callCfpa0001(req);
|
||||
CLCNNB00001_I mciRequest = onnba3011Converter.toMciRequest(req);
|
||||
Object response = mciCfpaClient.callCfpa0001(mciRequest);
|
||||
|
||||
log.info("[MCI Tool] Glow 기반 MCI 연동 성공.");
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package io.shinhanlife.dap.mcc.biz.smp.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.mcc.biz.smp.dto
|
||||
* @className TeamMemberRequest
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author jade
|
||||
* @create 2026.07.29
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.07.29 jade 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class TeamMemberRequest {
|
||||
@McpParameter(description = "조회할 팀 이름 (예: AX, MCP, TOOL, 전체 등)", required = false)
|
||||
private String teamName;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package io.shinhanlife.dap.mcc.biz.smp.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.mcc.biz.smp.dto
|
||||
* @className TeamMemberResponse
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author jade
|
||||
* @create 2026.07.29
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.07.29 jade 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class TeamMemberResponse {
|
||||
private String result;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package io.shinhanlife.dap.mcc.biz.smp.usecase;
|
||||
|
||||
import io.shinhanlife.dap.lib.annotation.McpFunction;
|
||||
import io.shinhanlife.dap.lib.annotation.McpTool;
|
||||
import io.shinhanlife.dap.mcc.biz.smp.dto.TeamMemberRequest;
|
||||
|
||||
@McpTool(
|
||||
routingType = "DIRECT",
|
||||
categoryKey = "smp"
|
||||
)
|
||||
public interface TeamMemberUseCase {
|
||||
@McpFunction(
|
||||
displayName = "신한라이프 MCP, TOOL 파트 구성원 조회",
|
||||
name = "get_smp_members",
|
||||
description = "신한라이프 MCP, TOOL 파트 구성원을 조회합니다.",
|
||||
prompt = "신한라이프 MCP, TOOL 파트 구성원을 조회해 줘. (주의: 응답 시 ** 등 마크다운 기호를 절대 사용하지 말고 평문으로만 출력해 줘)",
|
||||
mappingId = "DIRECT0001",
|
||||
register = false,
|
||||
requiresApproval = false,
|
||||
openWorldHint = true
|
||||
)
|
||||
Object execute(TeamMemberRequest req);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package io.shinhanlife.dap.mcc.biz.smp.usecase.impl;
|
||||
|
||||
import io.shinhanlife.dap.mcc.biz.smp.dto.TeamMemberRequest;
|
||||
import io.shinhanlife.dap.mcc.biz.smp.dto.TeamMemberResponse;
|
||||
import io.shinhanlife.dap.mcc.biz.smp.usecase.TeamMemberUseCase;
|
||||
import io.shinhanlife.dap.mcc.usecase.AbstractMcpToolUseCase;
|
||||
import org.springframework.stereotype.Service;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.mcc.biz.smp.usecase.impl
|
||||
* @className TeamMemberUseCaseImpl
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author jade
|
||||
* @create 2026.07.29
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.07.29 jade 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class TeamMemberUseCaseImpl extends AbstractMcpToolUseCase implements TeamMemberUseCase {
|
||||
|
||||
@Override
|
||||
public Object execute(TeamMemberRequest req) {
|
||||
log.info("[A01] 신한라이프 MCP, TOOL 파트 구성원 조회 요청: {}", req);
|
||||
|
||||
String filter = req != null && req.getTeamName() != null ? req.getTeamName().toUpperCase() : "전체";
|
||||
|
||||
String resultString = "";
|
||||
if (filter.contains("AX")) {
|
||||
resultString += "ax 추진팀 박세진 프로\n";
|
||||
} else if (filter.contains("MCP") && !filter.contains("TOOL")) {
|
||||
resultString += "MCP 팀은 고석민 수석 , 장효원 책임\n";
|
||||
} else if (filter.contains("TOOL") && !filter.contains("MCP")) {
|
||||
resultString += "TOOL 팀은 김형식 수석 ,김영진 책임 , 김도겸 대리 , 이주희 선임 , 문주현 선임 , 이보람 대리 , 박수빈 대리\n";
|
||||
} else {
|
||||
resultString += "ax 추진팀 박세진 프로\n" +
|
||||
"MCP & TOOL 팀 담당자는 윤희준 이사\n" +
|
||||
"MCP 팀은 고석민 수석 , 장효원 책임\n" +
|
||||
"TOOL 팀은 김형식 수석 ,김영진 책임 , 김도겸 대리 , 이주희 선임 , 문주현 선임 , 이보람 대리 , 박수빈 대리";
|
||||
}
|
||||
|
||||
TeamMemberResponse res = new TeamMemberResponse();
|
||||
res.setResult(resultString.trim());
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package io.shinhanlife.dap.mcc.biz.sol.converter;
|
||||
|
||||
import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqListRequest;
|
||||
import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqListResponse;
|
||||
import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io.SOLG00000001_I;
|
||||
import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io.SOLG00000001_O;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.mcc.biz.sol.converter
|
||||
* @className SolReqListConverter
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author jade
|
||||
* @create 2026.07.29
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.07.29 jade 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Mapper(componentModel = "spring")
|
||||
public interface SolReqListConverter {
|
||||
@Mapping(source = "status", target = "reqStatus", defaultValue = "진행중")
|
||||
@Mapping(source = "period", target = "reqPeriod", defaultValue = "최근 3개월")
|
||||
@Mapping(source = "target", target = "reqTarget", defaultValue = "나의 업무")
|
||||
SOLG00000001_I toLegacyRequest(SolReqListRequest req);
|
||||
|
||||
SolReqListResponse toResponse(SOLG00000001_O mciRes);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package io.shinhanlife.dap.mcc.biz.sol.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.mcc.biz.sol.dto
|
||||
* @className SolReqListRequest
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author jade
|
||||
* @create 2026.07.29
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.07.29 jade 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class SolReqListRequest {
|
||||
@McpParameter(description = "진행상태 (예: 진행중, 완료 등)", required = false)
|
||||
private String status;
|
||||
|
||||
@McpParameter(description = "조회기간 (예: 1개월, 3개월 등)", required = false)
|
||||
private String period;
|
||||
|
||||
@McpParameter(description = "조회대상 (예: 나의 업무, 전체 등)", required = false)
|
||||
private String target;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package io.shinhanlife.dap.mcc.biz.sol.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.mcc.biz.sol.dto
|
||||
* @className SolReqListResponse
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author jade
|
||||
* @create 2026.07.29
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.07.29 jade 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class SolReqListResponse {
|
||||
private List<SolReqListItem> reqList;
|
||||
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public static class SolReqListItem {
|
||||
private String srId;
|
||||
private String srName;
|
||||
private String process;
|
||||
private String devStage;
|
||||
private String appName;
|
||||
private String requester;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package io.shinhanlife.dap.mcc.biz.sol.usecase;
|
||||
|
||||
import io.shinhanlife.dap.lib.annotation.McpFunction;
|
||||
import io.shinhanlife.dap.lib.annotation.McpTool;
|
||||
import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqListRequest;
|
||||
|
||||
@McpTool(
|
||||
routingType = "MCI",
|
||||
categoryKey = "sol"
|
||||
)
|
||||
public interface SolReqListUseCase {
|
||||
@McpFunction(
|
||||
displayName = "SolReqList 툴",
|
||||
name = "solReqList",
|
||||
description = "SOL 의뢰서 목록 조회해줘",
|
||||
prompt = "SOL 의뢰서 목록 조회해줘",
|
||||
mappingId = "SOLG00000001",
|
||||
register = false,
|
||||
requiresApproval = false,
|
||||
openWorldHint = true
|
||||
)
|
||||
Object execute(SolReqListRequest req);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package io.shinhanlife.dap.mcc.biz.sol.usecase.impl;
|
||||
|
||||
import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqListRequest;
|
||||
import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqListResponse;
|
||||
import io.shinhanlife.dap.mcc.biz.sol.usecase.SolReqListUseCase;
|
||||
import io.shinhanlife.glow.communication.dto.Transfer;
|
||||
import org.springframework.stereotype.Service;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import io.shinhanlife.dap.mcc.biz.sol.converter.SolReqListConverter;
|
||||
import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io.SOLG00000001_I;
|
||||
import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io.SOLG00000001_O;
|
||||
import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.MciNclgClient;
|
||||
import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqListResponse.SolReqListItem;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.mcc.biz.sol.usecase.impl
|
||||
* @className SolReqListUseCaseImpl
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author jade
|
||||
* @create 2026.07.29
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.07.29 jade 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SolReqListUseCaseImpl implements SolReqListUseCase {
|
||||
|
||||
private final MciNclgClient mci;
|
||||
private final SolReqListConverter converter;
|
||||
|
||||
@Override
|
||||
public Object execute(SolReqListRequest req) {
|
||||
log.info("[MCI Tool] {} 요청 수신. 파라미터: {}", "solReqList", req);
|
||||
try {
|
||||
SOLG00000001_I mciRequest = converter.toLegacyRequest(req);
|
||||
Transfer<SOLG00000001_O> mciResponse = mci.callTo(
|
||||
"SOLG00000001", "SOLG00000001", mciRequest, SOLG00000001_O.class);
|
||||
log.info("[MCI Tool] SOLG00000001 MCI call completed. Returning dummy response: {}",
|
||||
mciResponse != null);
|
||||
// 현업 테스트용으로 MCI 통신을 바이패스하고 하드코딩된 결과를 반환합니다.
|
||||
SolReqListResponse res = new SolReqListResponse();
|
||||
List<SolReqListItem> list = new ArrayList<>();
|
||||
|
||||
SolReqListItem item1 = new SolReqListItem();
|
||||
item1.setSrId("SR-2026-001");
|
||||
item1.setSrName("AX HUB 메인 화면 UI 개편");
|
||||
item1.setProcess("진행중");
|
||||
item1.setDevStage("개발(단위테스트)");
|
||||
item1.setAppName("AX HUB");
|
||||
item1.setRequester("윤희준");
|
||||
list.add(item1);
|
||||
|
||||
SolReqListItem item2 = new SolReqListItem();
|
||||
item2.setSrId("SR-2026-002");
|
||||
item2.setSrName("툴 연동 모듈 추가 개발");
|
||||
item2.setProcess("진행중");
|
||||
item2.setDevStage("분석/설계");
|
||||
item2.setAppName("MCP Gateway");
|
||||
item2.setRequester("고석민");
|
||||
list.add(item2);
|
||||
|
||||
res.setReqList(list);
|
||||
|
||||
return res;
|
||||
} catch (Exception e) {
|
||||
log.error("[MCI Tool] 연동 중 오류 발생: {}", e.getMessage(), e);
|
||||
return Map.of("status", "ERROR", "message", e.getMessage() != null ? e.getMessage() : "Unknown error");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a;
|
||||
import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_I;
|
||||
import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_O;
|
||||
import io.shinhanlife.dap.lib.integration.mci.component.AxhubMciComponent;
|
||||
import io.shinhanlife.dap.mcc.biz.oth.dto.Onnba3011Request;
|
||||
import io.shinhanlife.glow.communication.dto.Transfer;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -48,39 +47,8 @@ public class MciCfpaClient {
|
||||
* @param inDto 입력 DTO
|
||||
* @return 출력 객체
|
||||
*/
|
||||
public Object callCfpa0001(Onnba3011Request inDto) throws Exception {
|
||||
public Object callCfpa0001(CLCNNB00001_I mciReq) throws Exception {
|
||||
// 인터페이스 IO 객체 생성 및 매핑
|
||||
CLCNNB00001_I mciReq = new CLCNNB00001_I();
|
||||
mciReq.setDalScCd(inDto.getDalScCd());
|
||||
mciReq.setCstSucoRltyCd(inDto.getCstSucoRltyCd());
|
||||
mciReq.setCsNo(inDto.getCsNo());
|
||||
mciReq.setRdreNo(inDto.getRdreNo());
|
||||
mciReq.setUnfcPvsCalReqYn(inDto.getUnfcPvsCalReqYn());
|
||||
mciReq.setKcisPymmTnnrRequest(inDto.getKcisPymmTnnrRequest());
|
||||
mciReq.setLmovYn(inDto.getLmovYn());
|
||||
mciReq.setGenPsthApvTrgtYn(inDto.getGenPsthApvTrgtYn());
|
||||
mciReq.setIrcoLmovEcpbTrgtYn(inDto.getIrcoLmovEcpbTrgtYn());
|
||||
mciReq.setDigCalYn(inDto.getDigCalYn());
|
||||
mciReq.setPrbuIciDigCalYn(inDto.getPrbuIciDigCalYn());
|
||||
|
||||
if (inDto.getUnfcPrbuIrcoAddu() != null) {
|
||||
mciReq.setUnfcPrbuIrcoAddu(new CLCNNB00001_I.UnfcPrbuIrcoAdduDto());
|
||||
}
|
||||
|
||||
if (inDto.getSucoIspaBasDto() != null) {
|
||||
CLCNNB00001_I.SucoIspaBasDto sucoIspa = new CLCNNB00001_I.SucoIspaBasDto();
|
||||
sucoIspa.setCcnDalTypCd(inDto.getSucoIspaBasDto().getCcnDalTypCd());
|
||||
sucoIspa.setNwcnptCursCd(inDto.getSucoIspaBasDto().getNwcnptCursCd());
|
||||
sucoIspa.setInduAsctScCd(inDto.getSucoIspaBasDto().getInduAsctScCd());
|
||||
sucoIspa.setCepeOgnzNo(inDto.getSucoIspaBasDto().getCepeOgnzNo());
|
||||
sucoIspa.setCepePrafNo(inDto.getSucoIspaBasDto().getCepePrafNo());
|
||||
sucoIspa.setClmoOgnzNo(inDto.getSucoIspaBasDto().getClmoOgnzNo());
|
||||
sucoIspa.setClmoPrafNo(inDto.getSucoIspaBasDto().getClmoPrafNo());
|
||||
sucoIspa.setSucoYmd(inDto.getSucoIspaBasDto().getSucoYmd());
|
||||
sucoIspa.setIspDt(inDto.getSucoIspaBasDto().getIspDt());
|
||||
sucoIspa.setContYmd(inDto.getSucoIspaBasDto().getContYmd());
|
||||
mciReq.setSucoIspaBasDto(sucoIspa);
|
||||
}
|
||||
|
||||
// 인터페이스 호출
|
||||
Transfer<CLCNNB00001_O> resTransfer = mci.callTo(INTERFACE_CODE, mciReq, CLCNNB00001_O.class);
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import io.shinhanlife.dap.lib.integration.mci.component.AxhubMciComponent;
|
||||
import io.shinhanlife.glow.communication.dto.Transfer;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g
|
||||
* @className MciNclgClient
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author jade
|
||||
* @create 2026.07.29
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.07.29 jade 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class MciNclgClient {
|
||||
private final AxhubMciComponent mci;
|
||||
|
||||
public <T> Transfer<T> callTo(String interfaceId, String dummy, Object mciReq, Class<T> resType) throws Exception {
|
||||
return mci.callTo(interfaceId, dummy, mciReq, resType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io
|
||||
* @className SOLG00000001_I
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author jade
|
||||
* @create 2026.07.29
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.07.29 jade 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Data
|
||||
public class SOLG00000001_I {
|
||||
private String reqStatus;
|
||||
private String reqPeriod;
|
||||
private String reqTarget;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io
|
||||
* @className SOLG00000001_O
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author jade
|
||||
* @create 2026.07.29
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.07.29 jade 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Data
|
||||
public class SOLG00000001_O {
|
||||
private List<SOLG00000001_O_Item> reqList;
|
||||
|
||||
@Data
|
||||
public static class SOLG00000001_O_Item {
|
||||
private String srId;
|
||||
private String srName;
|
||||
private String process;
|
||||
private String devStage;
|
||||
private String appName;
|
||||
private String requester;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package io.shinhanlife.dap.mcc.biz.oth.usecase.impl;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import io.shinhanlife.dap.mcc.biz.oth.converter.Onnba3011Converter;
|
||||
import io.shinhanlife.dap.mcc.biz.oth.dto.Onnba3011Request;
|
||||
import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.MciCfpaClient;
|
||||
import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_I;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.mcc.biz.oth.usecase.impl
|
||||
* @className OnnbaMciToolUseCaseImplTest
|
||||
* @description ONNBA MCI tool use case test
|
||||
* @author 0986406
|
||||
* @create 2026.07.27
|
||||
* <pre>
|
||||
* ---------- revision history ----------
|
||||
* date author description
|
||||
* ---------- --------- ---------------------------
|
||||
* 2026.07.27 0986406 initial creation
|
||||
* </pre>
|
||||
*/
|
||||
class Onnba3011UseCaseImplTest {
|
||||
|
||||
@Test
|
||||
void convertsToolRequestBeforeCallingMciClient() throws Exception {
|
||||
MciCfpaClient mciCfpaClient = Mockito.mock(MciCfpaClient.class);
|
||||
Onnba3011Converter converter = Mockito.mock(Onnba3011Converter.class);
|
||||
Onnba3011UseCaseImpl useCase = new Onnba3011UseCaseImpl(mciCfpaClient, converter);
|
||||
Onnba3011Request request = new Onnba3011Request();
|
||||
CLCNNB00001_I mciRequest = new CLCNNB00001_I();
|
||||
|
||||
when(converter.toMciRequest(request)).thenReturn(mciRequest);
|
||||
when(mciCfpaClient.callCfpa0001(mciRequest)).thenReturn("success");
|
||||
|
||||
Object result = useCase.callOnnba3011(request);
|
||||
|
||||
assertEquals("success", result);
|
||||
verify(converter).toMciRequest(request);
|
||||
verify(mciCfpaClient).callCfpa0001(mciRequest);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package io.shinhanlife.dap.mcc.biz.sol.usecase.impl;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import io.shinhanlife.dap.mcc.biz.sol.converter.SolReqListConverter;
|
||||
import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqListRequest;
|
||||
import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqListResponse;
|
||||
import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.MciNclgClient;
|
||||
import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io.SOLG00000001_I;
|
||||
import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io.SOLG00000001_O;
|
||||
import io.shinhanlife.glow.communication.dto.Transfer;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
class SolReqListUseCaseImplTest {
|
||||
|
||||
@Test
|
||||
void callsMciWithConvertedRequestAndReturnsDummyResponse() throws Exception {
|
||||
MciNclgClient mci = Mockito.mock(MciNclgClient.class);
|
||||
SolReqListConverter converter = Mockito.mock(SolReqListConverter.class);
|
||||
SolReqListUseCaseImpl useCase = new SolReqListUseCaseImpl(mci, converter);
|
||||
SolReqListRequest request = new SolReqListRequest();
|
||||
SOLG00000001_I mciRequest = new SOLG00000001_I();
|
||||
|
||||
when(converter.toLegacyRequest(request)).thenReturn(mciRequest);
|
||||
when(mci.callTo(eq("SOLG00000001"), eq("SOLG00000001"), eq(mciRequest), eq(SOLG00000001_O.class)))
|
||||
.thenReturn(new Transfer<>());
|
||||
|
||||
SolReqListResponse response = (SolReqListResponse) useCase.execute(request);
|
||||
|
||||
verify(converter).toLegacyRequest(request);
|
||||
verify(mci).callTo("SOLG00000001", "SOLG00000001", mciRequest, SOLG00000001_O.class);
|
||||
assertThat(response.getReqList()).extracting(SolReqListResponse.SolReqListItem::getSrId)
|
||||
.containsExactly("SR-2026-001", "SR-2026-002");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.converter;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
import io.shinhanlife.dap.mcc.biz.oth.converter.Onnba3011Converter;
|
||||
import io.shinhanlife.dap.mcc.biz.oth.dto.Onnba3011Request;
|
||||
import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_I;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.converter
|
||||
* @className Onnba3011MciRequestConverterTest
|
||||
* @description ONNBA 3011 request converter test
|
||||
* @author 0986406
|
||||
* @create 2026.07.27
|
||||
* <pre>
|
||||
* ---------- revision history ----------
|
||||
* date author description
|
||||
* ---------- --------- ---------------------------
|
||||
* 2026.07.27 0986406 initial creation
|
||||
* </pre>
|
||||
*/
|
||||
class Onnba3011MciRequestConverterTest {
|
||||
|
||||
private final Onnba3011Converter converter = Mappers.getMapper(Onnba3011Converter.class);
|
||||
|
||||
@Test
|
||||
void mapsRootAndNestedRequestFieldsToMciPayload() {
|
||||
Onnba3011Request source = request();
|
||||
|
||||
CLCNNB00001_I result = converter.toMciRequest(source);
|
||||
|
||||
assertEquals("A", result.getDalScCd());
|
||||
assertEquals("123456", result.getCsNo());
|
||||
assertNotNull(result.getUnfcPrbuIrcoAddu());
|
||||
assertNotNull(result.getSucoIspaBasDto());
|
||||
assertEquals("01", result.getSucoIspaBasDto().getCcnDalTypCd());
|
||||
assertEquals("20260727", result.getSucoIspaBasDto().getSucoYmd());
|
||||
}
|
||||
|
||||
private Onnba3011Request request() {
|
||||
Onnba3011Request request = new Onnba3011Request();
|
||||
request.setDalScCd("A");
|
||||
request.setCsNo("123456");
|
||||
request.setUnfcPrbuIrcoAddu(new Onnba3011Request.UnfcPrbuIrcoAdduDto());
|
||||
|
||||
Onnba3011Request.SucoIspaBasDto suco = new Onnba3011Request.SucoIspaBasDto();
|
||||
suco.setCcnDalTypCd("01");
|
||||
suco.setSucoYmd("20260727");
|
||||
request.setSucoIspaBasDto(suco);
|
||||
return request;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,8 @@
|
||||
FROM eclipse-temurin:21-jdk-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY gradlew .
|
||||
COPY gradle gradle
|
||||
COPY build.gradle settings.gradle ./
|
||||
|
||||
COPY dap-tool-core dap-tool-core
|
||||
COPY dap-tool-sms dap-tool-sms
|
||||
RUN chmod +x gradlew
|
||||
RUN ./gradlew clean :dap-tool-sms:build -x test
|
||||
|
||||
FROM eclipse-temurin:21-jre-alpine
|
||||
WORKDIR /app
|
||||
RUN apk add --no-cache tzdata
|
||||
ENV TZ=Asia/Seoul
|
||||
COPY --from=builder /app/dap-tool-sms/build/libs/*-SNAPSHOT.jar app.jar
|
||||
COPY dap-tool-sms/build/libs/*-SNAPSHOT.jar app.jar
|
||||
EXPOSE 8082
|
||||
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
||||
import io.shinhanlife.dap.lib.annotation.McpValidation;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@@ -30,10 +31,12 @@ import lombok.Setter;
|
||||
public class SmsSendRequest {
|
||||
|
||||
@McpParameter(description = "수신자 전화번호", required = true)
|
||||
@McpValidation(pattern = "^01[0-9]-?\\d{3,4}-?\\d{4}$")
|
||||
private String phoneNumber;
|
||||
|
||||
@McpParameter(description = "전송할 메시지 내용", required = true)
|
||||
@McpValidation(pattern = "\\S")
|
||||
private String message;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$extensions = @("*.java", "*.xml", "*.yml", "*.properties", "*.gradle", "*.md")
|
||||
$baseDir = "C:\eGovFrameDev-4.3.1-64bit\workspace-egov\axhub-backend-main"
|
||||
|
||||
Write-Host "Replacing text (Req -> Request, Res -> Response)..."
|
||||
Get-ChildItem -Path $baseDir -Include $extensions -Recurse -File | ForEach-Object {
|
||||
if ($_.FullName -notmatch "\\build\\" -and $_.FullName -notmatch "\\\.git\\" -and $_.FullName -notmatch "\\\.gradle\\" -and $_.FullName -notmatch "\\bin\\") {
|
||||
try {
|
||||
$content = Get-Content $_.FullName -Raw -Encoding UTF8
|
||||
$newContent = $content -creplace "Req\b", "Request"
|
||||
$newContent = $newContent -creplace "Res\b", "Response"
|
||||
if ($content -cne $newContent) {
|
||||
Write-Host "Updated text in: $($_.FullName)"
|
||||
$utf8NoBom = New-Object System.Text.UTF8Encoding $False
|
||||
[System.IO.File]::WriteAllText($_.FullName, $newContent, $utf8NoBom)
|
||||
}
|
||||
} catch {
|
||||
Write-Host "Skipped binary or locked file: $($_.FullName)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "Renaming DTO files..."
|
||||
Get-ChildItem -Path $baseDir -Include *Req.java,*Res.java -Recurse -File | ForEach-Object {
|
||||
if ($_.FullName -notmatch "\\build\\" -and $_.FullName -notmatch "\\\.git\\" -and $_.FullName -notmatch "\\\.gradle\\" -and $_.FullName -notmatch "\\bin\\") {
|
||||
$newName = $_.Name -creplace "Req\.java$", "Request.java"
|
||||
$newName = $newName -creplace "Res\.java$", "Response.java"
|
||||
Write-Host "Renaming file: $($_.FullName) -> $newName"
|
||||
Rename-Item -Path $_.FullName -NewName $newName
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "DTO Refactoring completed!"
|
||||
33
refactor.ps1
33
refactor.ps1
@@ -1,33 +0,0 @@
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$extensions = @("*.java", "*.xml", "*.yml", "*.properties", "*.gradle", "*.md", "Dockerfile*")
|
||||
$baseDir = "C:\eGovFrameDev-4.3.1-64bit\workspace-egov\axhub-backend-main"
|
||||
|
||||
Write-Host "Replacing text..."
|
||||
Get-ChildItem -Path $baseDir -Include $extensions -Recurse -File | ForEach-Object {
|
||||
if ($_.FullName -notmatch "\\build\\" -and $_.FullName -notmatch "\\\.git\\" -and $_.FullName -notmatch "\\\.gradle\\" -and $_.FullName -notmatch "\\bin\\") {
|
||||
try {
|
||||
$content = Get-Content $_.FullName -Raw -Encoding UTF8
|
||||
$newContent = $content -replace "dapms", "mcg"
|
||||
$newContent = $newContent -replace "dapmt", "mcc"
|
||||
if ($content -cne $newContent) {
|
||||
Write-Host "Updated text in: $($_.FullName)"
|
||||
$utf8NoBom = New-Object System.Text.UTF8Encoding $False
|
||||
[System.IO.File]::WriteAllText($_.FullName, $newContent, $utf8NoBom)
|
||||
}
|
||||
} catch {
|
||||
Write-Host "Skipped binary or locked file: $($_.FullName)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "Renaming directories..."
|
||||
Get-ChildItem -Path $baseDir -Recurse -Directory | Where-Object { $_.Name -eq "dapms" -or $_.Name -eq "dapmt" } | Sort-Object -Property @{Expression={$_.FullName.Length}; Descending=$true} | ForEach-Object {
|
||||
if ($_.FullName -notmatch "\\build\\" -and $_.FullName -notmatch "\\\.git\\" -and $_.FullName -notmatch "\\\.gradle\\" -and $_.FullName -notmatch "\\bin\\") {
|
||||
$newName = if ($_.Name -eq "dapms") { "mcg" } else { "mcc" }
|
||||
Write-Host "Renaming dir: $($_.FullName) -> $newName"
|
||||
Rename-Item -Path $_.FullName -NewName $newName
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "Refactoring completed!"
|
||||
Reference in New Issue
Block a user