From b199dad455c504df6bbd87b81641427559f7cbbe Mon Sep 17 00:00:00 2001 From: jade Date: Fri, 10 Jul 2026 10:30:59 +0900 Subject: [PATCH] fix: ToolSourceUpdater bug and add docker volume mount --- .../axhub/common/util/PodScaffolder.java | 9 ++-- .../axhub/common/util/ToolScaffolder.java | 9 ++-- .../axhub/common/util/ToolSourceUpdater.java | 24 ++++++--- .../glow/util/GlowMciParserTest.java | 50 +++++++++++++++++++ axhub-tool-hr/Dockerfile | 4 ++ axhub-tool-hr/build.gradle | 10 ++++ .../biz/mcp/tool/hr/HrToolApplication.java | 28 +++++++++++ .../resources/application-local.properties | 32 ++++++++++++ .../src/main/resources/application-local.yml | 12 +++++ .../src/main/resources/application.properties | 2 + .../mcp/tool/service/BondIssueService.java | 4 +- .../tool/service/CommonUtilityService.java | 5 ++ docker-compose.yml | 3 ++ 13 files changed, 178 insertions(+), 14 deletions(-) create mode 100644 axhub-common/src/test/java/io/shinhanlife/glow/util/GlowMciParserTest.java create mode 100644 axhub-tool-hr/Dockerfile create mode 100644 axhub-tool-hr/build.gradle create mode 100644 axhub-tool-hr/src/main/java/io/shinhanlife/axhub/biz/mcp/tool/hr/HrToolApplication.java create mode 100644 axhub-tool-hr/src/main/resources/application-local.properties create mode 100644 axhub-tool-hr/src/main/resources/application-local.yml create mode 100644 axhub-tool-hr/src/main/resources/application.properties diff --git a/axhub-common/src/main/java/io/shinhanlife/axhub/common/util/PodScaffolder.java b/axhub-common/src/main/java/io/shinhanlife/axhub/common/util/PodScaffolder.java index c95a89c..7cfc3b9 100644 --- a/axhub-common/src/main/java/io/shinhanlife/axhub/common/util/PodScaffolder.java +++ b/axhub-common/src/main/java/io/shinhanlife/axhub/common/util/PodScaffolder.java @@ -44,7 +44,10 @@ public class PodScaffolder { } public static String scaffoldPod(String moduleName, String portStr, String shortName, String author, String createDate) throws IOException { - Path modulePath = Paths.get(moduleName); + 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; } @@ -202,7 +205,7 @@ public class PodScaffolder { Files.writeString(resPath.resolve("logback-spring.xml"), logbackXml); log.append("[5/6] settings.gradle 에 모듈 등록 중...\n"); - Path settingsPath = Paths.get("settings.gradle"); + Path settingsPath = rootDir.resolve(Paths.get("settings.gradle")); if (Files.exists(settingsPath)) { String settings = Files.readString(settingsPath); if (!settings.contains("include '" + moduleName + "'")) { @@ -211,7 +214,7 @@ public class PodScaffolder { } log.append("[6/6] docker-compose.yml 에 서비스 추가 중...\n"); - Path dockerComposePath = Paths.get("docker-compose.yml"); + Path dockerComposePath = rootDir.resolve(Paths.get("docker-compose.yml")); if (Files.exists(dockerComposePath)) { String compose = Files.readString(dockerComposePath); String serviceName = moduleName.replace("axhub-", ""); // e.g. tool-payment diff --git a/axhub-common/src/main/java/io/shinhanlife/axhub/common/util/ToolScaffolder.java b/axhub-common/src/main/java/io/shinhanlife/axhub/common/util/ToolScaffolder.java index aabca1a..5fe3022 100644 --- a/axhub-common/src/main/java/io/shinhanlife/axhub/common/util/ToolScaffolder.java +++ b/axhub-common/src/main/java/io/shinhanlife/axhub/common/util/ToolScaffolder.java @@ -81,8 +81,11 @@ public class ToolScaffolder { } public static String scaffold(String baseName, String interfaceId, String description, String group, String routingType, String moduleName, String author, String createDate, boolean register) throws IOException { - Path serviceDir = Paths.get(moduleName, BASE_PACKAGE_PATH, "service"); - Path dtoDir = Paths.get(moduleName, BASE_PACKAGE_PATH, "dto"); + String envSourceDir = System.getenv("AXHUB_SOURCE_DIR"); + Path rootDir = envSourceDir != null ? Paths.get(envSourceDir) : Paths.get("."); + + Path serviceDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "service")); + Path dtoDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "dto")); Files.createDirectories(serviceDir); Files.createDirectories(dtoDir); @@ -204,7 +207,7 @@ public class ToolScaffolder { Files.writeString(serviceDir.resolve(baseName + "Service.java"), serviceContent); // Append to YAML - Path resourcesDir = Paths.get(moduleName, "src/main/resources"); + Path resourcesDir = rootDir.resolve(Paths.get(moduleName, "src/main/resources")); Files.createDirectories(resourcesDir); Path yamlPath = resourcesDir.resolve("application-local.yml"); diff --git a/axhub-common/src/main/java/io/shinhanlife/axhub/common/util/ToolSourceUpdater.java b/axhub-common/src/main/java/io/shinhanlife/axhub/common/util/ToolSourceUpdater.java index bf3b65d..eae8a86 100644 --- a/axhub-common/src/main/java/io/shinhanlife/axhub/common/util/ToolSourceUpdater.java +++ b/axhub-common/src/main/java/io/shinhanlife/axhub/common/util/ToolSourceUpdater.java @@ -14,7 +14,8 @@ public class ToolSourceUpdater { public static void updateToolSource(String toolName, String domainGroup, String description, boolean register) throws Exception { // 1. Find all *Service.java files in axhub-tool-* directories - Path rootDir = Paths.get("."); + String envSourceDir = System.getenv("AXHUB_SOURCE_DIR"); + Path rootDir = envSourceDir != null ? Paths.get(envSourceDir) : Paths.get("."); List javaFiles; try (Stream paths = Files.walk(rootDir)) { @@ -29,7 +30,13 @@ public class ToolSourceUpdater { String content = null; // 2. Find the specific file for the tool - Pattern namePattern = Pattern.compile("@McpFunction\\\\s*\\\\([^)]*name\\\\s*=\\\\s*\\\"" + Pattern.quote(toolName) + "\\\"", Pattern.DOTALL); + String functionName = toolName; + if (toolName.contains("_")) { + functionName = toolName.substring(toolName.indexOf("_") + 1); + } + + Pattern namePattern = Pattern.compile("@McpFunction\\s*\\([^)]*name\\s*=\\s*\"" + Pattern.quote(toolName) + "\"", Pattern.DOTALL); + Pattern namePattern2 = Pattern.compile("@McpFunction\\s*\\([^)]*name\\s*=\\s*\"" + Pattern.quote(functionName) + "\"", Pattern.DOTALL); for (Path path : javaFiles) { String text = Files.readString(path); @@ -37,6 +44,11 @@ public class ToolSourceUpdater { targetFile = path; content = text; break; + } else if (namePattern2.matcher(text).find()) { + targetFile = path; + content = text; + toolName = functionName; // Use baseName for subsequent replacements + break; } } @@ -46,7 +58,7 @@ public class ToolSourceUpdater { // 3. Update @McpTool group if (domainGroup != null && !domainGroup.trim().isEmpty()) { - Pattern groupPattern = Pattern.compile("(@McpTool\\\\s*\\\\([^)]*group\\\\s*=\\\\s*\\\")([^\\\"]+)(\\\")", Pattern.DOTALL); + Pattern groupPattern = Pattern.compile("(@McpTool\\s*\\([^)]*group\\s*=\\s*\")([^\"]+)(\")", Pattern.DOTALL); Matcher groupMatcher = groupPattern.matcher(content); if (groupMatcher.find()) { content = groupMatcher.replaceFirst("$1" + domainGroup + "$3"); @@ -55,7 +67,7 @@ public class ToolSourceUpdater { // 4. Update @McpFunction description if (description != null) { - Pattern funcPattern = Pattern.compile("(@McpFunction\\\\s*\\\\([^)]*name\\\\s*=\\\\s*\\\"" + Pattern.quote(toolName) + "\\\"[^)]*description\\\\s*=\\\\s*\\\")([^\\\"]+)(\\\")", Pattern.DOTALL); + Pattern funcPattern = Pattern.compile("(@McpFunction\\s*\\([^)]*name\\s*=\\s*\"" + Pattern.quote(toolName) + "\"[^)]*description\\s*=\\s*\")([^\"]+)(\")", Pattern.DOTALL); Matcher funcMatcher = funcPattern.matcher(content); if (funcMatcher.find()) { content = funcMatcher.replaceFirst("$1" + description.replace("\\", "\\\\").replace("$", "\\\\$") + "$3"); @@ -63,12 +75,12 @@ public class ToolSourceUpdater { } // 5. Update register flag - Pattern regPattern = Pattern.compile("(@McpFunction\\\\s*\\\\([^)]*name\\\\s*=\\\\s*\\\"" + Pattern.quote(toolName) + "\\\"[^)]*register\\\\s*=\\\\s*)(true|false)([^a-zA-Z0-9])", Pattern.DOTALL); + Pattern regPattern = Pattern.compile("(@McpFunction\\s*\\([^)]*name\\s*=\\s*\"" + Pattern.quote(toolName) + "\"[^)]*register\\s*=\\s*)(true|false)([^a-zA-Z0-9])", Pattern.DOTALL); Matcher regMatcher = regPattern.matcher(content); if (regMatcher.find()) { content = regMatcher.replaceFirst("$1" + register + "$3"); } else { - Pattern addRegPattern = Pattern.compile("(@McpFunction\\\\s*\\\\([^)]*name\\\\s*=\\\\s*\\\"" + Pattern.quote(toolName) + "\\\")", Pattern.DOTALL); + Pattern addRegPattern = Pattern.compile("(@McpFunction\\s*\\([^)]*name\\s*=\\s*\"" + Pattern.quote(toolName) + "\")", Pattern.DOTALL); Matcher addRegMatcher = addRegPattern.matcher(content); if (addRegMatcher.find()) { content = addRegMatcher.replaceFirst("$1, register = " + register); diff --git a/axhub-common/src/test/java/io/shinhanlife/glow/util/GlowMciParserTest.java b/axhub-common/src/test/java/io/shinhanlife/glow/util/GlowMciParserTest.java new file mode 100644 index 0000000..1ead27b --- /dev/null +++ b/axhub-common/src/test/java/io/shinhanlife/glow/util/GlowMciParserTest.java @@ -0,0 +1,50 @@ +package io.shinhanlife.glow.util; + +import io.shinhanlife.glow.GlowMciFieldInfo; +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 GlowMciParserTest { + + public static class DummyMciDto { + @GlowMciFieldInfo(order = 1, length = 10) + private String customerId; + + @GlowMciFieldInfo(order = 2, length = 15) + private String name; + + @GlowMciFieldInfo(order = 3, length = 3) + private int age; + + public String getCustomerId() { return customerId; } + public String getName() { return name; } + public int getAge() { return age; } + } + + @Test + @DisplayName("고정 길이 MCI 문자열을 DTO로 파싱하는 테스트") + public void testParseFixedLengthString() { + // given: 고정 길이 텍스트 (총 28자리) + // ID(10) + Name(15) + Age(3) + String rawMciString = "CUST000001KIM SHINHAN 035"; + + // when + DummyMciDto result = GlowMciParser.parse(rawMciString, DummyMciDto.class); + + // then + System.out.println("=================================================="); + System.out.println("✅ [원본 MCI 전문] : [" + rawMciString + "]"); + System.out.println("✅ [파싱된 ID (10자리)] : [" + result.getCustomerId() + "]"); + System.out.println("✅ [파싱된 Name (15자리)] : [" + result.getName() + "]"); + System.out.println("✅ [파싱된 Age (3자리)] : [" + result.getAge() + "]"); + System.out.println("=================================================="); + + assertNotNull(result); + assertEquals("CUST000001", result.getCustomerId()); + assertEquals("KIM SHINHAN", result.getName()); + assertEquals(35, result.getAge()); + } +} diff --git a/axhub-tool-hr/Dockerfile b/axhub-tool-hr/Dockerfile new file mode 100644 index 0000000..3343e67 --- /dev/null +++ b/axhub-tool-hr/Dockerfile @@ -0,0 +1,4 @@ +FROM eclipse-temurin:21-jdk-alpine +WORKDIR /app +COPY build/libs/axhub-tool-hr-0.0.1-SNAPSHOT.jar app.jar +ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/axhub-tool-hr/build.gradle b/axhub-tool-hr/build.gradle new file mode 100644 index 0000000..997adcb --- /dev/null +++ b/axhub-tool-hr/build.gradle @@ -0,0 +1,10 @@ +plugins { + id 'org.springframework.boot' +} +dependencies { + implementation project(':axhub-tool-core') +} +dependencies { + compileOnly 'org.projectlombok:lombok:1.18.32' + annotationProcessor 'org.projectlombok:lombok:1.18.32' +} diff --git a/axhub-tool-hr/src/main/java/io/shinhanlife/axhub/biz/mcp/tool/hr/HrToolApplication.java b/axhub-tool-hr/src/main/java/io/shinhanlife/axhub/biz/mcp/tool/hr/HrToolApplication.java new file mode 100644 index 0000000..11da8ae --- /dev/null +++ b/axhub-tool-hr/src/main/java/io/shinhanlife/axhub/biz/mcp/tool/hr/HrToolApplication.java @@ -0,0 +1,28 @@ +package io.shinhanlife.axhub.biz.mcp.tool.hr; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cache.annotation.EnableCaching; + +/** + * @package io.shinhanlife.axhub.biz.mcp.tool.hr + * @className HrToolApplication + * @description AX HUB 시스템 처리 클래스 + * @author + * @create + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ *       최초생성
+ *
+ * 
+ */ +@SpringBootApplication(scanBasePackages = {"io.shinhanlife.axhub.biz.mcp.tool", "io.shinhanlife.axhub.biz.mcp.adapter", "io.shinhanlife.axhub.common.mcp", "io.shinhanlife.axhub.common.config"}) +@org.springframework.boot.context.properties.ConfigurationPropertiesScan(basePackages = {"io.shinhanlife.axhub.biz.mcp.tool", "io.shinhanlife.axhub.biz.mcp.adapter", "io.shinhanlife.axhub.common.mcp", "io.shinhanlife.axhub.common.config"}) +@EnableCaching +public class HrToolApplication { + public static void main(String[] args) { + SpringApplication.run(HrToolApplication.class, args); + } +} diff --git a/axhub-tool-hr/src/main/resources/application-local.properties b/axhub-tool-hr/src/main/resources/application-local.properties new file mode 100644 index 0000000..54d056d --- /dev/null +++ b/axhub-tool-hr/src/main/resources/application-local.properties @@ -0,0 +1,32 @@ +# Local 환경 전용 설정 (H2 메모리 DB 등) +spring.datasource.url=jdbc:p6spy:h2:mem:testdb;DB_CLOSE_DELAY=-1; +spring.datasource.driverClassName=com.p6spy.engine.spy.P6SpyDriver +spring.datasource.username=sa +spring.datasource.password=password + +spring.h2.console.enabled=true +# EIMS 동적 라우팅 접속 정보 +eims.http.url=http://localhost:${server.port}/api/gateway +eims.tcp.host=127.0.0.1 +eims.tcp.port=8090 +eims.tcp.timeout=5000 +eims.jsp.form.url=http://localhost:${server.port}/mock/jsp-form +eims.jsp.json.url=http://localhost:${server.port}/mock/jsp-json +eims.mci.url=http://localhost:${server.port}/api/mock/esb/api +eims.mcistring.url=http://localhost:${server.port}/api/mock/esb/string + +# API 보안 키 설정 +mcp.security.api-keys.SHINHAN_MCP_SECRET_KEY_2026=mcp-client-1 +mcp.security.api-keys.SHINHAN_MCP_TEST_KEY_9999=mcp-client-2 +mcp.security.tenant-domains.mcp-client-1=CUSTOMER,COMMON +mcp.security.tenant-domains.mcp-client-2=ALL + +# Gateway/Tool URLs +axhub.gateway.url=http://localhost:8081 +axhub.tool.url=http://localhost:${server.port} + +# Disable Kafka +spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration + +# Suppress Kafka Connection Logs +logging.level.org.apache.kafka=ERROR diff --git a/axhub-tool-hr/src/main/resources/application-local.yml b/axhub-tool-hr/src/main/resources/application-local.yml new file mode 100644 index 0000000..2c12d7c --- /dev/null +++ b/axhub-tool-hr/src/main/resources/application-local.yml @@ -0,0 +1,12 @@ +spring: + application: + name: axhub-tool-hr + config: + import: "classpath:config/application-glow-local.yml" + +server: + port: 8086 + +axhub: + tool: + url: "http://tool-hr:8086" diff --git a/axhub-tool-hr/src/main/resources/application.properties b/axhub-tool-hr/src/main/resources/application.properties new file mode 100644 index 0000000..1a4d891 --- /dev/null +++ b/axhub-tool-hr/src/main/resources/application.properties @@ -0,0 +1,2 @@ +spring.profiles.active=local +mcp.namespace=hr diff --git a/axhub-tool-other/src/main/java/io/shinhanlife/axhub/biz/mcp/tool/service/BondIssueService.java b/axhub-tool-other/src/main/java/io/shinhanlife/axhub/biz/mcp/tool/service/BondIssueService.java index 5177a8b..57d638c 100644 --- a/axhub-tool-other/src/main/java/io/shinhanlife/axhub/biz/mcp/tool/service/BondIssueService.java +++ b/axhub-tool-other/src/main/java/io/shinhanlife/axhub/biz/mcp/tool/service/BondIssueService.java @@ -25,12 +25,12 @@ import io.shinhanlife.axhub.biz.mcp.tool.dto.BondIssueReq; */ public class BondIssueService extends AbstractMcpToolService { - @McpFunction(name = "check", description = "발행 가능 여부 조회", prompt = "디지털 증권 발행 한도가 충분한지 확인해줘.", mappingId = "BOND_001") + @McpFunction(name = "check", register = true, description = "발행 가능 여부 조회 테스트", prompt = "디지털 증권 발행 한도가 충분한지 확인해줘.", mappingId = "BOND_001") public Object check(BondCheckReq data) { return executeLegacy("EAI", "BOND_001", data); } - @McpFunction(name = "issue", description = "증권 발행", prompt = "디지털 증권 발행 프로세스를 실행해.", mappingId = "BOND_002") + @McpFunction(name = "issue", register = true, description = "증권 발행 테스트", prompt = "디지털 증권 발행 프로세스를 실행해.", mappingId = "BOND_002") public Object issue(BondIssueReq data) { return executeLegacy("EAI", "BOND_002", data); } diff --git a/axhub-tool-other/src/main/java/io/shinhanlife/axhub/biz/mcp/tool/service/CommonUtilityService.java b/axhub-tool-other/src/main/java/io/shinhanlife/axhub/biz/mcp/tool/service/CommonUtilityService.java index 6b11734..bd6f600 100644 --- a/axhub-tool-other/src/main/java/io/shinhanlife/axhub/biz/mcp/tool/service/CommonUtilityService.java +++ b/axhub-tool-other/src/main/java/io/shinhanlife/axhub/biz/mcp/tool/service/CommonUtilityService.java @@ -36,4 +36,9 @@ public class CommonUtilityService extends AbstractMcpToolService { return executeLegacy("HTTP", "HR_VAC_02", data); } + @McpFunction(name = "secret_tool", description = "비공개 툴 테스트", prompt = "숨겨진 툴 강제 호출", mappingId = "SECRET_001", visible = false) + public Object secretTool(LeaveCountReq data) { + return executeLegacy("HTTP", "SECRET_001", data); + } + } \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index a68b95c..b86bd27 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,6 +16,8 @@ services: context: ./axhub-gateway ports: - "8081:8081" + volumes: + - ./:/src depends_on: redis: condition: service_healthy @@ -25,6 +27,7 @@ services: - SPRING_REDIS_PORT=6379 - SPRING_DATA_REDIS_HOST=redis - SPRING_DATA_REDIS_PORT=6379 + - AXHUB_SOURCE_DIR=/src tool-sms: build: