diff --git a/build.gradle b/build.gradle index 5ce10eb..6d3a199 100644 --- a/build.gradle +++ b/build.gradle @@ -77,7 +77,6 @@ dependencies { } tasks.withType(JavaCompile) { - options.compilerArgs = [ - '-Amapstruct.defaultComponentModel=spring' - ] + options.compilerArgs << '-parameters' + options.compilerArgs << '-Amapstruct.defaultComponentModel=spring' } diff --git a/src/main/java/io/shinhanlife/AxHubGatewayApplication.java b/src/main/java/io/shinhanlife/AxHubGatewayApplication.java new file mode 100644 index 0000000..736db6f --- /dev/null +++ b/src/main/java/io/shinhanlife/AxHubGatewayApplication.java @@ -0,0 +1,22 @@ +package io.shinhanlife; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +import java.util.Collections; + +@SpringBootApplication +@ComponentScan(basePackages = { + "io.shinhanlife.axhub.biz.mcp.gateway", + "io.shinhanlife.axhub.biz.mcp.adapter", + "io.shinhanlife.axhub.common.mcp" +}) +public class AxHubGatewayApplication { + public static void main(String[] args) { + SpringApplication app = new SpringApplication(AxHubGatewayApplication.class); + // Gateway는 8081 포트에서 실행 + app.setDefaultProperties(Collections.singletonMap("server.port", "8081")); + app.run(args); + } +} diff --git a/src/main/java/io/shinhanlife/AxHubToolApplication.java b/src/main/java/io/shinhanlife/AxHubToolApplication.java new file mode 100644 index 0000000..9c68546 --- /dev/null +++ b/src/main/java/io/shinhanlife/AxHubToolApplication.java @@ -0,0 +1,31 @@ +package io.shinhanlife; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.scheduling.annotation.EnableScheduling; + +import java.util.Collections; + +@SpringBootApplication +@EnableScheduling +@ComponentScan(basePackages = { + "io.shinhanlife.axhub.biz.mcp.tool", + "io.shinhanlife.axhub.biz.mcp.adapter", + "io.shinhanlife.axhub.common.mcp" +}) +public class AxHubToolApplication { + public static void main(String[] args) { + // 기본 포트는 8082로 설정하되, 파라미터(--server.port=808x)가 있으면 해당 포트로 설정 + String port = "8082"; + for (String arg : args) { + if (arg.startsWith("--server.port=")) { + port = arg.substring("--server.port=".length()); + } + } + System.setProperty("server.port", port); + + SpringApplication app = new SpringApplication(AxHubToolApplication.class); + app.run(args); + } +} diff --git a/src/main/java/io/shinhanlife/axhub/biz/mcp/gateway/controller/McpRouterController.java b/src/main/java/io/shinhanlife/axhub/biz/mcp/gateway/controller/McpRouterController.java index 44509b4..5cd4686 100644 --- a/src/main/java/io/shinhanlife/axhub/biz/mcp/gateway/controller/McpRouterController.java +++ b/src/main/java/io/shinhanlife/axhub/biz/mcp/gateway/controller/McpRouterController.java @@ -153,7 +153,11 @@ public class McpRouterController { @PostMapping("/registry/heartbeat") public ResponseEntity heartbeat(@RequestBody String toolName) { - redisRegistryService.refreshHeartbeat(toolName); - return ResponseEntity.ok("Heartbeat updated"); + boolean success = redisRegistryService.refreshHeartbeat(toolName); + if (success) { + return ResponseEntity.ok("Heartbeat updated"); + } else { + return ResponseEntity.status(404).body("Tool not found"); + } } } diff --git a/src/main/java/io/shinhanlife/axhub/biz/mcp/gateway/registry/RedisRegistryService.java b/src/main/java/io/shinhanlife/axhub/biz/mcp/gateway/registry/RedisRegistryService.java index 1771ff3..7318eb7 100644 --- a/src/main/java/io/shinhanlife/axhub/biz/mcp/gateway/registry/RedisRegistryService.java +++ b/src/main/java/io/shinhanlife/axhub/biz/mcp/gateway/registry/RedisRegistryService.java @@ -34,13 +34,15 @@ public class RedisRegistryService { /** * 하트비트 갱신 (TTL 초기화) */ - public void refreshHeartbeat(String toolName) { + public boolean refreshHeartbeat(String toolName) { String key = KEY_PREFIX + toolName; Boolean exists = redisTemplate.expire(key, DEFAULT_TTL); if (Boolean.TRUE.equals(exists)) { log.debug(" [RedisRegistry] 하트비트 갱신: {}", toolName); + return true; } else { log.warn(" [RedisRegistry] 존재하지 않는 툴에 대한 하트비트 요청: {}", toolName); + return false; } } public List getAvailablePods(String toolName) { diff --git a/src/main/java/io/shinhanlife/axhub/biz/mcp/tool/registry/MockToolAutoRegistrar.java b/src/main/java/io/shinhanlife/axhub/biz/mcp/tool/registry/MockToolAutoRegistrar.java index 85869b4..879ebf0 100644 --- a/src/main/java/io/shinhanlife/axhub/biz/mcp/tool/registry/MockToolAutoRegistrar.java +++ b/src/main/java/io/shinhanlife/axhub/biz/mcp/tool/registry/MockToolAutoRegistrar.java @@ -212,6 +212,7 @@ public class MockToolAutoRegistrar { HttpHeaders headers = createHeaders(); headers.setContentType(MediaType.TEXT_PLAIN); + boolean needsReRegistration = false; for (ToolConfig tool : TOOLS) { try { String heartbeatUrl = gatewayUrl + "/registry/heartbeat"; @@ -224,8 +225,14 @@ public class MockToolAutoRegistrar { log.info(" [MockTool] {} Heartbeat 서버 전송 완료", tool.getName()); } catch (Exception e) { log.error(" [MockTool] {} Heartbeat 전송 실패: {}", tool.getName(), e.getMessage()); + needsReRegistration = true; } } + + if (needsReRegistration) { + log.info(" [MockTool] 하트비트 실패로 인해 전체 툴 재등록을 시도합니다."); + registerOnStartup(); + } } @EventListener(ContextClosedEvent.class) diff --git a/src/main/resources/application-local.properties b/src/main/resources/application-local.properties index f71c299..81d1474 100644 --- a/src/main/resources/application-local.properties +++ b/src/main/resources/application-local.properties @@ -1,7 +1,26 @@ -# Local 환경 전용 설정 (H2 메모리 DB 등) +# 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:8081/api/gateway +eims.tcp.host=127.0.0.1 +eims.tcp.port=8090 +eims.tcp.timeout=5000 +eims.jsp.form.url=http://localhost:8081/mock/jsp-form +eims.jsp.json.url=http://localhost:8081/mock/jsp-json +eims.mci.url=http://localhost:8081/mock/esb/api + +# 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 +mcp.adapter.url=http://localhost:8081/rpc/v1/execute +mcp.gateway.url=http://localhost:8081/mcp/api/v1 +mcp.tool.host=localhost diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index e1ef928..bd90c42 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,5 +1,5 @@ spring.application.name=axhub-admin -server.port=8080 +server.port=8081 # 기본적으로 local 환경을 실행 spring.profiles.active=local diff --git a/src/main/resources/mock-responses.json b/src/main/resources/mock-responses.json new file mode 100644 index 0000000..ea1674d --- /dev/null +++ b/src/main/resources/mock-responses.json @@ -0,0 +1,99 @@ +{ + "BILL_001": { + "status": "SUCCESS", + "message": "청구 심사 상태 조회가 완료되었습니다.", + "data": { + "billingStatus": "PROCESSING", + "expectedCompletionDate": "2026-07-05" + } + }, + "BILL_002": { + "status": "SUCCESS", + "message": "청구 처리가 완료되었습니다.", + "data": { + "processId": "PRC-998811", + "result": "APPROVED" + } + }, + "BOND_001": { + "status": "SUCCESS", + "message": "디지털 증권 발행 가능 한도 조회가 완료되었습니다.", + "data": { + "availableLimit": 500000000, + "currency": "KRW" + } + }, + "BOND_002": { + "status": "SUCCESS", + "message": "디지털 증권 발행이 성공적으로 완료되었습니다.", + "data": { + "bondId": "BND-2026-07-04-1234", + "issuedAmount": 10000000 + } + }, + "HR_VAC_01": { + "status": "SUCCESS", + "message": "연차 휴가 등록이 완료되었습니다.", + "data": { + "vacationId": "VAC-8877", + "status": "APPROVED" + } + }, + "HR_VAC_02": { + "status": "SUCCESS", + "message": "잔여 연차 일수 조회가 완료되었습니다.", + "data": { + "totalDays": 15, + "usedDays": 3, + "remainingDays": 12 + } + }, + "COM_SMS_01": { + "status": "SUCCESS", + "message": "SMS 발송이 성공적으로 완료되었습니다.", + "data": { + "messageId": "SMS-11223344", + "sentTime": "2026-07-04 10:00:00" + } + }, + "COM_EML_01": { + "status": "SUCCESS", + "message": "이메일 발송이 성공적으로 완료되었습니다.", + "data": { + "emailId": "EML-998877", + "sentTime": "2026-07-04 10:00:00" + } + }, + "CNTR_001": { + "status": "SUCCESS", + "message": "계약 상태 조회가 완료되었습니다.", + "data": { + "contractStatus": "ACTIVE", + "startDate": "2024-01-01" + } + }, + "CNTR_002": { + "status": "SUCCESS", + "message": "계약 상세 내역 조회가 완료되었습니다.", + "data": { + "productName": "신한 라이프 스마트 연금보험", + "monthlyPremium": 500000 + } + }, + "CRM_001": { + "status": "SUCCESS", + "message": "고객 등급 조회가 완료되었습니다.", + "data": { + "customerGrade": "VIP", + "loyaltyPoints": 15000 + } + }, + "CRM_002": { + "status": "SUCCESS", + "message": "고객 상세 정보 조회가 완료되었습니다.", + "data": { + "address": "서울특별시 중구 세종대로 9", + "phoneNumber": "010-XXXX-XXXX" + } + } +}