feat: Separate Gateway and Tool server architectures, implement Tool Auto-Registration, and add mock response mapping
This commit is contained in:
@@ -77,7 +77,6 @@ dependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType(JavaCompile) {
|
tasks.withType(JavaCompile) {
|
||||||
options.compilerArgs = [
|
options.compilerArgs << '-parameters'
|
||||||
'-Amapstruct.defaultComponentModel=spring'
|
options.compilerArgs << '-Amapstruct.defaultComponentModel=spring'
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
22
src/main/java/io/shinhanlife/AxHubGatewayApplication.java
Normal file
22
src/main/java/io/shinhanlife/AxHubGatewayApplication.java
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/main/java/io/shinhanlife/AxHubToolApplication.java
Normal file
31
src/main/java/io/shinhanlife/AxHubToolApplication.java
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -153,7 +153,11 @@ public class McpRouterController {
|
|||||||
|
|
||||||
@PostMapping("/registry/heartbeat")
|
@PostMapping("/registry/heartbeat")
|
||||||
public ResponseEntity<String> heartbeat(@RequestBody String toolName) {
|
public ResponseEntity<String> heartbeat(@RequestBody String toolName) {
|
||||||
redisRegistryService.refreshHeartbeat(toolName);
|
boolean success = redisRegistryService.refreshHeartbeat(toolName);
|
||||||
return ResponseEntity.ok("Heartbeat updated");
|
if (success) {
|
||||||
|
return ResponseEntity.ok("Heartbeat updated");
|
||||||
|
} else {
|
||||||
|
return ResponseEntity.status(404).body("Tool not found");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,13 +34,15 @@ public class RedisRegistryService {
|
|||||||
/**
|
/**
|
||||||
* 하트비트 갱신 (TTL 초기화)
|
* 하트비트 갱신 (TTL 초기화)
|
||||||
*/
|
*/
|
||||||
public void refreshHeartbeat(String toolName) {
|
public boolean refreshHeartbeat(String toolName) {
|
||||||
String key = KEY_PREFIX + toolName;
|
String key = KEY_PREFIX + toolName;
|
||||||
Boolean exists = redisTemplate.expire(key, DEFAULT_TTL);
|
Boolean exists = redisTemplate.expire(key, DEFAULT_TTL);
|
||||||
if (Boolean.TRUE.equals(exists)) {
|
if (Boolean.TRUE.equals(exists)) {
|
||||||
log.debug(" [RedisRegistry] 하트비트 갱신: {}", toolName);
|
log.debug(" [RedisRegistry] 하트비트 갱신: {}", toolName);
|
||||||
|
return true;
|
||||||
} else {
|
} else {
|
||||||
log.warn(" [RedisRegistry] 존재하지 않는 툴에 대한 하트비트 요청: {}", toolName);
|
log.warn(" [RedisRegistry] 존재하지 않는 툴에 대한 하트비트 요청: {}", toolName);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public List<String> getAvailablePods(String toolName) {
|
public List<String> getAvailablePods(String toolName) {
|
||||||
|
|||||||
@@ -212,6 +212,7 @@ public class MockToolAutoRegistrar {
|
|||||||
HttpHeaders headers = createHeaders();
|
HttpHeaders headers = createHeaders();
|
||||||
headers.setContentType(MediaType.TEXT_PLAIN);
|
headers.setContentType(MediaType.TEXT_PLAIN);
|
||||||
|
|
||||||
|
boolean needsReRegistration = false;
|
||||||
for (ToolConfig tool : TOOLS) {
|
for (ToolConfig tool : TOOLS) {
|
||||||
try {
|
try {
|
||||||
String heartbeatUrl = gatewayUrl + "/registry/heartbeat";
|
String heartbeatUrl = gatewayUrl + "/registry/heartbeat";
|
||||||
@@ -224,8 +225,14 @@ public class MockToolAutoRegistrar {
|
|||||||
log.info(" [MockTool] {} Heartbeat 서버 전송 완료", tool.getName());
|
log.info(" [MockTool] {} Heartbeat 서버 전송 완료", tool.getName());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error(" [MockTool] {} Heartbeat 전송 실패: {}", tool.getName(), e.getMessage());
|
log.error(" [MockTool] {} Heartbeat 전송 실패: {}", tool.getName(), e.getMessage());
|
||||||
|
needsReRegistration = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (needsReRegistration) {
|
||||||
|
log.info(" [MockTool] 하트비트 실패로 인해 전체 툴 재등록을 시도합니다.");
|
||||||
|
registerOnStartup();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventListener(ContextClosedEvent.class)
|
@EventListener(ContextClosedEvent.class)
|
||||||
|
|||||||
@@ -1,7 +1,26 @@
|
|||||||
# Local 환경 전용 설정 (H2 메모리 DB 등)
|
# Local ?˜ê²½ ?„ìš© ?¤ì • (H2 메모ë¦?DB ??
|
||||||
spring.datasource.url=jdbc:p6spy:h2:mem:testdb;DB_CLOSE_DELAY=-1;
|
spring.datasource.url=jdbc:p6spy:h2:mem:testdb;DB_CLOSE_DELAY=-1;
|
||||||
spring.datasource.driverClassName=com.p6spy.engine.spy.P6SpyDriver
|
spring.datasource.driverClassName=com.p6spy.engine.spy.P6SpyDriver
|
||||||
spring.datasource.username=sa
|
spring.datasource.username=sa
|
||||||
spring.datasource.password=password
|
spring.datasource.password=password
|
||||||
|
|
||||||
spring.h2.console.enabled=true
|
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
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
spring.application.name=axhub-admin
|
spring.application.name=axhub-admin
|
||||||
server.port=8080
|
server.port=8081
|
||||||
|
|
||||||
# 기본적으로 local 환경을 실행
|
# 기본적으로 local 환경을 실행
|
||||||
spring.profiles.active=local
|
spring.profiles.active=local
|
||||||
|
|||||||
99
src/main/resources/mock-responses.json
Normal file
99
src/main/resources/mock-responses.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user