forked from kimhyungsik/ax_hub_mcp_tool
feat: Refactor monolith to microservices architecture
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
package io.shinhanlife.axhub.biz.mcp.gateway;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableCaching
|
||||
public class AxHubGatewayApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(AxHubGatewayApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package io.shinhanlife.axhub.biz.mcp.gateway.config;
|
||||
|
||||
import io.shinhanlife.axhub.biz.mcp.gateway.dto.ToolMetadata;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
|
||||
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
@Configuration
|
||||
public class RedisConfig {
|
||||
|
||||
@Bean
|
||||
public RedisTemplate<String, ToolMetadata> redisTemplate(RedisConnectionFactory connectionFactory) {
|
||||
RedisTemplate<String, ToolMetadata> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(connectionFactory);
|
||||
|
||||
// 1. Key는 무조건 String
|
||||
template.setKeySerializer(new StringRedisSerializer());
|
||||
template.setHashKeySerializer(new StringRedisSerializer());
|
||||
|
||||
// 2. Value는 Generic 직렬화기 사용 (생성자 인자 없음)
|
||||
Jackson2JsonRedisSerializer<ToolMetadata> serializer = new Jackson2JsonRedisSerializer<>(ToolMetadata.class);
|
||||
template.setValueSerializer(serializer);
|
||||
template.setHashValueSerializer(serializer);
|
||||
|
||||
template.afterPropertiesSet();
|
||||
return template;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package io.shinhanlife.axhub.biz.mcp.gateway.controller;
|
||||
|
||||
import io.shinhanlife.axhub.biz.mcp.gateway.service.KillSwitchService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/mcp/api/v1/admin/kill-switch")
|
||||
@RequiredArgsConstructor
|
||||
public class KillSwitchController {
|
||||
|
||||
private final KillSwitchService killSwitchService;
|
||||
|
||||
/**
|
||||
* 관리자가 비상 차단(Kill Switch) 상태를 토글합니다.
|
||||
* @param type "agent", "tool", "route" 중 하나
|
||||
* @param target 차단할 대상의 ID 또는 이름
|
||||
* @param state true(차단), false(차단 해제)
|
||||
*/
|
||||
@PostMapping("/{type}/{target}")
|
||||
public ResponseEntity<?> toggleKillSwitch(
|
||||
@PathVariable String type,
|
||||
@PathVariable String target,
|
||||
@RequestParam boolean state) {
|
||||
|
||||
killSwitchService.toggleKillSwitch(type, target, state);
|
||||
|
||||
return ResponseEntity.ok(Map.of(
|
||||
"message", "Kill Switch 상태 변경 완료",
|
||||
"type", type,
|
||||
"target", target,
|
||||
"state", state
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* 관리자가 비상 차단(Kill Switch) 키를 Redis에서 완전히 삭제합니다.
|
||||
*/
|
||||
@DeleteMapping("/{type}/{target}")
|
||||
public ResponseEntity<?> removeKillSwitch(
|
||||
@PathVariable String type,
|
||||
@PathVariable String target) {
|
||||
|
||||
killSwitchService.removeKillSwitch(type, target);
|
||||
|
||||
return ResponseEntity.ok(Map.of(
|
||||
"message", "Kill Switch 키 삭제 완료 (차단 해제)",
|
||||
"type", type,
|
||||
"target", target
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package io.shinhanlife.axhub.biz.mcp.gateway.controller;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.shinhanlife.axhub.biz.mcp.adapter.dto.JsonRpcRequest;
|
||||
import io.shinhanlife.axhub.biz.mcp.adapter.dto.JsonRpcResponse;
|
||||
import io.shinhanlife.axhub.biz.mcp.adapter.dto.Params;
|
||||
import io.shinhanlife.axhub.biz.mcp.gateway.dto.ToolMetadata;
|
||||
import io.shinhanlife.axhub.biz.mcp.gateway.service.ExecuteService;
|
||||
import io.shinhanlife.axhub.biz.mcp.gateway.registry.RedisRegistryService;
|
||||
import io.shinhanlife.axhub.common.mcp.security.SecurityProperties;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.List;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/mcp/api/v1")
|
||||
@Tag(name = "MCP Router API", description = "AI Agent의 요청을 받아 Adapter 시스템으로 라우팅하는 게이트웨이 API")
|
||||
public class McpRouterController {
|
||||
|
||||
private final RedisRegistryService redisRegistryService;
|
||||
private final ExecuteService executeService;
|
||||
private final SecurityProperties securityProperties;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
public McpRouterController(RedisRegistryService redisRegistryService,
|
||||
ExecuteService executeService,
|
||||
SecurityProperties securityProperties,
|
||||
ObjectMapper objectMapper) {
|
||||
this.redisRegistryService = redisRegistryService;
|
||||
this.executeService = executeService;
|
||||
this.securityProperties = securityProperties;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@Operation(summary = "MCP 파이프라인 호출", description = "MCP 표준 파이프라인(ExecuteService)을 통해 레거시 툴을 호출합니다.")
|
||||
@PostMapping("/tools/call")
|
||||
public ResponseEntity<Object> callTool(
|
||||
@RequestHeader(value = "X-Trace-Id", required = false) String traceId,
|
||||
@RequestHeader(value = "X-Agent-Id", required = false) String agentId,
|
||||
@RequestHeader(value = "X-User-Prompt", required = false) String userPrompt,
|
||||
@RequestBody Map<String, Object> payload,
|
||||
@RequestAttribute(value = "tenantId", required = false) String tenantId) {
|
||||
|
||||
java.util.Map<String, Object> meta = new java.util.HashMap<>();
|
||||
meta.put("traceId", traceId != null ? traceId : java.util.UUID.randomUUID().toString());
|
||||
meta.put("agentId", agentId != null ? agentId : "UNKNOWN");
|
||||
meta.put("userPrompt", userPrompt != null ? userPrompt : "");
|
||||
payload.put("meta", meta);
|
||||
|
||||
log.info("[MCP 표준] ExecuteService 파이프라인을 통한 툴 호출 시작 (Tenant: {}, Trace: {})", tenantId, meta.get("traceId"));
|
||||
|
||||
try {
|
||||
Object result = executeService.execute(payload, tenantId);
|
||||
return ResponseEntity.ok(result);
|
||||
} catch (SecurityException se) {
|
||||
log.warn(" [보안 차단] 권한 오류: {}", se.getMessage());
|
||||
return ResponseEntity.status(403).body(Map.of("error", se.getMessage()));
|
||||
} catch (Exception e) {
|
||||
log.error(" 파이프라인 실행 중 오류: {}", e.getMessage());
|
||||
return ResponseEntity.internalServerError().body(Map.of("error", e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/tools/list")
|
||||
public ResponseEntity<JsonRpcResponse> listTools() {
|
||||
List<ToolMetadata> activeTools = redisRegistryService.getAllTools();
|
||||
|
||||
JsonRpcResponse response = new JsonRpcResponse();
|
||||
response.setId(UUID.randomUUID().toString());
|
||||
response.setResult(Map.of("tools", activeTools));
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
@PostMapping("/registry/register")
|
||||
public ResponseEntity<String> registerTool(@RequestBody ToolMetadata meta) {
|
||||
redisRegistryService.saveTool(meta);
|
||||
return ResponseEntity.ok("Registered");
|
||||
}
|
||||
|
||||
@PostMapping("/registry/deregister")
|
||||
public ResponseEntity<String> deregisterTool(@RequestBody String toolName) {
|
||||
redisRegistryService.removeTool(toolName);
|
||||
return ResponseEntity.ok("Deregistered");
|
||||
}
|
||||
|
||||
@PostMapping("/registry/heartbeat")
|
||||
public ResponseEntity<String> heartbeat(@RequestBody String toolName) {
|
||||
boolean success = redisRegistryService.refreshHeartbeat(toolName);
|
||||
if (success) {
|
||||
return ResponseEntity.ok("Heartbeat updated");
|
||||
} else {
|
||||
return ResponseEntity.status(404).body("Tool not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package io.shinhanlife.axhub.biz.mcp.gateway.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Tool(Agent)의 명세 및 라우팅 정보를 담고 있는 메타데이터 클래스
|
||||
* Redis 레지스트리에 저장되며, Planner와 Router 간의 통신 객체(Plan)로 사용됩니다.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ToolMetadata {
|
||||
|
||||
// 1. Tool 기본 정보
|
||||
private String toolName; // 툴 고유 명칭 (예: CustomerSearchTool)
|
||||
private String description; // 툴의 목적 및 설명 (LLM 프롬프트에 활용 가능)
|
||||
|
||||
// 2. 파라미터 스키마 (JSON Schema 형태의 Map)
|
||||
private Map<String, Object> parametersSchema;
|
||||
|
||||
// 2-0. 프론트엔드 UI용 함수별 프롬프트 매핑 (추가됨)
|
||||
private Map<String, String> actionPrompts;
|
||||
|
||||
// 2-1. 도메인 부서 그룹명 (권한 관리에 사용)
|
||||
private String domainGroup;
|
||||
|
||||
// 2-2. 툴 처리 엔드포인트 URI 경로 (예: /api/tool/customer-info)
|
||||
private String endpoint;
|
||||
|
||||
// 2-3. Pod 실행 URL (독립적인 Microservice 라우팅용, 예: http://localhost:8082)
|
||||
private String podUrl;
|
||||
|
||||
// 3. 연동 아키텍처 구분 (DIRECT / MCI_EAI)
|
||||
private String integrationType; // 연동 타입: "DIRECT" 또는 "MCI_EAI"
|
||||
|
||||
// 4. 레거시(MCI/EAI) 연동 시 필수 정보 (integrationType이 "MCI_EAI"일 때 사용)
|
||||
private String mciServiceId; // MCI/EAI 호출을 위한 서비스 ID (예: CRM_001, LICO_992)
|
||||
|
||||
// 5. 인프라 상태 정보 (DIRECT 연동 시 사용)
|
||||
private Long lastHeartbeat; // Redis TTL 갱신용 마지막 하트비트 타임스탬프
|
||||
|
||||
// 6. 동적 서킷 브레이커 & 속도 제어 설정 (Registry 기반)
|
||||
private Integer failureRateThreshold; // 서킷 브레이커 동작 기준 실패율 (%)
|
||||
private Integer slidingWindowSize; // 서킷 브레이커 에러율 계산 표본 요청 수
|
||||
private Integer rateLimitForPeriod; // 속도 제어: 1초당 허용 최대 요청 수
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package io.shinhanlife.axhub.biz.mcp.gateway.messaging;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.kafka.core.KafkaTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class KafkaProducerService {
|
||||
|
||||
// Spring Kafka 제공 템플릿
|
||||
private final KafkaTemplate<String, String> kafkaTemplate;
|
||||
private final ObjectMapper jsonMapper;
|
||||
|
||||
/**
|
||||
* 트래픽 초과 등의 이유로 즉시 처리가 불가능한 요청을 Kafka 대기열로 보냅니다.
|
||||
* @param topic 발행할 Kafka 토픽명
|
||||
* @param payload 원본 요청 페이로드
|
||||
* @return 발급된 고유 티켓 ID
|
||||
*/
|
||||
public String queueRequest(String topic, Map<String, Object> payload) {
|
||||
String ticketId = "TICKET-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
|
||||
|
||||
// 페이로드에 티켓 ID 추가 (Consumer가 알 수 있도록)
|
||||
payload.put("ticketId", ticketId);
|
||||
payload.put("queuedAt", System.currentTimeMillis());
|
||||
|
||||
try {
|
||||
String jsonPayload = jsonMapper.writeValueAsString(payload);
|
||||
// 실제 Kafka 서버로 전송
|
||||
kafkaTemplate.send(topic, ticketId, jsonPayload);
|
||||
log.info("🎫 [Kafka Queue] 요청 대기열 등록 완료 - Topic: {}, Ticket ID: {}", topic, ticketId);
|
||||
} catch (Exception e) {
|
||||
// 로컬 테스트 시 실제 Kafka 브로커가 없어서 나는 예외를 방어합니다.
|
||||
// 실제 상용 환경에서는 Dead Letter Queue 등을 태우거나 예외 처리 정책에 따릅니다.
|
||||
log.error(" [Kafka Queue] Kafka 서버 통신 실패 (Broker Down). Mock 모드로 티켓은 발급합니다. Error: {}", e.getMessage());
|
||||
}
|
||||
|
||||
return ticketId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package io.shinhanlife.axhub.biz.mcp.gateway.registry;
|
||||
|
||||
import io.shinhanlife.axhub.biz.mcp.gateway.dto.ToolMetadata;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.Objects;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class RedisRegistryService {
|
||||
|
||||
private final RedisTemplate<String, ToolMetadata> redisTemplate;
|
||||
private static final String KEY_PREFIX = "mcp:tool:";
|
||||
private static final Duration DEFAULT_TTL = Duration.ofSeconds(60); // 다이어그램 3-3 하트비트 주기 반영
|
||||
|
||||
/**
|
||||
* 툴 등록 및 갱신 (TTL 기반으로 60초 뒤 자동 만료)
|
||||
*/
|
||||
public void saveTool(ToolMetadata meta) {
|
||||
String key = KEY_PREFIX + meta.getToolName();
|
||||
meta.setLastHeartbeat(System.currentTimeMillis());
|
||||
redisTemplate.opsForValue().set(key, meta, DEFAULT_TTL);
|
||||
log.info(" [RedisRegistry] 툴 등록 완료: {}", meta.getToolName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 하트비트 갱신 (TTL 초기화)
|
||||
*/
|
||||
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<String> getAvailablePods(String toolName) {
|
||||
ToolMetadata tool = getTool(toolName);
|
||||
|
||||
if (tool != null && tool.getPodUrl() != null) {
|
||||
return List.of(tool.getPodUrl());
|
||||
}
|
||||
|
||||
return List.of();
|
||||
}
|
||||
/**
|
||||
* 실행 시 툴 정보 조회 (Tool Execution 시 참조)
|
||||
*/
|
||||
public ToolMetadata getTool(String toolName) {
|
||||
return redisTemplate.opsForValue().get(KEY_PREFIX + toolName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 등록된 모든 활성 툴 목록 조회
|
||||
*/
|
||||
public List<ToolMetadata> getAllTools() {
|
||||
Set<String> keys = redisTemplate.keys(KEY_PREFIX + "*");
|
||||
if (keys == null || keys.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
List<ToolMetadata> tools = redisTemplate.opsForValue().multiGet(keys);
|
||||
return tools != null ? tools.stream().filter(Objects::nonNull).collect(Collectors.toList()) : List.of();
|
||||
}
|
||||
|
||||
/**
|
||||
* 툴 명시적 제거 (Deregister)
|
||||
*/
|
||||
public void removeTool(String toolName) {
|
||||
redisTemplate.delete(KEY_PREFIX + toolName);
|
||||
log.info(" [RedisRegistry] 툴 삭제 완료: {}", toolName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package io.shinhanlife.axhub.biz.mcp.gateway.service;
|
||||
|
||||
import io.shinhanlife.axhub.biz.mcp.adapter.dto.Params;
|
||||
import io.shinhanlife.axhub.biz.mcp.gateway.dto.ToolMetadata;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ExecuteService {
|
||||
|
||||
private final ToolPlanner planner;
|
||||
private final KillSwitchService killSwitchService;
|
||||
private final RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
public Object execute(Map<String, Object> payload, String tenantId) {
|
||||
log.info(" [ExecuteService] 전체 실행 흐름 제어 시작");
|
||||
|
||||
killSwitchService.checkAgent(tenantId);
|
||||
|
||||
Map<String, Object> params = payload.containsKey("params") ? (Map<String, Object>) payload.get("params") : null;
|
||||
String toolName = params != null ? (String) params.get("name") : (String) payload.get("toolName");
|
||||
killSwitchService.checkTool(toolName);
|
||||
|
||||
var planObj = planner.createPlan(payload, tenantId);
|
||||
ToolMetadata plan = (ToolMetadata) planObj;
|
||||
|
||||
if (plan.getIntegrationType() != null) {
|
||||
killSwitchService.checkRoute(plan.getIntegrationType());
|
||||
}
|
||||
|
||||
// Dynamic Routing: Find the target Pod from the Redis registry
|
||||
String targetUrl = "http://localhost:8082"; // Fallback
|
||||
if (plan.getPodUrl() != null && !plan.getPodUrl().isEmpty()) {
|
||||
targetUrl = plan.getPodUrl();
|
||||
}
|
||||
|
||||
log.info(" [ExecuteService] 라우팅 목적지: {}", targetUrl);
|
||||
String executeApiUrl = targetUrl + "/mcp/api/v1/tools/call";
|
||||
|
||||
// Forward the request to the target tool pod
|
||||
try {
|
||||
ResponseEntity<Object> response = restTemplate.postForEntity(executeApiUrl, payload, Object.class);
|
||||
return response.getBody();
|
||||
} catch (Exception e) {
|
||||
log.error(" [ExecuteService] Tool Pod 호출 실패: {}", e.getMessage());
|
||||
throw new RuntimeException("Tool Pod 호출 실패: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package io.shinhanlife.axhub.biz.mcp.gateway.service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class KillSwitchService {
|
||||
|
||||
// 기본 제공되는 StringRedisTemplate 사용
|
||||
private final StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
public void checkAgent(String tenantId) {
|
||||
String key = "mcp:kill:agent:" + tenantId;
|
||||
if ("true".equalsIgnoreCase(stringRedisTemplate.opsForValue().get(key))) {
|
||||
log.warn(" [KillSwitch] 차단된 에이전트 접근 시도: {}", tenantId);
|
||||
throw new SecurityException(" 비상 차단: 해당 테넌트(" + tenantId + ")의 접근이 관리자에 의해 차단되었습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
public void checkTool(String toolName) {
|
||||
if (toolName == null || toolName.isBlank()) return;
|
||||
|
||||
String key = "mcp:kill:tool:" + toolName;
|
||||
if ("true".equalsIgnoreCase(stringRedisTemplate.opsForValue().get(key))) {
|
||||
log.warn(" [KillSwitch] 차단된 툴 실행 시도: {}", toolName);
|
||||
throw new SecurityException(" 비상 차단: 해당 툴(" + toolName + ")의 실행이 관리자에 의해 차단되었습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
public void checkRoute(String integrationType) {
|
||||
if (integrationType == null || integrationType.isBlank()) return;
|
||||
|
||||
String key = "mcp:kill:route:" + integrationType;
|
||||
if ("true".equalsIgnoreCase(stringRedisTemplate.opsForValue().get(key))) {
|
||||
log.warn(" [KillSwitch] 차단된 라우트 통신 시도: {}", integrationType);
|
||||
throw new SecurityException(" 비상 차단: 해당 레거시 라우트(" + integrationType + ")로의 통신이 관리자에 의해 차단되었습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
// 관리자 API용 토글 메서드
|
||||
public void toggleKillSwitch(String type, String target, boolean state) {
|
||||
String key = "mcp:kill:" + type + ":" + target;
|
||||
stringRedisTemplate.opsForValue().set(key, String.valueOf(state));
|
||||
log.info(" [KillSwitch] 상태 변경: {} -> {} (차단 상태: {})", type, target, state);
|
||||
}
|
||||
|
||||
// 관리자 API용 삭제 메서드 (Redis 키 자체를 완전 삭제)
|
||||
public void removeKillSwitch(String type, String target) {
|
||||
String key = "mcp:kill:" + type + ":" + target;
|
||||
stringRedisTemplate.delete(key);
|
||||
log.info(" [KillSwitch] 차단 키 완전 삭제: {} -> {}", type, target);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package io.shinhanlife.axhub.biz.mcp.gateway.service;
|
||||
|
||||
import io.shinhanlife.axhub.common.mcp.security.SecurityProperties;
|
||||
import io.shinhanlife.axhub.biz.mcp.gateway.registry.RedisRegistryService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ToolPlanner {
|
||||
|
||||
private final RedisRegistryService redisRegistryService;
|
||||
private final SecurityProperties securityProperties;
|
||||
|
||||
/**
|
||||
* 요청(payload)을 분석하여 실행해야 할 Tool의 계획을 생성합니다.
|
||||
*/
|
||||
public Object createPlan(Map<String, Object> payload, String tenantId) {
|
||||
log.info("📝 [Planner] 요청 분석 및 실행 계획 수립 시작");
|
||||
|
||||
// 1. 요청에서 호출하려는 툴 이름 추출 (JSON-RPC params.name)
|
||||
Map<String, Object> params = (Map<String, Object>) payload.get("params");
|
||||
String toolName = params != null ? (String) params.get("name") : null;
|
||||
|
||||
if (toolName == null || toolName.isEmpty()) {
|
||||
throw new IllegalArgumentException("요청에 toolName이 포함되어 있지 않습니다.");
|
||||
}
|
||||
|
||||
// 2. RedisRegistry에서 해당 툴의 메타데이터 조회
|
||||
// (실제로는 이 메타데이터가 실행 계획의 핵심이 됩니다)
|
||||
var toolMetadata = redisRegistryService.getTool(toolName);
|
||||
|
||||
if (toolMetadata == null) {
|
||||
log.warn(" [Planner] 등록되지 않은 툴 요청: {}", toolName);
|
||||
throw new RuntimeException("해당 툴(" + toolName + ")이 레지스트리에 존재하지 않습니다.");
|
||||
}
|
||||
|
||||
// 2-1. [신규] 도메인 그룹핑 기반 권한 검증
|
||||
if (tenantId != null && toolMetadata.getDomainGroup() != null) {
|
||||
List<String> allowedDomains = securityProperties.getTenantDomains().get(tenantId);
|
||||
if (allowedDomains == null ||
|
||||
(!allowedDomains.contains("ALL") && !allowedDomains.contains(toolMetadata.getDomainGroup()))) {
|
||||
log.warn(" [Planner] 권한 거부 - Tenant: {}, Request Domain: {}", tenantId, toolMetadata.getDomainGroup());
|
||||
throw new SecurityException("해당 도메인(" + toolMetadata.getDomainGroup() + ")의 툴을 실행할 권한이 없습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
log.info(" [Planner] 툴 '{}'에 대한 실행 계획 수립 완료", toolName);
|
||||
|
||||
// 3. 수립된 계획(툴 메타데이터) 반환
|
||||
return toolMetadata;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package io.shinhanlife.axhub.biz.so.atm.converter;
|
||||
|
||||
import io.shinhanlife.axhub.biz.so.atm.dto.AthrOutDto;
|
||||
import io.shinhanlife.axhub.biz.so.atm.dto.AthrSaveInDto;
|
||||
import io.shinhanlife.axhub.biz.so.atm.dto.AthrSearchInDto;
|
||||
import io.shinhanlife.axhub.biz.so.atm.dto.RoleListInDto;
|
||||
import io.shinhanlife.axhub.biz.so.atm.presentation.io.AthrSaveRequest;
|
||||
import io.shinhanlife.axhub.biz.so.atm.presentation.io.AthrSearchRequest;
|
||||
import io.shinhanlife.axhub.biz.so.atm.presentation.io.AthrSearchResponse;
|
||||
import io.shinhanlife.axhub.biz.so.atm.presentation.io.RoleListRequest;
|
||||
import org.mapstruct.Mapper;
|
||||
|
||||
@Mapper(componentModel = "spring")
|
||||
public abstract class AccessMgmtConverter {
|
||||
|
||||
public abstract RoleListInDto toInDto(RoleListRequest request);
|
||||
|
||||
public abstract AthrSearchInDto toInDto(AthrSearchRequest request);
|
||||
|
||||
public abstract AthrSaveInDto toInDto(AthrSaveRequest request);
|
||||
|
||||
public abstract AthrSearchResponse toResponse(AthrOutDto outDto);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package io.shinhanlife.axhub.biz.so.atm.domain.repository;
|
||||
|
||||
import io.shinhanlife.axhub.biz.so.atm.dto.AthrItemOutDto;
|
||||
import io.shinhanlife.axhub.biz.so.atm.dto.AthrSearchInDto;
|
||||
import io.shinhanlife.axhub.biz.so.atm.dto.RoleKnwlAthrInDto;
|
||||
import io.shinhanlife.axhub.biz.so.atm.dto.RoleListInDto;
|
||||
import io.shinhanlife.axhub.biz.so.atm.dto.RoleListOutDto;
|
||||
import io.shinhanlife.axhub.biz.so.atm.dto.RoleToolAthrInDto;
|
||||
import io.shinhanlife.glow.GlowMybatisMapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@GlowMybatisMapper
|
||||
public interface AccessMgmtMapper {
|
||||
|
||||
List<RoleListOutDto> selectRoles(RoleListInDto inDto);
|
||||
|
||||
List<AthrItemOutDto> selectAllTools();
|
||||
|
||||
List<AthrItemOutDto> selectAllKnwls();
|
||||
|
||||
List<String> selectGrantedToolIds(AthrSearchInDto inDto);
|
||||
|
||||
List<String> selectGrantedKnwlIds(AthrSearchInDto inDto);
|
||||
|
||||
void deleteToolAthr(@Param("systId") String systId, @Param("roleNo") String roleNo);
|
||||
|
||||
void insertToolAthr(RoleToolAthrInDto inDto);
|
||||
|
||||
void deleteKnwlAthr(@Param("systId") String systId, @Param("roleNo") String roleNo);
|
||||
|
||||
void insertKnwlAthr(RoleKnwlAthrInDto inDto);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package io.shinhanlife.axhub.biz.so.atm.domain.service;
|
||||
|
||||
import io.shinhanlife.axhub.biz.so.atm.dto.AthrOutDto;
|
||||
import io.shinhanlife.axhub.biz.so.atm.dto.AthrSaveInDto;
|
||||
import io.shinhanlife.axhub.biz.so.atm.dto.AthrSearchInDto;
|
||||
import io.shinhanlife.axhub.biz.so.atm.dto.RoleListInDto;
|
||||
import io.shinhanlife.axhub.biz.so.atm.dto.RoleListOutDto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface AccessMgmtService {
|
||||
|
||||
List<RoleListOutDto> getRoles(RoleListInDto inDto);
|
||||
|
||||
AthrOutDto getAthr(AthrSearchInDto inDto);
|
||||
|
||||
void saveAthr(AthrSaveInDto inDto);
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package io.shinhanlife.axhub.biz.so.atm.domain.service.impl;
|
||||
|
||||
import io.shinhanlife.axhub.biz.so.atm.domain.repository.AccessMgmtMapper;
|
||||
import io.shinhanlife.axhub.biz.so.atm.domain.service.AccessMgmtService;
|
||||
import io.shinhanlife.axhub.biz.so.atm.dto.AthrItemOutDto;
|
||||
import io.shinhanlife.axhub.biz.so.atm.dto.AthrOutDto;
|
||||
import io.shinhanlife.axhub.biz.so.atm.dto.AthrSaveInDto;
|
||||
import io.shinhanlife.axhub.biz.so.atm.dto.AthrSearchInDto;
|
||||
import io.shinhanlife.axhub.biz.so.atm.dto.RoleKnwlAthrInDto;
|
||||
import io.shinhanlife.axhub.biz.so.atm.dto.RoleListInDto;
|
||||
import io.shinhanlife.axhub.biz.so.atm.dto.RoleListOutDto;
|
||||
import io.shinhanlife.axhub.biz.so.atm.dto.RoleToolAthrInDto;
|
||||
import io.shinhanlife.axhub.common.util.SessionUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AccessMgmtServiceImpl implements AccessMgmtService {
|
||||
|
||||
private final AccessMgmtMapper accessMgmtMapper;
|
||||
|
||||
private static final String SYSTEM_CD = "AXH";
|
||||
private static final String SYSTEM_PRG = "SOATM0100";
|
||||
|
||||
@Override
|
||||
public List<RoleListOutDto> getRoles(RoleListInDto inDto) {
|
||||
return accessMgmtMapper.selectRoles(inDto);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AthrOutDto getAthr(AthrSearchInDto inDto) {
|
||||
Set<String> grantedToolIds = accessMgmtMapper.selectGrantedToolIds(inDto)
|
||||
.stream().collect(Collectors.toSet());
|
||||
|
||||
Set<String> grantedKnwlIds = accessMgmtMapper.selectGrantedKnwlIds(inDto)
|
||||
.stream().collect(Collectors.toSet());
|
||||
|
||||
List<AthrItemOutDto> tools = accessMgmtMapper.selectAllTools()
|
||||
.stream()
|
||||
.peek(item -> item.setGranted(grantedToolIds.contains(item.getResourceId())))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<AthrItemOutDto> knwls = accessMgmtMapper.selectAllKnwls()
|
||||
.stream()
|
||||
.peek(item -> item.setGranted(grantedKnwlIds.contains(item.getResourceId())))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return new AthrOutDto(tools, knwls);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void saveAthr(AthrSaveInDto inDto) {
|
||||
Date now = new Date();
|
||||
String systId = inDto.getSystId();
|
||||
String roleNo = inDto.getRoleNo();
|
||||
|
||||
accessMgmtMapper.deleteToolAthr(systId, roleNo);
|
||||
if (inDto.getGrantedToolIds() != null) {
|
||||
for (String toolId : inDto.getGrantedToolIds()) {
|
||||
accessMgmtMapper.insertToolAthr(buildToolAthrInDto(systId, roleNo, toolId, now));
|
||||
}
|
||||
}
|
||||
|
||||
accessMgmtMapper.deleteKnwlAthr(systId, roleNo);
|
||||
if (inDto.getGrantedKnwlIds() != null) {
|
||||
for (String knwlId : inDto.getGrantedKnwlIds()) {
|
||||
accessMgmtMapper.insertKnwlAthr(buildKnwlAthrInDto(systId, roleNo, knwlId, now));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private RoleToolAthrInDto buildToolAthrInDto(String systId, String roleNo, String toolId, Date now) {
|
||||
RoleToolAthrInDto dto = new RoleToolAthrInDto();
|
||||
dto.setRoleToolAthrId("RTA-" + shortUuid());
|
||||
dto.setSystId(systId);
|
||||
dto.setRoleNo(roleNo);
|
||||
dto.setToolId(toolId);
|
||||
dto.setPuseYn("Y");
|
||||
fillAudit(dto, now);
|
||||
return dto;
|
||||
}
|
||||
|
||||
private RoleKnwlAthrInDto buildKnwlAthrInDto(String systId, String roleNo, String knwlId, Date now) {
|
||||
RoleKnwlAthrInDto dto = new RoleKnwlAthrInDto();
|
||||
dto.setRoleKnwlAthrId("RKA-" + shortUuid());
|
||||
dto.setSystId(systId);
|
||||
dto.setRoleNo(roleNo);
|
||||
dto.setKnwlId(knwlId);
|
||||
dto.setPuseYn("Y");
|
||||
fillAudit(dto, now);
|
||||
return dto;
|
||||
}
|
||||
|
||||
private void fillAudit(RoleToolAthrInDto dto, Date now) {
|
||||
String prafNo = SessionUtil.getPrafNo();
|
||||
String ognzNo = SessionUtil.getOgnzNo();
|
||||
dto.setSystRgiDt(now); dto.setSystRgiPrafNo(prafNo);
|
||||
dto.setSystRgiOgnzNo(ognzNo); dto.setSystRgiSystCd(SYSTEM_CD);
|
||||
dto.setSystRgiPrgrId(SYSTEM_PRG);
|
||||
dto.setSystChgDt(now); dto.setSystChgPrafNo(prafNo);
|
||||
dto.setSystChgOgnzNo(ognzNo); dto.setSystChgSystCd(SYSTEM_CD);
|
||||
dto.setSystChgPrgrId(SYSTEM_PRG);
|
||||
}
|
||||
|
||||
private void fillAudit(RoleKnwlAthrInDto dto, Date now) {
|
||||
String prafNo = SessionUtil.getPrafNo();
|
||||
String ognzNo = SessionUtil.getOgnzNo();
|
||||
dto.setSystRgiDt(now); dto.setSystRgiPrafNo(prafNo);
|
||||
dto.setSystRgiOgnzNo(ognzNo); dto.setSystRgiSystCd(SYSTEM_CD);
|
||||
dto.setSystRgiPrgrId(SYSTEM_PRG);
|
||||
dto.setSystChgDt(now); dto.setSystChgPrafNo(prafNo);
|
||||
dto.setSystChgOgnzNo(ognzNo); dto.setSystChgSystCd(SYSTEM_CD);
|
||||
dto.setSystChgPrgrId(SYSTEM_PRG);
|
||||
}
|
||||
|
||||
private String shortUuid() {
|
||||
return UUID.randomUUID().toString().replace("-", "").substring(0, 12).toUpperCase();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package io.shinhanlife.axhub.biz.so.atm.dto;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class AthrItemOutDto {
|
||||
private String resourceId;
|
||||
private String resourceNm;
|
||||
private String resourceDs;
|
||||
private String typeCode;
|
||||
private boolean granted;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package io.shinhanlife.axhub.biz.so.atm.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class AthrOutDto {
|
||||
private List<AthrItemOutDto> tools;
|
||||
private List<AthrItemOutDto> knwls;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package io.shinhanlife.axhub.biz.so.atm.dto;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class AthrSaveInDto {
|
||||
private String systId;
|
||||
private String roleNo;
|
||||
private List<String> grantedToolIds;
|
||||
private List<String> grantedKnwlIds;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package io.shinhanlife.axhub.biz.so.atm.dto;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class AthrSearchInDto {
|
||||
private String systId;
|
||||
private String roleNo;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package io.shinhanlife.axhub.biz.so.atm.dto;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class RoleKnwlAthrInDto {
|
||||
private String roleKnwlAthrId;
|
||||
private String systId;
|
||||
private String roleNo;
|
||||
private String knwlId;
|
||||
private String puseYn;
|
||||
|
||||
private Date systRgiDt;
|
||||
private String systRgiPrafNo;
|
||||
private String systRgiOgnzNo;
|
||||
private String systRgiSystCd;
|
||||
private String systRgiPrgrId;
|
||||
private Date systChgDt;
|
||||
private String systChgPrafNo;
|
||||
private String systChgOgnzNo;
|
||||
private String systChgSystCd;
|
||||
private String systChgPrgrId;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package io.shinhanlife.axhub.biz.so.atm.dto;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class RoleListInDto {
|
||||
private String systId;
|
||||
private String puseYn;
|
||||
private String keyword;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package io.shinhanlife.axhub.biz.so.atm.dto;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class RoleListOutDto {
|
||||
private String systId;
|
||||
private String roleNo;
|
||||
private String roleNm;
|
||||
private String roleDs;
|
||||
private String puseYn;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package io.shinhanlife.axhub.biz.so.atm.dto;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class RoleToolAthrInDto {
|
||||
private String roleToolAthrId;
|
||||
private String systId;
|
||||
private String roleNo;
|
||||
private String toolId;
|
||||
private String puseYn;
|
||||
|
||||
private Date systRgiDt;
|
||||
private String systRgiPrafNo;
|
||||
private String systRgiOgnzNo;
|
||||
private String systRgiSystCd;
|
||||
private String systRgiPrgrId;
|
||||
private Date systChgDt;
|
||||
private String systChgPrafNo;
|
||||
private String systChgOgnzNo;
|
||||
private String systChgSystCd;
|
||||
private String systChgPrgrId;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package io.shinhanlife.axhub.biz.so.atm.presentation;
|
||||
|
||||
import io.shinhanlife.axhub.biz.so.atm.dto.RoleListOutDto;
|
||||
import io.shinhanlife.axhub.biz.so.atm.presentation.io.AthrSaveRequest;
|
||||
import io.shinhanlife.axhub.biz.so.atm.presentation.io.AthrSearchRequest;
|
||||
import io.shinhanlife.axhub.biz.so.atm.presentation.io.AthrSearchResponse;
|
||||
import io.shinhanlife.axhub.biz.so.atm.presentation.io.RoleListRequest;
|
||||
import io.shinhanlife.axhub.biz.so.atm.usecase.AccessMgmtUseCase;
|
||||
import io.shinhanlife.glow.BaseResponse;
|
||||
import io.shinhanlife.glow.ResponseUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/so/atm")
|
||||
@RequiredArgsConstructor
|
||||
public class SOATM0100Controller {
|
||||
|
||||
private final AccessMgmtUseCase accessMgmtUseCase;
|
||||
|
||||
@PostMapping("/SOATM0100R")
|
||||
public ResponseEntity<BaseResponse<List<RoleListOutDto>>> getRoles(
|
||||
@RequestBody RoleListRequest request) {
|
||||
return ResponseUtil.ok(accessMgmtUseCase.getRoles(request));
|
||||
}
|
||||
|
||||
@PostMapping("/SOATM0101R")
|
||||
public ResponseEntity<BaseResponse<AthrSearchResponse>> getAthr(
|
||||
@RequestBody AthrSearchRequest request) {
|
||||
return ResponseUtil.ok(accessMgmtUseCase.getAthr(request));
|
||||
}
|
||||
|
||||
@PostMapping("/SOATM0100U")
|
||||
public ResponseEntity<BaseResponse<Void>> saveAthr(
|
||||
@RequestBody AthrSaveRequest request) {
|
||||
accessMgmtUseCase.saveAthr(request);
|
||||
return ResponseUtil.ok();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package io.shinhanlife.axhub.biz.so.atm.presentation.io;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class AthrSaveRequest {
|
||||
private String systId;
|
||||
private String roleNo;
|
||||
private List<String> grantedToolIds;
|
||||
private List<String> grantedKnwlIds;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package io.shinhanlife.axhub.biz.so.atm.presentation.io;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class AthrSearchRequest {
|
||||
private String systId;
|
||||
private String roleNo;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package io.shinhanlife.axhub.biz.so.atm.presentation.io;
|
||||
|
||||
import io.shinhanlife.axhub.biz.so.atm.dto.AthrItemOutDto;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class AthrSearchResponse {
|
||||
private List<AthrItemOutDto> tools;
|
||||
private List<AthrItemOutDto> knwls;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package io.shinhanlife.axhub.biz.so.atm.presentation.io;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class RoleListRequest {
|
||||
private String systId;
|
||||
private String puseYn;
|
||||
private String keyword;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package io.shinhanlife.axhub.biz.so.atm.usecase;
|
||||
|
||||
import io.shinhanlife.axhub.biz.so.atm.dto.RoleListOutDto;
|
||||
import io.shinhanlife.axhub.biz.so.atm.presentation.io.AthrSaveRequest;
|
||||
import io.shinhanlife.axhub.biz.so.atm.presentation.io.AthrSearchRequest;
|
||||
import io.shinhanlife.axhub.biz.so.atm.presentation.io.AthrSearchResponse;
|
||||
import io.shinhanlife.axhub.biz.so.atm.presentation.io.RoleListRequest;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface AccessMgmtUseCase {
|
||||
|
||||
List<RoleListOutDto> getRoles(RoleListRequest request);
|
||||
|
||||
AthrSearchResponse getAthr(AthrSearchRequest request);
|
||||
|
||||
void saveAthr(AthrSaveRequest request);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package io.shinhanlife.axhub.biz.so.atm.usecase.impl;
|
||||
|
||||
import io.shinhanlife.axhub.biz.so.atm.converter.AccessMgmtConverter;
|
||||
import io.shinhanlife.axhub.biz.so.atm.domain.service.AccessMgmtService;
|
||||
import io.shinhanlife.axhub.biz.so.atm.dto.RoleListOutDto;
|
||||
import io.shinhanlife.axhub.biz.so.atm.presentation.io.AthrSaveRequest;
|
||||
import io.shinhanlife.axhub.biz.so.atm.presentation.io.AthrSearchRequest;
|
||||
import io.shinhanlife.axhub.biz.so.atm.presentation.io.AthrSearchResponse;
|
||||
import io.shinhanlife.axhub.biz.so.atm.presentation.io.RoleListRequest;
|
||||
import io.shinhanlife.axhub.biz.so.atm.usecase.AccessMgmtUseCase;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AccessMgmtUseCaseImpl implements AccessMgmtUseCase {
|
||||
|
||||
private final AccessMgmtService accessMgmtService;
|
||||
private final AccessMgmtConverter converter;
|
||||
|
||||
@Override
|
||||
public List<RoleListOutDto> getRoles(RoleListRequest request) {
|
||||
return accessMgmtService.getRoles(converter.toInDto(request));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AthrSearchResponse getAthr(AthrSearchRequest request) {
|
||||
return converter.toResponse(accessMgmtService.getAthr(converter.toInDto(request)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveAthr(AthrSaveRequest request) {
|
||||
accessMgmtService.saveAthr(converter.toInDto(request));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package io.shinhanlife.axhub.sample.converter;
|
||||
|
||||
import io.shinhanlife.axhub.sample.domain.model.AppliSystNtfyPatiModel;
|
||||
import io.shinhanlife.axhub.sample.dto.AppliSystNtfyRgiInDTO;
|
||||
import io.shinhanlife.axhub.sample.dto.StrnTermListInDTO;
|
||||
import io.shinhanlife.axhub.sample.dto.StrnTermListOutDTO;
|
||||
import io.shinhanlife.axhub.sample.presentation.io.AppliSystNtfyPatiRequest;
|
||||
import io.shinhanlife.axhub.sample.presentation.io.StrnTermRequest;
|
||||
import io.shinhanlife.axhub.sample.presentation.io.StrnTermResponse;
|
||||
import org.mapstruct.Mapper;
|
||||
|
||||
@Mapper(componentModel = "spring")
|
||||
public abstract class SampleConverter {
|
||||
|
||||
public abstract StrnTermListInDTO convertRequestToDto(StrnTermRequest req);
|
||||
|
||||
public abstract StrnTermResponse convertDtoToResponse(StrnTermListOutDTO outDto);
|
||||
|
||||
public abstract AppliSystNtfyPatiModel convertDtoToModel(AppliSystNtfyRgiInDTO inDto);
|
||||
|
||||
public abstract AppliSystNtfyRgiInDTO convertAppliRequestToDto(AppliSystNtfyPatiRequest request);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package io.shinhanlife.axhub.sample.domain.model;
|
||||
|
||||
import io.shinhanlife.glow.db.dto.AuditInfo;
|
||||
import lombok.*;
|
||||
|
||||
@Getter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
public class AppliSystNtfyPatiModel extends AuditInfo {
|
||||
private String appliSystNtfyPatiId;
|
||||
private String appliSystNtfyKdCd;
|
||||
private String appliDutjCd;
|
||||
private String appliDutjPrjcCd;
|
||||
private String ntfyMsgCt;
|
||||
private String ntfyOccDt;
|
||||
private String ntfyCfmDt;
|
||||
private String ntfyTrgtPrafNo;
|
||||
private String shmsPmlYn;
|
||||
private String ntfyCfmYn;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package io.shinhanlife.axhub.sample.domain.repository;
|
||||
|
||||
import io.shinhanlife.axhub.sample.domain.model.AppliSystNtfyPatiModel;
|
||||
import io.shinhanlife.axhub.sample.dto.StrnTermListInDTO;
|
||||
import io.shinhanlife.axhub.sample.dto.StrnTermListOutDTO;
|
||||
import io.shinhanlife.glow.GlowIndexPaging;
|
||||
import io.shinhanlife.glow.GlowMybatisMapper;
|
||||
import io.shinhanlife.glow.PageInfo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@GlowMybatisMapper
|
||||
public interface GlowSampleRepository {
|
||||
|
||||
@GlowIndexPaging
|
||||
List<StrnTermListOutDTO.StrnTerm> selectStrnTerm(StrnTermListInDTO inDto, PageInfo pageInfo);
|
||||
|
||||
int insertAppliSystNtfyPati(AppliSystNtfyPatiModel appliSystNtfyPatiModel);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package io.shinhanlife.axhub.sample.domain.service;
|
||||
|
||||
import io.shinhanlife.axhub.sample.dto.AppliSystNtfyRgiInDTO;
|
||||
import io.shinhanlife.axhub.sample.dto.StrnTermListInDTO;
|
||||
import io.shinhanlife.axhub.sample.dto.StrnTermListOutDTO;
|
||||
import io.shinhanlife.glow.PageInfo;
|
||||
|
||||
public interface GlowSampleService {
|
||||
StrnTermListOutDTO getStrnTerms(StrnTermListInDTO inDto, PageInfo pageInfo);
|
||||
int insertAppliSystNtfyPati(AppliSystNtfyRgiInDTO inDto);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package io.shinhanlife.axhub.sample.domain.service.impl;
|
||||
|
||||
import io.shinhanlife.axhub.sample.converter.SampleConverter;
|
||||
import io.shinhanlife.axhub.sample.domain.model.AppliSystNtfyPatiModel;
|
||||
import io.shinhanlife.axhub.sample.domain.repository.GlowSampleRepository;
|
||||
import io.shinhanlife.axhub.sample.domain.service.GlowSampleService;
|
||||
import io.shinhanlife.axhub.sample.dto.AppliSystNtfyRgiInDTO;
|
||||
import io.shinhanlife.axhub.sample.dto.StrnTermListInDTO;
|
||||
import io.shinhanlife.axhub.sample.dto.StrnTermListOutDTO;
|
||||
import io.shinhanlife.glow.GlowLogger;
|
||||
import io.shinhanlife.glow.GlowLogTarget;
|
||||
import io.shinhanlife.glow.PageInfo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class GlowSampleServiceImpl implements GlowSampleService {
|
||||
|
||||
@GlowLogTarget(GlowLogTarget.Target.FILE)
|
||||
private final GlowLogger log;
|
||||
private final GlowSampleRepository glowSampleRepository;
|
||||
private final SampleConverter sampleConverter;
|
||||
|
||||
@Override
|
||||
public StrnTermListOutDTO getStrnTerms(StrnTermListInDTO inDto, PageInfo pageInfo) {
|
||||
List<StrnTermListOutDTO.StrnTerm> out = glowSampleRepository.selectStrnTerm(inDto, pageInfo);
|
||||
return StrnTermListOutDTO.builder()
|
||||
.strnTerms(out)
|
||||
.pageInfo(pageInfo)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int insertAppliSystNtfyPati(AppliSystNtfyRgiInDTO inDto) {
|
||||
AppliSystNtfyPatiModel appliSystNtfyPatiModel = sampleConverter.convertDtoToModel(inDto);
|
||||
return glowSampleRepository.insertAppliSystNtfyPati(appliSystNtfyPatiModel);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package io.shinhanlife.axhub.sample.dto;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AppliSystNtfyRgiInDTO {
|
||||
private String appliSystNtfyPatiId;
|
||||
private String appliSystNtfyKdCd;
|
||||
private String appliDutjCd;
|
||||
private String appliDutjPrjcCd;
|
||||
private String ntfyMsgCt;
|
||||
private String ntfyOccDt;
|
||||
private String ntfyCfmDt;
|
||||
private String ntfyTrgtPrafNo;
|
||||
private String shmsPmlYn;
|
||||
private String ntfyCfmYn;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package io.shinhanlife.axhub.sample.dto;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
public class StrnTermListInDTO {
|
||||
private String strnTermHanNm;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package io.shinhanlife.axhub.sample.dto;
|
||||
|
||||
|
||||
import io.shinhanlife.glow.PageInfo;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Builder
|
||||
@Getter
|
||||
@Setter
|
||||
public class StrnTermListOutDTO {
|
||||
private List<StrnTerm> strnTerms;
|
||||
private PageInfo pageInfo;
|
||||
|
||||
@Builder
|
||||
@Getter
|
||||
@Setter
|
||||
public static class StrnTerm{
|
||||
private String strnTermHanNm;
|
||||
private String strnTermEngcAbrNm;
|
||||
private String strnTermEngNm;
|
||||
private String strnTermScrnTermNm;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package io.shinhanlife.axhub.sample.presentation;
|
||||
|
||||
|
||||
import io.shinhanlife.glow.BaseResponse;
|
||||
import io.shinhanlife.glow.GlowControllerId;
|
||||
import io.shinhanlife.glow.GlowLogTarget;
|
||||
import io.shinhanlife.glow.GlowLogger;
|
||||
import io.shinhanlife.glow.ResponseUtil;
|
||||
import io.shinhanlife.axhub.sample.presentation.io.AppliSystNtfyPatiRequest;
|
||||
import io.shinhanlife.axhub.sample.presentation.io.AppliSystNtfyPatiResponse;
|
||||
import io.shinhanlife.axhub.sample.presentation.io.StrnTermRequest;
|
||||
import io.shinhanlife.axhub.sample.presentation.io.StrnTermResponse;
|
||||
import io.shinhanlife.axhub.sample.usecase.GlowSampleUseCase;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
public class GlowSampleController {
|
||||
@GlowLogTarget({GlowLogTarget.Target.CONSOLE, GlowLogTarget.Target.FILE})
|
||||
private final GlowLogger log;
|
||||
private final GlowSampleUseCase glowSampleUseCase;
|
||||
|
||||
@GlowControllerId(value = "selectStrnTerm")
|
||||
@PostMapping("/selectStrnTerm")
|
||||
public ResponseEntity<BaseResponse<StrnTermResponse>> getStrnTerms(@RequestBody StrnTermRequest request) {
|
||||
return ResponseUtil.ok(glowSampleUseCase.getStrnTerms(request));
|
||||
}
|
||||
|
||||
@GlowControllerId(value = "insertAp")
|
||||
@PostMapping("/insertAppliSystNtfyPati")
|
||||
public ResponseEntity<BaseResponse<AppliSystNtfyPatiResponse>> insertAppliSystNtfyPati(@RequestBody AppliSystNtfyPatiRequest request) {
|
||||
return ResponseUtil.ok(glowSampleUseCase.insertAppliSystNtfyPati(request));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package io.shinhanlife.axhub.sample.presentation.io;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
@AllArgsConstructor
|
||||
public class AppliSystNtfyPatiRequest {
|
||||
private String appliSystNtfyPatiId;
|
||||
private String appliSystNtfyKdCd;
|
||||
private String appliDutjCd;
|
||||
private String appliDutjPrjcCd;
|
||||
private String ntfyMsgCt;
|
||||
private String ntfyOccDt;
|
||||
private String ntfyCfmDt;
|
||||
private String ntfyTrgtPrafNo;
|
||||
private String shmsPmlYn;
|
||||
private String ntfyCfmYn;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package io.shinhanlife.axhub.sample.presentation.io;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
@AllArgsConstructor
|
||||
public class AppliSystNtfyPatiResponse {
|
||||
private String successYn;
|
||||
private String msg;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package io.shinhanlife.axhub.sample.presentation.io;
|
||||
|
||||
import io.shinhanlife.glow.PageInfo;
|
||||
import lombok.*;
|
||||
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
@AllArgsConstructor
|
||||
public class StrnTermRequest {
|
||||
private PageInfo pageInfo;
|
||||
|
||||
private String strnTermHanNm;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package io.shinhanlife.axhub.sample.presentation.io;
|
||||
|
||||
import io.shinhanlife.glow.PageInfo;
|
||||
import lombok.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
@AllArgsConstructor
|
||||
public class StrnTermResponse {
|
||||
|
||||
private List<StrnTerm> strnTerms;
|
||||
private PageInfo pageInfo;
|
||||
|
||||
@Builder
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class StrnTerm{
|
||||
private String strnTermHanNm;
|
||||
private String strnTermEngcAbrNm;
|
||||
private String strnTermEngNm;
|
||||
private String strnTermScrnTermNm;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package io.shinhanlife.axhub.sample.usecase;
|
||||
|
||||
import io.shinhanlife.axhub.sample.presentation.io.AppliSystNtfyPatiRequest;
|
||||
import io.shinhanlife.axhub.sample.presentation.io.AppliSystNtfyPatiResponse;
|
||||
import io.shinhanlife.axhub.sample.presentation.io.StrnTermRequest;
|
||||
import io.shinhanlife.axhub.sample.presentation.io.StrnTermResponse;
|
||||
|
||||
public interface GlowSampleUseCase {
|
||||
StrnTermResponse getStrnTerms(StrnTermRequest req);
|
||||
AppliSystNtfyPatiResponse insertAppliSystNtfyPati(AppliSystNtfyPatiRequest req);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package io.shinhanlife.axhub.sample.usecase.impl;
|
||||
|
||||
import io.shinhanlife.axhub.sample.converter.SampleConverter;
|
||||
import io.shinhanlife.axhub.sample.domain.service.GlowSampleService;
|
||||
import io.shinhanlife.axhub.sample.dto.StrnTermListOutDTO;
|
||||
import io.shinhanlife.axhub.sample.presentation.io.AppliSystNtfyPatiRequest;
|
||||
import io.shinhanlife.axhub.sample.presentation.io.AppliSystNtfyPatiResponse;
|
||||
import io.shinhanlife.axhub.sample.presentation.io.StrnTermRequest;
|
||||
import io.shinhanlife.axhub.sample.presentation.io.StrnTermResponse;
|
||||
import io.shinhanlife.axhub.sample.usecase.GlowSampleUseCase;
|
||||
import io.shinhanlife.glow.GlowAppServiceId;
|
||||
import io.shinhanlife.glow.GlowLogTarget;
|
||||
import io.shinhanlife.glow.GlowLogger;
|
||||
import io.shinhanlife.glow.GlowServiceGroupId;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@GlowServiceGroupId(value = "GlowSample", description="GlowSampleUseCase")
|
||||
public class GlowSampleUseCaseImpl implements GlowSampleUseCase {
|
||||
@GlowLogTarget(GlowLogTarget.Target.FILE)
|
||||
private final GlowLogger log;
|
||||
private final GlowSampleService glowSampleService;
|
||||
private final SampleConverter converter;
|
||||
|
||||
@GlowAppServiceId(value = "SAMPLE-DB-0001", description = "표준용어조회(DB)")
|
||||
public StrnTermResponse getStrnTerms(StrnTermRequest req) {
|
||||
StrnTermListOutDTO outDto = glowSampleService.getStrnTerms(converter.convertRequestToDto(req), req.getPageInfo());
|
||||
|
||||
return converter.convertDtoToResponse(outDto);
|
||||
}
|
||||
|
||||
@GlowAppServiceId(value = "SAMPLE-DB-0002", description = "어플리케이션시스템알림내역등록")
|
||||
public AppliSystNtfyPatiResponse insertAppliSystNtfyPati(AppliSystNtfyPatiRequest req) {
|
||||
int rtn = glowSampleService.insertAppliSystNtfyPati(converter.convertAppliRequestToDto(req));
|
||||
|
||||
return AppliSystNtfyPatiResponse.builder()
|
||||
.successYn(rtn > 0 ? "Y" : "N")
|
||||
.msg(rtn > 0 ? "성공적으로 처리 되었습니다." : "처리중 오류발생!!")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user