Remove scaffolding utilities

This commit is contained in:
jade
2026-08-04 22:22:01 +09:00
parent 2796b883da
commit b099397623
3 changed files with 0 additions and 1050 deletions

View File

@@ -1,308 +0,0 @@
package io.shinhanlife.dap.lib.util;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Scanner;
public class PodScaffolder {
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
System.out.println("=========================================");
System.out.println(" MCP Tool Pod Scaffolder (Java CLI) ");
System.out.println("=========================================\n");
String rawModuleName = getOrAsk(args, 0, scanner, "1. 생성할 모듈(Pod) 이름 (예: payment 또는 dap-was-payment): ");
String moduleName = rawModuleName.startsWith("dap-was-") ? rawModuleName : "dap-was-" + rawModuleName;
String portStr = getOrAsk(args, 1, scanner, "2. 사용할 포트 번호 (예: 8085): ");
String shortName = moduleName.replace("dap-was-", "").replace("-", "");
String defaultAuthor = System.getProperty("user.name");
String defaultDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy.MM.dd"));
String author = getOrAsk(args, 2, scanner, "3. 작성자 (엔터 입력 시 '" + defaultAuthor + "'): ");
if (author.trim().isEmpty()) author = defaultAuthor;
String createDate = getOrAsk(args, 3, scanner, "4. 작성일 (엔터 입력 시 '" + defaultDate + "'): ");
if (createDate.trim().isEmpty()) createDate = defaultDate;
String result = scaffoldPod(moduleName, portStr, shortName, author, createDate);
System.out.println(result);
}
private static String getOrAsk(String[] args, int index, Scanner scanner, String prompt) {
if (args.length > index) {
return args[index];
}
System.out.print(prompt);
return scanner.nextLine().trim();
}
public static String scaffoldPod(String moduleName, String portStr, String shortName, String author, String createDate) throws IOException {
String envSourceDir = System.getenv("AXHUB_SOURCE_DIR");
Path rootDir = envSourceDir != null ? Paths.get(envSourceDir) : Paths.get(".");
Path modulePath = rootDir.resolve(Paths.get(moduleName));
if (Files.exists(modulePath)) {
return "[오류] 이미 존재하는 모듈입니다: " + moduleName;
}
StringBuilder log = new StringBuilder();
log.append("[1/6] 모듈 디렉터리 생성 중...\n");
Files.createDirectories(modulePath);
log.append("[2/6] build.gradle 생성 중...\n");
String buildGradle = """
plugins {
id 'org.springframework.boot'
}
dependencies {
implementation project(':dap-was-core')
}
dependencies {
compileOnly 'org.projectlombok:lombok:1.18.32'
annotationProcessor 'org.projectlombok:lombok:1.18.32'
}
""";
Files.writeString(modulePath.resolve("build.gradle"), buildGradle);
log.append("[3/6] Dockerfile 생성 중...\n");
String dockerfile = """
FROM eclipse-temurin:21-jdk-alpine
WORKDIR /app
COPY build/libs/%s-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]
""".formatted(moduleName);
Files.writeString(modulePath.resolve("Dockerfile"), dockerfile);
log.append("[4/6] Application 클래스 및 설정 파일 생성 중...\n");
Path srcPath = modulePath.resolve("src/main/java/io/shinhanlife/dap/mcc/" + shortName);
Files.createDirectories(srcPath);
String appClass = """
package io.shinhanlife.dap.mcc.%s;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.cache.annotation.EnableCaching;
/**
* @package io.shinhanlife.dap.mcc.%s
* @className DapWas%sApplication
* @description AX HUB 시스템 처리 클래스
* @author %s
* @create %s
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* %s %s 최초생성
*
* </pre>
*/
@SpringBootApplication(scanBasePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.lib.adapter", "io.shinhanlife.dap.lib.mcp", "io.shinhanlife.dap.lib.config", "io.shinhanlife.dap.lib.integration"})
@ConfigurationPropertiesScan(basePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.lib.adapter", "io.shinhanlife.dap.lib.mcp", "io.shinhanlife.dap.lib.config", "io.shinhanlife.dap.lib.integration"})
@EnableCaching
public class DapWas%sApplication {
public static void main(String[] args) {
SpringApplication.run(DapWas%sApplication.class, args);
}
}
""".formatted(shortName, shortName, capitalize(shortName), author, createDate, createDate, author, capitalize(shortName), capitalize(shortName));
Files.writeString(srcPath.resolve("DapTool" + capitalize(shortName) + "Application.java"), appClass);
Path resPath = modulePath.resolve("src/main/resources");
Files.createDirectories(resPath);
String applicationYml = """
server:
port: %s
spring:
application:
name: %s
profiles:
active: local
logging:
level:
org.apache.kafka: ERROR
mcp:
namespace: ""
security:
tenant-domains:
TESTER-DEV: ALL
""".formatted(portStr, moduleName);
Files.writeString(resPath.resolve("application.yml"), applicationYml);
String applicationLocalYml = """
# Local 환경 전용 설정 (H2 메모리 DB 등)
spring:
datasource:
url: jdbc:p6spy:h2:mem:testdb;DB_CLOSE_DELAY=-1;
driverClassName: com.p6spy.engine.spy.P6SpyDriver
username: sa
password: password
h2:
console:
enabled: true
eims:
http:
url: http://localhost:${server.port}/api/gateway
tcp:
host: 127.0.0.1
port: 8090
timeout: 5000
jsp:
form:
url: http://localhost:${server.port}/mock/jsp-form
json:
url: http://localhost:${server.port}/mock/jsp-json
mci:
url: http://localhost:${server.port}/api/mock/esb/api
mcistring:
url: http://localhost:${server.port}/api/mock/esb/string
mcp:
security:
tenant-domains:
mcp-client-1: CUSTOMER,COMMON
mcp-client-2: ALL
axhub:
gateway:
url: http://localhost:8081
tool:
url: ${AXHUB_TOOL_URL:http://localhost:${server.port}}
""";
Files.writeString(resPath.resolve("application-local.yml"), applicationLocalYml);
String applicationDevYml = """
# OCI 클라우드 환경 전용 설정
server:
port: ${PORT:%s}
axhub:
gateway:
url: https://axhubmcp.devjun.net
tool:
url: http://144.24.70.100:%s
eims:
http:
url: http://localhost:${server.port}/api/gateway
tcp:
host: 127.0.0.1
port: 8090
timeout: 5000
jsp:
form:
url: http://localhost:${server.port}/mock/jsp-form
json:
url: http://localhost:${server.port}/mock/jsp-json
mci:
url: http://localhost:${server.port}/api/mock/esb/api
mcistring:
url: http://localhost:${server.port}/api/mock/esb/string
shinhan:
integration:
envrTypeCd: D
eai:
url: http://10.176.32.181
internalMci:
url: http://10.176.32.173
bancaMci:
url: http://10.176.32.117
externalMci:
url: http://10.176.32.176
""".formatted(portStr, portStr);
Files.writeString(resPath.resolve("application-dev.yml"), applicationDevYml);
String logbackXml = """
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="LOG_PATTERN" value="%%d{yyyy-MM-dd HH:mm:ss.SSS} [%%thread] [%%X{traceId}] %%-5level %%logger{36} - %%msg%%n" />
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/%s.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/%s-%%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
<logger name="io.shinhanlife" level="DEBUG" />
</configuration>
""".formatted(moduleName, moduleName);
Files.writeString(resPath.resolve("logback-spring.xml"), logbackXml);
log.append("[5/6] settings.gradle 에 모듈 등록 중...\n");
Path settingsPath = rootDir.resolve(Paths.get("settings.gradle"));
if (Files.exists(settingsPath)) {
String settings = Files.readString(settingsPath);
if (!settings.contains("include '" + moduleName + "'")) {
Files.writeString(settingsPath, System.lineSeparator() + "include '" + moduleName + "'" + System.lineSeparator(), StandardOpenOption.APPEND);
}
}
log.append("[6/6] docker-compose.yml 에 서비스 추가 중...\n");
Path dockerComposePath = rootDir.resolve(Paths.get("docker-compose.yml"));
if (Files.exists(dockerComposePath)) {
String compose = Files.readString(dockerComposePath);
String serviceName = moduleName.replace("dap-", ""); // e.g. tool-payment
if (!compose.contains(" " + serviceName + ":")) {
String newService = """
%s:
build:
context: .
dockerfile: %s/Dockerfile
ports:
- "%s:%s"
depends_on:
- redis
environment:
- TZ=Asia/Seoul
- SPRING_REDIS_HOST=redis
- SPRING_REDIS_PORT=6379
- SPRING_DATA_REDIS_PORT=6379
- AXHUB_TOOL_URL=http://%s:%s
- GLOW_COMMUNICATION_MCI_HOST=http://mci-mock
- GLOW_COMMUNICATION_MCI_PORT=8080
- GLOW_COMMUNICATION_EXTMCI_HOST=http://mci-mock
- GLOW_COMMUNICATION_EXTMCI_PORT=8080
- GLOW_COMMUNICATION_EAI_HOST=http://mci-mock
- GLOW_COMMUNICATION_EAI_PORT=8080
""".formatted(serviceName, moduleName, portStr, portStr, serviceName, portStr);
Files.writeString(dockerComposePath, System.lineSeparator() + newService, StandardOpenOption.APPEND);
}
}
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) {
if (str == null || str.isEmpty()) return str;
return str.substring(0, 1).toUpperCase() + str.substring(1);
}
}

View File

@@ -1,714 +0,0 @@
package io.shinhanlife.dap.lib.util;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.Scanner;
/**
* MCP Tool 코드를 자동 생성(Scaffolding)하는 유틸리티 클래스
*
* [실행 방법]
* 방법 1. IDE(IntelliJ 등)에서 직접 실행 (대화형 모드 추천 ⭐)
* - 이 클래스(ToolScaffolder.java)를 열고 main 메서드를 직접 실행(Run)합니다.
* - 콘솔 창에 뜨는 질문에 차례대로 값을 입력하기만 하면 파일이 생성됩니다.
*
* 방법 2. 커맨드라인(터미널)에서 실행 (명령어 기반)
* - 컴파일: javac -encoding UTF-8 dap-was-core/src/main/java/io/shinhanlife/dap/mcc/util/ToolScaffolder.java
* - 실행: java -cp dap-was-core/src/main/java io.shinhanlife.dap.lib.util.ToolScaffolder [이름] [ID] "[설명]" "[그룹]" "[통신방식]" "[모듈명]"
*/
/**
* @package io.shinhanlife.dap.lib.util
* @className ToolScaffolder
* @description AX HUB 시스템 처리 클래스
* @author 0986406
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 0986406 최초생성
*
* </pre>
*/
public class ToolScaffolder {
private static final String BASE_PACKAGE = "io.shinhanlife.dap.mcc";
private static final String BASE_PACKAGE_PATH = "src/main/java/io/shinhanlife/dap/mcc";
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
System.out.println("=========================================");
System.out.println(" MCP Tool Scaffolder (Java CLI) ");
System.out.println("=========================================\n");
String baseName = getOrAsk(args, 0, scanner, "1. 생성할 Tool의 기본 이름 (예: ExchangeRate) [영문 PascalCase]: ");
String interfaceId = getOrAsk(args, 1, scanner, "2. 레거시 API 인터페이스 ID (예: EXCH_001): ");
String description = getOrAsk(args, 2, scanner, "3. Tool 기능 설명 (예: 환율 조회): ");
String group = getOrAsk(args, 3, scanner, "4. Tool 소속 그룹 (예: SAMPLE, NOTIFICATION, CLAIM, POLICY, HR, CONTRACT, CUSTOMER 등): ");
if (group.isEmpty()) group = "COMMON";
String routingType = getOrAsk(args, 4, scanner, "5. 통신 프로토콜 (예: HTTP, TCP, MCI, EAI): ");
if (routingType.trim().isEmpty()) {
routingType = "HTTP";
}
String moduleName = getOrAsk(args, 5, scanner, "6. 코드를 생성할 모듈 (기본: dap-was-oth): ");
if (moduleName.trim().isEmpty()) {
moduleName = "dap-was-oth";
}
String defaultAuthor = System.getProperty("user.name");
String defaultDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy.MM.dd"));
String author = getOrAsk(args, 6, scanner, "7. 작성자 (엔터 입력 시 '" + defaultAuthor + "'): ");
if (author.trim().isEmpty()) author = defaultAuthor;
String createDate = getOrAsk(args, 7, scanner, "8. 작성일 (엔터 입력 시 '" + defaultDate + "'): ");
if (createDate.trim().isEmpty()) createDate = defaultDate;
String result = scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, createDate, true, null);
System.out.println(result);
}
private static String getOrAsk(String[] args, int index, Scanner scanner, String prompt) {
if (args.length > index) {
return args[index];
}
System.out.print(prompt);
return scanner.nextLine().trim();
}
public static String scaffold(String baseName, String interfaceId, String description, String group, String routingType, String moduleName, String author, String createDate, boolean register, String clientSystemCode) throws IOException {
baseName = toPascalCase(baseName);
String envSourceDir = System.getenv("AXHUB_SOURCE_DIR");
Path rootDir = envSourceDir != null ? Paths.get(envSourceDir) : Paths.get(".");
Path usecaseDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "biz", group.toLowerCase(), "usecase"));
Path usecaseImplDir = usecaseDir.resolve("impl");
Path dtoDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "biz", group.toLowerCase(), "dto"));
Path legacyDtoDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "biz", group.toLowerCase(), "legacy"));
Path converterDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "biz", group.toLowerCase(), "converter"));
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);
Files.createDirectories(usecaseImplDir);
Files.createDirectories(dtoDir);
if (isMci) {
Files.createDirectories(mciIoDir);
if (mciClientDir != null) {
Files.createDirectories(mciClientDir);
}
} else {
Files.createDirectories(legacyDtoDir);
}
Files.createDirectories(converterDir);
StringBuilder log = new StringBuilder();
// Generate Request DTO
String reqContent = """
package %s.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.shinhanlife.dap.lib.annotation.McpParameter;
import io.shinhanlife.dap.lib.annotation.McpValidation;
import lombok.Data;
/**
* @package %s.dto
* @className %sRequest
* @description AX HUB 시스템 처리 클래스
* @author %s
* @create %s
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* %s %s 최초생성
*
* </pre>
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class %sRequest {
@McpParameter(description = "수신자 전화번호", required = true)
@McpValidation(pattern = "^01(?:0|1|[6-9])-(?:\\\\d{3}|\\\\d{4})-\\\\d{4}$", examples = {"010-1234-5678"})
private String phoneNumber;
@McpParameter(description = "전송할 메시지 내용", required = true)
@McpValidation(defaultValue = "안녕하세요.")
private String message;
}
""".formatted(bizPackage, bizPackage, baseName, author, createDate, createDate, author, baseName);
Files.writeString(dtoDir.resolve(baseName + "Request.java"), reqContent);
// Generate Response DTO
String resContent = """
package %s.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.shinhanlife.dap.lib.annotation.McpOutputSchema;
import io.shinhanlife.dap.lib.annotation.McpValidation;
import lombok.Data;
/**
* @package %s.dto
* @className %sResponse
* @description AX HUB 시스템 처리 클래스
* @author %s
* @create %s
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* %s %s 최초생성
*
* </pre>
*/
@Data
@McpOutputSchema
@JsonInclude(JsonInclude.Include.NON_NULL)
public class %sResponse {
@McpValidation(required = true)
private String resultCode;
@McpValidation(maxLength = 200, nullable = true)
private String resultMessage;
// TODO: Add response fields here. Do not include PII in the Tool response.
}
""".formatted(bizPackage, bizPackage, baseName, author, createDate, createDate, author, baseName);
Files.writeString(dtoDir.resolve(baseName + "Response.java"), resContent);
String toolName = toToolName(moduleName, group, baseName);
String serviceInterfaceContent = """
package %s.usecase;
import io.shinhanlife.dap.lib.annotation.McpFunction;
import io.shinhanlife.dap.lib.annotation.McpTool;
import %s.dto.%sRequest;
@McpTool(
routingType = "%s",
categoryKey = "%s"
)
public interface %sUseCase {
@McpFunction(
displayName = "%s 툴",
name = "%s",
description = "%s",
prompt = "%s",
mappingId = "%s",
register = %s,
requiresApproval = false,
openWorldHint = true,
version = "1.0.0",
timeoutMillis = 300000L,
enabled = true
)
Object execute(%sRequest req);
}
""".formatted(
bizPackage,
bizPackage, baseName,
routingType, group.toLowerCase(),
baseName,
baseName, toolName, description, description + " ?줘.", interfaceId, register,
baseName
);
Files.writeString(usecaseDir.resolve(baseName + "UseCase.java"), serviceInterfaceContent);
String serviceImplContent;
if (isMci) {
serviceImplContent = """
package %s.usecase.impl;
import %s.dto.%sRequest;
import %s.dto.%sResponse;
import %s.usecase.%sUseCase;
import io.shinhanlife.dap.lib.integration.mci.component.AxhubMciComponent;
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 %s.converter.%sConverter;
import %s.%s.io.%s_I;
%s
/**
* @package %s.usecase.impl
* @className %sUseCaseImpl
* @description AX HUB 시스템 처리 클래스
* @author %s
* @create %s
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* %s %s 최초생성
*
* </pre>
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class %sUseCaseImpl implements %sUseCase {
%s
private final %sConverter converter;
@Override
public Object execute(%sRequest req) {
log.info("[MCI Tool] {} 요청 수신.", "%s");
try {
// MapStruct를 이용한 자동 매핑 (AI DTO -> MCI DTO)
%s_I mciReq = converter.toLegacyRequest(req);
Transfer<Object> resTransfer = mci.callTo(
"%s",
null,
mciReq,
Object.class
);
return resTransfer.getBody() != null ? resTransfer.getBody() : Map.of("status", "SUCCESS");
} catch (Exception e) {
log.error("[MCI Tool] 연동 중 오류 발생: {}", e.getMessage(), e);
return Map.of("status", "ERROR", "message", e.getMessage() != null ? e.getMessage() : "Unknown error");
}
}
}
""".formatted(
bizPackage,
bizPackage, baseName,
bizPackage, baseName,
bizPackage, baseName,
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,
(clientPrefixCap.isEmpty() ? "private final AxhubMciComponent mci;" : "private final Mci" + clientPrefixCap + "Client mci;"),
baseName,
baseName,
toolName,
interfaceId,
interfaceId
);
} else {
serviceImplContent = """
package %s.usecase.impl;
import %s.dto.%sRequest;
import %s.dto.%sResponse;
import %s.usecase.%sUseCase;
import io.shinhanlife.dap.mcc.usecase.AbstractMcpToolUseCase;
import %s.converter.%sConverter;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* @package %s.usecase.impl
* @className %sUseCaseImpl
* @description AX HUB 시스템 처리 클래스
* @author %s
* @create %s
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* %s %s 최초생성
*
* </pre>
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class %sUseCaseImpl extends AbstractMcpToolUseCase implements %sUseCase {
private final %sConverter converter;
@Override
public Object execute(%sRequest req) {
// %sLegacyRequest legacyRequest = converter.toLegacyRequest(req);
return executeLegacy("%s", "%s", req); // Or pass legacyRequest
}
}
""".formatted(
bizPackage,
bizPackage, baseName,
bizPackage, baseName,
bizPackage, baseName,
bizPackage, baseName,
bizPackage, baseName,
author,
createDate,
createDate, author,
baseName, baseName,
baseName,
baseName,
baseName,
routingType, interfaceId
);
}
Files.writeString(usecaseImplDir.resolve(baseName + "UseCaseImpl.java"), serviceImplContent);
if (isMci) {
String mciReqContent = """
package %s.%s.io;
import lombok.Data;
/**
* @package %s.%s.io
* @className %s_I
* @description AX HUB 시스템 처리 클래스
* @author %s
* @create %s
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* %s %s 최초생성
*
* </pre>
*/
@Data
public class %s_I {
/**
* EAI 시스템이 요구하는 수신자 번호 파라미터명
*/
private String phone;
/**
* EAI 시스템이 요구하는 메시지 내용 파라미터명
*/
private String content;
}
""".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.%s.io;
import lombok.Data;
/**
* @package %s.%s.io
* @className %s_O
* @description AX HUB 시스템 처리 클래스
* @author %s
* @create %s
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* %s %s 최초생성
*
* </pre>
*/
@Data
public class %s_O {
// TODO: Add response fields here
}
""".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 = """
package %s.converter;
import %s.dto.%sRequest;
import %s.dto.%sResponse;
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 %sConverter
* @description AX HUB 시스템 처리 클래스
* @author %s
* @create %s
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* %s %s 최초생성
*
* </pre>
*/
@Mapper(componentModel = "spring")
public interface %sConverter {
@Mapping(source = "phoneNumber", target = "phone")
@Mapping(source = "message", target = "content")
%s_I toLegacyRequest(%sRequest req);
@Mapping(source = "phone", target = "phoneNumber")
@Mapping(source = "content", target = "message")
%sRequest toRequest(%s_I mciReq);
// %sResponse toResponse(%s_O mciRes);
}
""".formatted(
bizPackage,
bizPackage, baseName,
bizPackage, baseName,
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 + "Converter.java"), converterContent);
log.append("\n=========================================\n");
log.append(" Scaffolding Complete! (Routing: " + routingType + ")\n");
log.append("=========================================\n");
log.append("[Usecase Interface] ").append(usecaseDir.resolve(baseName + "UseCase.java")).append("\n");
log.append("[Usecase Impl] ").append(usecaseImplDir.resolve(baseName + "UseCaseImpl.java")).append("\n");
log.append("[Request DTO] ").append(dtoDir.resolve(baseName + "Request.java")).append("\n");
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 + "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 = """
package %s.legacy;
import lombok.Data;
/**
* @package %s.legacy
* @className %sLegacyRequest
* @description AX HUB 시스템 처리 클래스
* @author %s
* @create %s
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* %s %s 최초생성
*
* </pre>
*/
@Data
public class %sLegacyRequest {
/**
* EAI 시스템이 요구하는 수신자 번호 파라미터명
*/
private String phone;
/**
* EAI 시스템이 요구하는 메시지 내용 파라미터명
*/
private String content;
}
""".formatted(bizPackage, bizPackage, baseName, author, createDate, createDate, author, baseName);
Files.writeString(legacyDtoDir.resolve(baseName + "LegacyRequest.java"), legacyReqContent);
String legacyResContent = """
package %s.legacy;
import lombok.Data;
/**
* @package %s.legacy
* @className %sLegacyResponse
* @description AX HUB 시스템 처리 클래스
* @author %s
* @create %s
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* %s %s 최초생성
*
* </pre>
*/
@Data
public class %sLegacyResponse {
// TODO: Add legacy response fields here
}
""".formatted(bizPackage, bizPackage, baseName, author, createDate, createDate, author, baseName);
Files.writeString(legacyDtoDir.resolve(baseName + "LegacyResponse.java"), legacyResContent);
String converterContent = """
package %s.converter;
import %s.dto.%sRequest;
import %s.dto.%sResponse;
import %s.legacy.%sLegacyRequest;
import %s.legacy.%sLegacyResponse;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
/**
* @package %s.converter
* @className %sConverter
* @description AX HUB 시스템 처리 클래스
* @author %s
* @create %s
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* %s %s 최초생성
*
* </pre>
*/
@Mapper(componentModel = "spring")
public interface %sConverter {
@Mapping(source = "phoneNumber", target = "phone")
@Mapping(source = "message", target = "content")
%sLegacyRequest toLegacyRequest(%sRequest req);
@Mapping(source = "phone", target = "phoneNumber")
@Mapping(source = "content", target = "message")
%sRequest toRequest(%sLegacyRequest legacyRequest);
// %sResponse toResponse(%sLegacyResponse legacyResponse);
}
""".formatted(
bizPackage,
bizPackage, baseName,
bizPackage, baseName,
bizPackage, baseName,
bizPackage, baseName,
bizPackage, baseName, author, createDate, createDate, author,
baseName, baseName, baseName, baseName, baseName, baseName, baseName
);
Files.writeString(converterDir.resolve(baseName + "Converter.java"), converterContent);
log.append("\n=========================================\n");
log.append(" Scaffolding Complete! (Routing: " + routingType + ")\n");
log.append("=========================================\n");
log.append("[Usecase Interface] ").append(usecaseDir.resolve(baseName + "UseCase.java")).append("\n");
log.append("[Usecase Impl] ").append(usecaseImplDir.resolve(baseName + "UseCaseImpl.java")).append("\n");
log.append("[Request DTO] ").append(dtoDir.resolve(baseName + "Request.java")).append("\n");
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 + "Converter.java")).append("\n");
}
log.append("\n Tip: ").append(interfaceId).append(" 목업 데이터를 mock-responses.json에 추가하세요.\n");
return log.toString();
}
private static String toToolName(String moduleName, String group, String baseName) {
String pod = moduleName.startsWith("dap-was-")
? moduleName.substring("dap-was-".length())
: "oth";
String normalizedName = baseName.replaceAll("([a-z0-9])([A-Z])", "$1 $2")
.toLowerCase(Locale.ROOT)
.replaceAll("[^a-z0-9]+", " ")
.trim();
String[] words = normalizedName.split("\\s+");
String service = words[0];
String action = words.length == 1 ? "execute" : words[words.length - 1];
return "%s.%s.%s.%s".formatted(
pod.toLowerCase(Locale.ROOT),
group.toLowerCase(Locale.ROOT),
service,
action);
}
private static String toPascalCase(String str) {
if (str == null || str.isEmpty()) {
return str;
}
StringBuilder result = new StringBuilder();
boolean capitalizeNext = true;
for (char c : str.toCharArray()) {
if (c == '_' || c == '-' || c == ' ') {
capitalizeNext = true;
} else if (capitalizeNext) {
result.append(Character.toUpperCase(c));
capitalizeNext = false;
} else {
result.append(c);
}
}
if (result.length() > 0) {
result.setCharAt(0, Character.toUpperCase(result.charAt(0)));
}
return result.toString();
}
}

View File

@@ -1,28 +0,0 @@
package io.shinhanlife.dap.lib.util;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.nio.file.Files;
import java.nio.file.Path;
import org.junit.jupiter.api.Test;
class ToolScaffolderTest {
@Test
void generatesManifestReadyToolAndOutputSchemaDto() throws Exception {
String moduleName = "build/scaffold-manifest-test";
ToolScaffolder.scaffold("claim search", "CLM0001", "청구 조회", "cmm", "HTTP", moduleName,
"tester", "2026.08.04", true, null);
Path root = Path.of(moduleName, "src/main/java/io/shinhanlife/dap/mcc/biz/cmm");
String useCase = Files.readString(root.resolve("usecase/ClaimSearchUseCase.java"));
String response = Files.readString(root.resolve("dto/ClaimSearchResponse.java"));
assertTrue(useCase.contains("name = \"oth.cmm.claim.search\""));
assertTrue(useCase.contains("version = \"1.0.0\""));
assertTrue(useCase.contains("timeoutMillis = 300000L"));
assertTrue(response.contains("@McpOutputSchema"));
assertTrue(response.contains("@McpValidation"));
}
}