feat: Add Developer Portal Web UI for scaffolding to Gateway

This commit is contained in:
jade
2026-07-09 23:04:37 +09:00
parent 3f6f2eed4f
commit 9af1581da3
4 changed files with 237 additions and 28 deletions

View File

@@ -1,4 +1,4 @@
package io.shinhanlife.axhub.biz.mcp.tool.util;
package io.shinhanlife.axhub.common.util;
import java.io.IOException;
import java.nio.file.Files;
@@ -31,7 +31,8 @@ public class PodScaffolder {
String createDate = getOrAsk(args, 3, scanner, "4. 작성일 (엔터 입력 시 '" + defaultDate + "'): ");
if (createDate.trim().isEmpty()) createDate = defaultDate;
scaffoldPod(moduleName, portStr, shortName, author, createDate);
String result = scaffoldPod(moduleName, portStr, shortName, author, createDate);
System.out.println(result);
}
private static String getOrAsk(String[] args, int index, Scanner scanner, String prompt) {
@@ -42,17 +43,17 @@ public class PodScaffolder {
return scanner.nextLine().trim();
}
private static void scaffoldPod(String moduleName, String portStr, String shortName, String author, String createDate) throws IOException {
public static String scaffoldPod(String moduleName, String portStr, String shortName, String author, String createDate) throws IOException {
Path modulePath = Paths.get(moduleName);
if (Files.exists(modulePath)) {
System.out.println("[오류] 이미 존재하는 모듈입니다: " + moduleName);
return;
return "[오류] 이미 존재하는 모듈입니다: " + moduleName;
}
System.out.println("\n[1/6] 모듈 디렉터리 생성 중...");
StringBuilder log = new StringBuilder();
log.append("[1/6] 모듈 디렉터리 생성 중...\n");
Files.createDirectories(modulePath);
System.out.println("[2/6] build.gradle 생성 중...");
log.append("[2/6] build.gradle 생성 중...\n");
String buildGradle = """
plugins {
id 'org.springframework.boot'
@@ -67,7 +68,7 @@ public class PodScaffolder {
""";
Files.writeString(modulePath.resolve("build.gradle"), buildGradle);
System.out.println("[3/6] Dockerfile 생성 중...");
log.append("[3/6] Dockerfile 생성 중...\n");
String dockerfile = """
FROM eclipse-temurin:21-jdk-alpine
WORKDIR /app
@@ -76,7 +77,7 @@ public class PodScaffolder {
""".formatted(moduleName);
Files.writeString(modulePath.resolve("Dockerfile"), dockerfile);
System.out.println("[4/6] Application 클래스 및 설정 파일 생성 중...");
log.append("[4/6] Application 클래스 및 설정 파일 생성 중...\n");
Path srcPath = modulePath.resolve("src/main/java/io/shinhanlife/axhub/biz/mcp/tool/" + shortName);
Files.createDirectories(srcPath);
@@ -200,7 +201,7 @@ public class PodScaffolder {
""".formatted(moduleName, moduleName);
Files.writeString(resPath.resolve("logback-spring.xml"), logbackXml);
System.out.println("[5/6] settings.gradle 에 모듈 등록 중...");
log.append("[5/6] settings.gradle 에 모듈 등록 중...\n");
Path settingsPath = Paths.get("settings.gradle");
if (Files.exists(settingsPath)) {
String settings = Files.readString(settingsPath);
@@ -209,7 +210,7 @@ public class PodScaffolder {
}
}
System.out.println("[6/6] docker-compose.yml 에 서비스 추가 중...");
log.append("[6/6] docker-compose.yml 에 서비스 추가 중...\n");
Path dockerComposePath = Paths.get("docker-compose.yml");
if (Files.exists(dockerComposePath)) {
String compose = Files.readString(dockerComposePath);
@@ -241,12 +242,13 @@ public class PodScaffolder {
}
}
System.out.println("\\n=========================================");
System.out.println(" 🎉 Pod Scaffolding Complete! ");
System.out.println("=========================================");
System.out.println("1. [새로운 모듈] " + moduleName + " 폴더가 생성되었습니다.");
System.out.println("2. [ToolScaffolder]를 사용해 이 모듈 안에 툴을 추가하세요.");
System.out.println("3. 실행 전 Gradle 동기화(Sync)를 한 번 진행해 주세요.");
log.append("\n=========================================\n");
log.append(" 🎉 Pod Scaffolding Complete! \n");
log.append("=========================================\n");
log.append("1. [새로운 모듈] ").append(moduleName).append(" 폴더가 생성되었습니다.\n");
log.append("2. [ToolScaffolder]를 사용해 이 모듈 안에 툴을 추가하세요.\n");
log.append("3. 실행 전 Gradle 동기화(Sync)를 한 번 진행해 주세요.\n");
return log.toString();
}
private static String capitalize(String str) {

View File

@@ -1,4 +1,4 @@
package io.shinhanlife.axhub.biz.mcp.tool.util;
package io.shinhanlife.axhub.common.util;
import java.io.IOException;
import java.nio.file.Files;
@@ -68,7 +68,8 @@ public class ToolScaffolder {
String createDate = getOrAsk(args, 7, scanner, "8. 작성일 (엔터 입력 시 '" + defaultDate + "'): ");
if (createDate.trim().isEmpty()) createDate = defaultDate;
scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, createDate);
String result = scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, createDate);
System.out.println(result);
}
private static String getOrAsk(String[] args, int index, Scanner scanner, String prompt) {
@@ -79,13 +80,15 @@ public class ToolScaffolder {
return scanner.nextLine().trim();
}
private static void scaffold(String baseName, String interfaceId, String description, String group, String routingType, String moduleName, String author, String createDate) throws IOException {
public static String scaffold(String baseName, String interfaceId, String description, String group, String routingType, String moduleName, String author, String createDate) throws IOException {
Path serviceDir = Paths.get(moduleName, BASE_PACKAGE_PATH, "service");
Path dtoDir = Paths.get(moduleName, BASE_PACKAGE_PATH, "dto");
Files.createDirectories(serviceDir);
Files.createDirectories(dtoDir);
StringBuilder log = new StringBuilder();
// Generate Req DTO
String reqContent = """
package %s.dto;
@@ -212,16 +215,18 @@ public class ToolScaffolder {
""".formatted(toolName, description, description + " 해줘.", interfaceId);
yamlContent = yamlContent.replaceFirst("functions:", "functions:" + newFunctionYaml);
Files.writeString(yamlPath, yamlContent);
System.out.println("[YAML] " + yamlPath + " (함수 설정 자동 등록됨)");
log.append("[YAML] ").append(yamlPath).append(" (함수 설정 자동 등록됨)\n");
}
}
System.out.println("\n=========================================");
System.out.println(" Scaffolding Complete!");
System.out.println("=========================================");
System.out.println("[Service] " + serviceDir.resolve(baseName + "Service.java"));
System.out.println("[Req DTO] " + dtoDir.resolve(baseName + "Req.java"));
System.out.println("[Res DTO] " + dtoDir.resolve(baseName + "Res.java"));
System.out.println("\n Tip: " + interfaceId + " 목업 데이터를 mock-responses.json에 추가하세요.");
log.append("\n=========================================\n");
log.append(" Scaffolding Complete!\n");
log.append("=========================================\n");
log.append("[Service] ").append(serviceDir.resolve(baseName + "Service.java")).append("\n");
log.append("[Req DTO] ").append(dtoDir.resolve(baseName + "Req.java")).append("\n");
log.append("[Res DTO] ").append(dtoDir.resolve(baseName + "Res.java")).append("\n");
log.append("\n Tip: ").append(interfaceId).append(" 목업 데이터를 mock-responses.json에 추가하세요.\n");
return log.toString();
}
}

View File

@@ -0,0 +1,45 @@
package io.shinhanlife.axhub.biz.mcp.gateway.presentation;
import io.shinhanlife.axhub.common.util.PodScaffolder;
import io.shinhanlife.axhub.common.util.ToolScaffolder;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/api/v1/scaffold")
public class ScaffoldingController {
@PostMapping("/pod")
public String scaffoldPod(@RequestBody Map<String, String> req) {
try {
String moduleName = req.getOrDefault("moduleName", "axhub-tool-other");
if (!moduleName.startsWith("axhub-tool-")) moduleName = "axhub-tool-" + moduleName;
String port = req.getOrDefault("port", "8085");
String shortName = moduleName.replace("axhub-tool-", "").replace("-", "");
String author = req.getOrDefault("author", "System");
String date = req.getOrDefault("date", java.time.LocalDate.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyy.MM.dd")));
return PodScaffolder.scaffoldPod(moduleName, port, shortName, author, date);
} catch (Exception e) {
return "오류 발생: " + e.getMessage();
}
}
@PostMapping("/tool")
public String scaffoldTool(@RequestBody Map<String, String> req) {
try {
String baseName = req.get("baseName");
String interfaceId = req.get("interfaceId");
String description = req.get("description");
String group = req.getOrDefault("group", "COMMON");
String routingType = req.getOrDefault("routingType", "HTTP");
String moduleName = req.getOrDefault("moduleName", "axhub-tool-other");
String author = req.getOrDefault("author", "System");
String date = req.getOrDefault("date", java.time.LocalDate.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyy.MM.dd")));
return ToolScaffolder.scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, date);
} catch (Exception e) {
return "오류 발생: " + e.getMessage();
}
}
}

View File

@@ -0,0 +1,157 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>AXHUB Developer Portal - Scaffolding</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body { background-color: #f8f9fa; }
.container { max-width: 800px; margin-top: 50px; }
.card { box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
.nav-tabs .nav-link.active { font-weight: bold; color: #0d6efd; }
#resultBox { display: none; margin-top: 20px; white-space: pre-wrap; font-family: monospace; }
</style>
</head>
<body>
<div class="container">
<h2 class="mb-4">🚀 AXHUB Developer Portal</h2>
<div class="card">
<div class="card-header">
<ul class="nav nav-tabs card-header-tabs" id="myTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="tool-tab" data-bs-toggle="tab" data-bs-target="#tool" type="button" role="tab">Tool (기능) 생성</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="pod-tab" data-bs-toggle="tab" data-bs-target="#pod" type="button" role="tab">Pod (모듈) 생성</button>
</li>
</ul>
</div>
<div class="card-body">
<div class="tab-content" id="myTabContent">
<!-- Tool Creation Form -->
<div class="tab-pane fade show active" id="tool" role="tabpanel">
<form id="toolForm">
<div class="mb-3">
<label class="form-label">기본 이름 (PascalCase)</label>
<input type="text" class="form-control" name="baseName" placeholder="예: ExchangeRate" required>
</div>
<div class="mb-3">
<label class="form-label">레거시 API 인터페이스 ID</label>
<input type="text" class="form-control" name="interfaceId" placeholder="예: EXCH_001" required>
</div>
<div class="mb-3">
<label class="form-label">기능 설명</label>
<input type="text" class="form-control" name="description" placeholder="예: 환율 조회 기능" required>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">소속 그룹</label>
<select class="form-select" name="group">
<option value="COMMON">COMMON</option>
<option value="CUSTOMER">CUSTOMER</option>
<option value="CONTRACT">CONTRACT</option>
<option value="CLAIM">CLAIM</option>
</select>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">통신 프로토콜</label>
<select class="form-select" name="routingType">
<option value="HTTP">HTTP</option>
<option value="TCP">TCP</option>
<option value="MCI">MCI</option>
<option value="EAI">EAI</option>
</select>
</div>
</div>
<div class="mb-3">
<label class="form-label">타겟 모듈명</label>
<input type="text" class="form-control" name="moduleName" value="axhub-tool-other" required>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">작성자</label>
<input type="text" class="form-control" name="author" placeholder="시스템 사용자명">
</div>
<div class="col-md-6 mb-3">
<label class="form-label">작성일</label>
<input type="text" class="form-control" name="date" placeholder="오늘 날짜">
</div>
</div>
<button type="submit" class="btn btn-primary w-100">Tool 자동 생성</button>
</form>
</div>
<!-- Pod Creation Form -->
<div class="tab-pane fade" id="pod" role="tabpanel">
<form id="podForm">
<div class="mb-3">
<label class="form-label">새로운 모듈 이름</label>
<input type="text" class="form-control" name="moduleName" placeholder="예: hr (axhub-tool-hr 로 자동 변환됨)" required>
</div>
<div class="mb-3">
<label class="form-label">포트 번호</label>
<input type="number" class="form-control" name="port" placeholder="예: 8086" required>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">작성자</label>
<input type="text" class="form-control" name="author" placeholder="시스템 사용자명">
</div>
<div class="col-md-6 mb-3">
<label class="form-label">작성일</label>
<input type="text" class="form-control" name="date" placeholder="오늘 날짜">
</div>
</div>
<button type="submit" class="btn btn-success w-100">Pod 자동 생성</button>
</form>
</div>
</div>
</div>
</div>
<div id="resultBox" class="alert alert-info"></div>
</div>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
function handleFormSubmit(formId, apiUrl) {
document.getElementById(formId).addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
const data = Object.fromEntries(formData.entries());
fetch(apiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
.then(response => response.text())
.then(result => {
const resultBox = document.getElementById('resultBox');
resultBox.style.display = 'block';
resultBox.className = 'alert alert-success';
resultBox.textContent = result;
// Add a gentle reminder to run gradle build
resultBox.textContent += "\n\n💡 변경사항 반영을 위해 IDE에서 Gradle 리로드(Sync)를 수행해주세요!";
})
.catch(error => {
const resultBox = document.getElementById('resultBox');
resultBox.style.display = 'block';
resultBox.className = 'alert alert-danger';
resultBox.textContent = "오류 발생: " + error;
});
});
}
handleFormSubmit('toolForm', '/api/v1/scaffold/tool');
handleFormSubmit('podForm', '/api/v1/scaffold/pod');
</script>
</body>
</html>