refactor: apply Option B, remove heartbeat and register tools once on startup
Some checks failed
Deploy to OCIWP / deploy (push) Failing after 44s
Some checks failed
Deploy to OCIWP / deploy (push) Failing after 44s
This commit is contained in:
@@ -18,8 +18,7 @@ dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3'
|
||||
|
||||
// Gateway가 /mcp Endpoint를 MCP Server로 노출하도록 지원합니다.
|
||||
implementation 'org.springframework.ai:spring-ai-starter-mcp-server-webmvc'
|
||||
// Gateway MCP Server/Client는 공통 모듈이 제공하는 MCP Java SDK 2.0.0을 사용합니다.
|
||||
|
||||
// ChatClient를 통해 OpenAI/OpenRouter 호환 모델을 호출합니다.
|
||||
implementation 'org.springframework.ai:spring-ai-starter-model-openai'
|
||||
|
||||
@@ -250,13 +250,5 @@ public class McpRouterController {
|
||||
return ResponseEntity.ok("Deregistered");
|
||||
}
|
||||
|
||||
@PostMapping("/registry/heartbeat")
|
||||
public ResponseEntity<String> heartbeat(@RequestBody String uid) {
|
||||
boolean success = registryService.refreshHeartbeat(uid);
|
||||
if (success) {
|
||||
return ResponseEntity.ok("Heartbeat updated");
|
||||
} else {
|
||||
return ResponseEntity.status(404).body("Tool not found");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -58,7 +58,7 @@ public class ScaffoldingController {
|
||||
@PostMapping("/pod")
|
||||
public String scaffoldPod(@RequestBody Map<String, String> req) {
|
||||
try {
|
||||
String moduleName = req.getOrDefault("moduleName", "dap-was-oth");
|
||||
String moduleName = req.getOrDefault("moduleName", "dap-was-cus");
|
||||
if (!moduleName.startsWith("dap-was-")) moduleName = "dap-was-" + moduleName;
|
||||
String port = req.getOrDefault("port", "8085");
|
||||
String shortName = moduleName.replace("dap-was-", "").replace("-", "");
|
||||
@@ -83,7 +83,7 @@ public class ScaffoldingController {
|
||||
String description = req.get("description");
|
||||
String group = req.getOrDefault("categoryKey", req.getOrDefault("group", "COMMON"));
|
||||
String routingType = req.getOrDefault("routingType", "HTTP");
|
||||
String moduleName = req.getOrDefault("moduleName", "dap-was-oth");
|
||||
String moduleName = req.getOrDefault("moduleName", "dap-was-cus");
|
||||
String author = req.get("author");
|
||||
if (author == null || author.trim().isEmpty()) author = System.getProperty("user.name");
|
||||
String date = req.get("date");
|
||||
@@ -124,7 +124,7 @@ public class ScaffoldingController {
|
||||
throw new IllegalArgumentException("UseCase name must be PascalCase.");
|
||||
}
|
||||
String moduleName = request.moduleName() == null || request.moduleName().isBlank()
|
||||
? "dap-was-oth" : request.moduleName().trim();
|
||||
? "dap-was-cus" : request.moduleName().trim();
|
||||
String author = request.author() == null || request.author().isBlank()
|
||||
? System.getProperty("user.name") : request.author().trim();
|
||||
String date = request.date() == null || request.date().isBlank()
|
||||
@@ -248,12 +248,12 @@ public class ScaffoldingController {
|
||||
File[] files = dir.listFiles(f -> f.isDirectory() && f.getName().startsWith("dap-was-") && !f.getName().equals("dap-was-lib"));
|
||||
|
||||
if (files == null || files.length == 0) {
|
||||
return List.of("dap-was-oth", "dap-was-hr", "dap-was-sms");
|
||||
return List.of("dap-was-cus", "dap-was-sal", "dap-was-pro", "dap-was-sys");
|
||||
}
|
||||
|
||||
return Arrays.stream(files).map(File::getName).sorted().collect(Collectors.toList());
|
||||
} catch (Exception e) {
|
||||
return List.of("dap-was-oth", "dap-was-hr", "dap-was-sms");
|
||||
return List.of("dap-was-cus", "dap-was-sal", "dap-was-pro", "dap-was-sys");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,26 +30,10 @@ public class InMemoryRegistryService {
|
||||
* 툴 등록 및 갱신
|
||||
*/
|
||||
public void saveTool(ToolMetadata meta) {
|
||||
meta.setLastHeartbeat(System.currentTimeMillis());
|
||||
toolCache.put(meta.getUid(), meta);
|
||||
log.info(" [InMemoryRegistry] 툴 등록 완료: {}", meta.getUid());
|
||||
}
|
||||
|
||||
/**
|
||||
* 하트비트 갱신 (TTL 초기화)
|
||||
*/
|
||||
public boolean refreshHeartbeat(String uid) {
|
||||
ToolMetadata meta = toolCache.get(uid);
|
||||
if (meta != null) {
|
||||
meta.setLastHeartbeat(System.currentTimeMillis());
|
||||
log.debug(" [InMemoryRegistry] 하트비트 갱신: {}", uid);
|
||||
return true;
|
||||
} else {
|
||||
log.warn(" [InMemoryRegistry] 존재하지 않는 툴에 대한 하트비트 요청: {}", uid);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> getAvailablePods(String uid) {
|
||||
ToolMetadata tool = getTool(uid);
|
||||
if (tool != null && tool.getPodUrl() != null) {
|
||||
@@ -91,21 +75,4 @@ public class InMemoryRegistryService {
|
||||
toolCache.remove(uid);
|
||||
log.info(" [InMemoryRegistry] 툴 삭제 완료: {}", uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 만료된 툴을 스케줄러로 삭제합니다. (10초마다 실행)
|
||||
*/
|
||||
@Scheduled(fixedRate = 10000)
|
||||
public void evictExpiredTools() {
|
||||
long now = System.currentTimeMillis();
|
||||
List<String> expiredKeys = toolCache.entrySet().stream()
|
||||
.filter(entry -> (now - entry.getValue().getLastHeartbeat()) > DEFAULT_TTL_MILLIS)
|
||||
.map(Map.Entry::getKey)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (String key : expiredKeys) {
|
||||
toolCache.remove(key);
|
||||
log.info(" [InMemoryRegistry] TTL 초과로 툴 자동 삭제: {}", key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,9 +51,15 @@ public class DynamicMcpServerManager {
|
||||
getOrCreateServer("common");
|
||||
getOrCreateServer("hr");
|
||||
getOrCreateServer("payment");
|
||||
// Tool category names are not Pod names. Keep existing external category
|
||||
// endpoints available while the SMS/OTH Pods transition to SAL/CUS.
|
||||
getOrCreateServer("sms");
|
||||
getOrCreateServer("email");
|
||||
getOrCreateServer("oth");
|
||||
getOrCreateServer("sal");
|
||||
getOrCreateServer("email");
|
||||
getOrCreateServer("cus");
|
||||
getOrCreateServer("pro");
|
||||
getOrCreateServer("sys");
|
||||
getOrCreateServer("sample");
|
||||
getOrCreateServer("smp"); // smp 카테고리도 기본적으로 항상 열어두도록 추가
|
||||
}
|
||||
|
||||
@@ -10,10 +10,15 @@
|
||||
mcp:
|
||||
gateway:
|
||||
fallback:
|
||||
default-url: http://was-oth:8084
|
||||
default-url: http://was-cus:8084
|
||||
routes:
|
||||
sms: http://was-sms:8082
|
||||
hr: http://was-oth:8084
|
||||
cus: http://was-cus:8084
|
||||
sal: http://was-sal:8082
|
||||
sms: http://was-sal:8082
|
||||
oth: http://was-cus:8084
|
||||
hr: http://was-cus:8084
|
||||
pro: http://was-pro:8085
|
||||
sys: http://was-sys:8086
|
||||
|
||||
# --- 신한라이프 EAI/MCI 연계 IP 정보 (개발 환경) ---
|
||||
shinhan:
|
||||
|
||||
@@ -37,10 +37,15 @@ mcp:
|
||||
default-url: http://localhost:8084
|
||||
routes:
|
||||
cmm_memo_retriever: http://localhost:8082
|
||||
cus: http://localhost:8084
|
||||
sal: http://localhost:8082
|
||||
sms: http://localhost:8082
|
||||
oth: http://localhost:8084
|
||||
email: http://localhost:8083
|
||||
payment: http://localhost:8085
|
||||
hr: http://localhost:8084
|
||||
pro: http://localhost:8085
|
||||
sys: http://localhost:8086
|
||||
tool:
|
||||
host: localhost
|
||||
|
||||
|
||||
@@ -27,10 +27,16 @@ server:
|
||||
mcp:
|
||||
gateway:
|
||||
fallback:
|
||||
default-url: http://was-oth:8084
|
||||
default-url: http://was-cus:8084
|
||||
routes:
|
||||
sms: http://was-sms:8082
|
||||
hr: http://was-oth:8084
|
||||
cus: http://was-cus:8084
|
||||
sal: http://was-sal:8082
|
||||
# Existing SMS category callers continue to resolve to the SAL Pod.
|
||||
sms: http://was-sal:8082
|
||||
oth: http://was-cus:8084
|
||||
hr: http://was-cus:8084
|
||||
pro: http://was-pro:8085
|
||||
sys: http://was-sys:8086
|
||||
agent-claims-required: false
|
||||
trusted-claims-required: false
|
||||
write-approval-required: false
|
||||
|
||||
@@ -981,9 +981,11 @@
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Target Module</label>
|
||||
<select class="form-select" name="moduleName" id="targetModuleSelect" required>
|
||||
<option value="dap-was-oth">dap-was-oth</option>
|
||||
<option value="dap-was-cus">dap-was-cus</option>
|
||||
<option value="dap-was-hr">dap-was-hr</option>
|
||||
<option value="dap-was-sms">dap-was-sms</option>
|
||||
<option value="dap-was-sal">dap-was-sal</option>
|
||||
<option value="dap-was-pro">dap-was-pro</option>
|
||||
<option value="dap-was-sys">dap-was-sys</option>
|
||||
</select>
|
||||
<div class="input-hint">Destination Pod directory.</div>
|
||||
</div>
|
||||
@@ -1478,7 +1480,7 @@
|
||||
.then(modules => {
|
||||
const select = document.getElementById('targetModuleSelect');
|
||||
if (modules && modules.length > 0) {
|
||||
select.innerHTML = modules.map(m => `<option value="${m}" ${m === 'dap-was-oth' ? 'selected' : ''}>${m}</option>`).join('');
|
||||
select.innerHTML = modules.map(m => `<option value="${m}" ${m === 'dap-was-cus' ? 'selected' : ''}>${m}</option>`).join('');
|
||||
}
|
||||
})
|
||||
.catch(err => console.error('Failed to load modules:', err));
|
||||
|
||||
Reference in New Issue
Block a user