From 204dd04d5f3fc66eee6d0bfca83dc38297edb928 Mon Sep 17 00:00:00 2001 From: hjgram Date: Thu, 30 Jul 2026 06:19:06 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20OCI=20=EB=B0=B0=ED=8F=AC=20=EB=93=80?= =?UTF-8?q?=EC=96=BC=20LLM=20=EC=8A=A4=EC=9C=84=EC=B9=AD=20=EB=B0=8F=20?= =?UTF-8?q?=EC=8B=A0=EA=B7=9C=20=ED=88=B4=20DTO=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=A3=BC=EC=9E=85=20=EB=B3=91=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dap-gateway/build.gradle | 1 + .../dap/mcg/presentation/ChatController.java | 78 +++++++++++++- .../src/main/resources/application.yml | 6 +- .../src/main/resources/static/chat.html | 35 +++--- dap-tool-core/build.gradle | 1 + .../mcc/mcp/ToolMcpServerConfiguration.java | 26 +++++ .../mcc/mcp/ToolPodMcpToolSynchronizer.java | 74 +++++++++++++ .../ToolArgumentSchemaValidator.java | 30 ++++++ .../mcp/ToolMcpServerConfigurationTest.java | 22 ++++ .../ToolArgumentSchemaValidatorTest.java | 25 +++++ .../converter/MetaCommonCodeConverter.java | 31 ++++++ .../biz/cmm/converter/MetaTableConverter.java | 31 ++++++ .../biz/cmm/dto/MetaCommonCodeRequest.java | 32 ++++++ .../biz/cmm/dto/MetaCommonCodeResponse.java | 37 +++++++ .../dap/mcc/biz/cmm/dto/MetaTableRequest.java | 32 ++++++ .../mcc/biz/cmm/dto/MetaTableResponse.java | 37 +++++++ .../cmm/usecase/MetaCommonCodeUseCase.java | 37 +++++++ .../mcc/biz/cmm/usecase/MetaTableUseCase.java | 37 +++++++ .../impl/MetaCommonCodeUseCaseImpl.java | 89 +++++++++++++++ .../usecase/impl/MetaTableUseCaseImpl.java | 89 +++++++++++++++ .../sol/converter/SolReqDetailConverter.java | 31 ++++++ .../mcc/biz/sol/dto/SolReqDetailRequest.java | 27 +++++ .../mcc/biz/sol/dto/SolReqDetailResponse.java | 33 ++++++ .../biz/sol/usecase/SolReqDetailUseCase.java | 39 +++++++ .../usecase/impl/SolReqDetailUseCaseImpl.java | 101 ++++++++++++++++++ .../itrf/mci/ncl/g/io/SOLG00000002_I.java | 23 ++++ .../itrf/mci/ncl/g/io/SOLG00000002_O.java | 31 ++++++ .../impl/SolReqDetailUseCaseImplTest.java | 45 ++++++++ 28 files changed, 1059 insertions(+), 21 deletions(-) create mode 100644 dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfiguration.java create mode 100644 dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/mcp/ToolPodMcpToolSynchronizer.java create mode 100644 dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/presentation/ToolArgumentSchemaValidator.java create mode 100644 dap-tool-core/src/test/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfigurationTest.java create mode 100644 dap-tool-core/src/test/java/io/shinhanlife/dap/mcc/presentation/ToolArgumentSchemaValidatorTest.java create mode 100644 dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MetaCommonCodeConverter.java create mode 100644 dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MetaTableConverter.java create mode 100644 dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeRequest.java create mode 100644 dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeResponse.java create mode 100644 dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaTableRequest.java create mode 100644 dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaTableResponse.java create mode 100644 dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaCommonCodeUseCase.java create mode 100644 dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaTableUseCase.java create mode 100644 dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaCommonCodeUseCaseImpl.java create mode 100644 dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaTableUseCaseImpl.java create mode 100644 dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/converter/SolReqDetailConverter.java create mode 100644 dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqDetailRequest.java create mode 100644 dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqDetailResponse.java create mode 100644 dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/SolReqDetailUseCase.java create mode 100644 dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqDetailUseCaseImpl.java create mode 100644 dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/io/SOLG00000002_I.java create mode 100644 dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/io/SOLG00000002_O.java create mode 100644 dap-tool-oth/src/test/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqDetailUseCaseImplTest.java diff --git a/dap-gateway/build.gradle b/dap-gateway/build.gradle index 2980a8c8..609a4d72 100644 --- a/dap-gateway/build.gradle +++ b/dap-gateway/build.gradle @@ -11,6 +11,7 @@ dependencies { // Spring AI MCP Server implementation 'org.springframework.ai:spring-ai-starter-mcp-server-webmvc' implementation 'org.springframework.ai:spring-ai-starter-model-openai' + implementation 'org.springframework.ai:spring-ai-openai:2.0.0' // MyBatis & DB implementation 'org.springframework.boot:spring-boot-starter-jdbc' implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3' diff --git a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/presentation/ChatController.java b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/presentation/ChatController.java index 9567075d..58f06561 100644 --- a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/presentation/ChatController.java +++ b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/presentation/ChatController.java @@ -26,6 +26,7 @@ import org.springframework.ai.tool.definition.ToolDefinition; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import reactor.core.publisher.Flux; @@ -60,21 +61,90 @@ public class ChatController { Map resultMap = (Map) toolsResponse.getResult(); if (resultMap.containsKey("tools")) { List allTools = (List) resultMap.get("tools"); + Set addedToolNames = new HashSet<>(); for (ToolMetadata meta : allTools) { - if (Boolean.TRUE.equals(meta.getVisible())) { - callbacks.add(new DynamicMcpToolCallback(meta, executeService, objectMapper, effectiveTenantId)); + if (Boolean.TRUE.equals(meta.getVisible()) && meta.getName() != null) { + String cleanName = meta.getName().replaceAll("[^a-zA-Z0-9_-]", "_"); + if (!addedToolNames.contains(cleanName)) { + callbacks.add(new DynamicMcpToolCallback(meta, executeService, objectMapper, effectiveTenantId)); + addedToolNames.add(cleanName); + } } } } } - ChatClient chatClient = chatClientBuilder + String selectedModel = request.getOrDefault("model", "gemini-flash-latest").trim(); + if (selectedModel.isEmpty()) { + selectedModel = "gemini-flash-latest"; + } + + // 1. Google Gemini 무료 티어 직접 API 연동 (사용량 및 컴파일 충돌 차단용 Short-circuit) + if (selectedModel.equals("gemini-flash-latest")) { + String geminiKey = "AQ.Ab8RN6KFZggsQf8iooY1v_3h3vp2TIjiYB54dV4Yay3vVKEMtg"; + String geminiUrl = "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions"; + + Map body = new HashMap<>(); + body.put("model", "gemini-1.5-flash"); + body.put("stream", true); + body.put("messages", List.of(Map.of("role", "user", "content", message))); + + try { + java.net.http.HttpClient httpClient = java.net.http.HttpClient.newHttpClient(); + java.net.http.HttpRequest httpRequest = java.net.http.HttpRequest.newBuilder() + .uri(java.net.URI.create(geminiUrl)) + .header("Content-Type", "application/json") + .header("Authorization", "Bearer " + geminiKey) + .POST(java.net.http.HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(body))) + .build(); + + httpClient.sendAsync(httpRequest, java.net.http.HttpResponse.BodyHandlers.ofLines()) + .thenAccept(response -> { + response.body().forEach(line -> { + try { + if (line.startsWith("data: ")) { + String jsonStr = line.substring(6).trim(); + if (!jsonStr.equals("[DONE]")) { + JsonNode node = objectMapper.readTree(jsonStr); + String text = node.path("choices").path(0).path("delta").path("content").asText(""); + if (!text.isEmpty()) { + emitter.send(text); + } + } + } + } catch (Exception e) { + log.error("Gemini stream parse error: {}", e.getMessage()); + } + }); + emitter.complete(); + }) + .exceptionally(ex -> { + log.error("Gemini stream connection failed", ex); + try { + emitter.send("\n\n⚠️ **Gemini API 호출에 실패했습니다.** (" + ex.getMessage() + ")"); + } catch (Exception ignored) {} + emitter.completeWithError(ex); + return null; + }); + log.info("[Real AI Chat] 구글 제미나이 다이렉트 API 스트리밍 개시 완료!"); + return emitter; + } catch (Exception e) { + log.error("Gemini direct API setup error", e); + emitter.completeWithError(e); + return emitter; + } + } + + // 2. OpenRouter 무료 모델 처리 (Spring AI 빌드된 ChatClient 및 MCP 툴 호출 완벽 지원!) + ChatClient activeChatClient = chatClientBuilder .defaultSystem("You are AX HUB Assistant, a highly capable enterprise AI agent. You must use the provided tools to answer user questions when necessary. Always answer politely in Korean.") .build(); - Flux responseStream = chatClient.prompt() + Flux responseStream = activeChatClient.prompt() .user(message) .tools((Object[]) callbacks.toArray(new ToolCallback[0])) // Spring AI 2.0 uses tools() + .options(org.springframework.ai.openai.OpenAiChatOptions.builder() + .model(selectedModel)) .stream() .content(); diff --git a/dap-gateway/src/main/resources/application.yml b/dap-gateway/src/main/resources/application.yml index d7a606ad..0f030430 100644 --- a/dap-gateway/src/main/resources/application.yml +++ b/dap-gateway/src/main/resources/application.yml @@ -10,11 +10,11 @@ spring: timeout-per-shutdown-phase: 20s ai: openai: - api-key: AQ.Ab8RN6KFZggsQf8iooY1v_3h3vp2TIjiYB54dV4Yay3vVKEMtg - base-url: https://generativelanguage.googleapis.com/v1beta/openai/ + api-key: ${OPENROUTER_API_KEY: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} + base-url: https://openrouter.ai/api/v1 chat: options: - model: gemini-flash-latest + model: google/gemma-4-31b-it:free temperature: 0.3 server: diff --git a/dap-gateway/src/main/resources/static/chat.html b/dap-gateway/src/main/resources/static/chat.html index aaaf1c1d..193fab87 100644 --- a/dap-gateway/src/main/resources/static/chat.html +++ b/dap-gateway/src/main/resources/static/chat.html @@ -43,12 +43,23 @@

Powered by Mock Engine & MCP Tools

-
- - - - - Online +
+ + + +
+ + + + + Online +
@@ -173,14 +184,12 @@ .replace(/'/g, "'"); } - function removeMarkdownEmphasis(text) { - return text.replace(/\*\*/g, ''); - } - async function sendMessage() { const text = chatInput.value.trim(); if (!text || isLoading) return; + const selectedModel = document.getElementById('model-select').value; + // 1. 유저 메시지 추가 appendUserMessage(text); chatInput.value = ''; @@ -197,7 +206,7 @@ 'Content-Type': 'application/json', 'X-Agent-Id': 'TESTER-DEV' // 권한 통과를 위한 테스트 Agent ID }, - body: JSON.stringify({ message: text }) + body: JSON.stringify({ message: text, model: selectedModel }) }); if (!response.ok) throw new Error('Network response was not ok'); @@ -209,7 +218,6 @@ // 5. 스트림 읽기 const reader = response.body.getReader(); const decoder = new TextDecoder("utf-8"); - let responseText = ''; while (true) { const { done, value } = await reader.read(); @@ -220,8 +228,7 @@ for (const line of lines) { if (line.startsWith('data:')) { const data = line.substring(5); - responseText += data; - textContainer.textContent = removeMarkdownEmphasis(responseText); + textContainer.innerHTML += escapeHtml(data); scrollToBottom(); } } diff --git a/dap-tool-core/build.gradle b/dap-tool-core/build.gradle index 66471abf..fd52e4cd 100644 --- a/dap-tool-core/build.gradle +++ b/dap-tool-core/build.gradle @@ -25,6 +25,7 @@ dependencies { api 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.17.1' api 'com.fasterxml.jackson.core:jackson-databind:2.17.1' api 'com.networknt:json-schema-validator:1.4.0' + api 'org.springframework.ai:spring-ai-starter-mcp-server-webmvc' api 'org.springframework.kafka:spring-kafka:3.2.0' api 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0' } diff --git a/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfiguration.java b/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfiguration.java new file mode 100644 index 00000000..b07916ab --- /dev/null +++ b/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfiguration.java @@ -0,0 +1,26 @@ +package io.shinhanlife.dap.mcc.mcp; + +import io.modelcontextprotocol.server.transport.HttpServletStreamableServerTransportProvider; +import org.springframework.boot.web.servlet.ServletRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + +/** Exposes every Tool Pod through the MCP Streamable HTTP transport. */ +@Configuration +public class ToolMcpServerConfiguration { + + @Bean + @Primary + public HttpServletStreamableServerTransportProvider toolMcpTransportProvider() { + return HttpServletStreamableServerTransportProvider.builder() + .mcpEndpoint("/mcp") + .build(); + } + + @Bean + public ServletRegistrationBean toolMcpServlet( + HttpServletStreamableServerTransportProvider transportProvider) { + return new ServletRegistrationBean<>(transportProvider, "/mcp"); + } +} diff --git a/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/mcp/ToolPodMcpToolSynchronizer.java b/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/mcp/ToolPodMcpToolSynchronizer.java new file mode 100644 index 00000000..4fe9ca75 --- /dev/null +++ b/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/mcp/ToolPodMcpToolSynchronizer.java @@ -0,0 +1,74 @@ +package io.shinhanlife.dap.mcc.mcp; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.modelcontextprotocol.server.McpServerFeatures; +import io.modelcontextprotocol.server.McpSyncServer; +import io.modelcontextprotocol.spec.McpSchema; +import io.shinhanlife.dap.mcc.dto.ToolMetadata; +import io.shinhanlife.dap.mcc.presentation.BusinessToolController; +import io.shinhanlife.dap.mcc.usecase.ToolRegistryHeartbeatSender; +import java.util.LinkedHashMap; +import java.util.Map; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; + +/** Registers the Tool Pod's existing annotated tools with its MCP SDK server. */ +@Component +public class ToolPodMcpToolSynchronizer { + private final McpSyncServer mcpServer; + private final ToolRegistryHeartbeatSender heartbeatSender; + private final BusinessToolController businessToolController; + private final ObjectMapper objectMapper; + + public ToolPodMcpToolSynchronizer(McpSyncServer mcpServer, ToolRegistryHeartbeatSender heartbeatSender, + BusinessToolController businessToolController, ObjectMapper objectMapper) { + this.mcpServer = mcpServer; + this.heartbeatSender = heartbeatSender; + this.businessToolController = businessToolController; + this.objectMapper = objectMapper; + } + + @EventListener(ApplicationReadyEvent.class) + public void registerLocalTools() { + heartbeatSender.getAllScannedTools().stream() + .filter(tool -> Boolean.TRUE.equals(tool.getVisible())) + .forEach(tool -> mcpServer.addTool(specification(tool))); + } + + private McpServerFeatures.SyncToolSpecification specification(ToolMetadata tool) { + McpSchema.Tool mcpTool = McpSchema.Tool.builder() + .name(tool.getName()) + .description(tool.getDescription() == null || tool.getDescription().isBlank() ? tool.getName() + " Tool" : tool.getDescription()) + .inputSchema(tool.getParametersSchema() == null ? emptySchema() : tool.getParametersSchema()) + .annotations(McpSchema.ToolAnnotations.builder() + .readOnlyHint(Boolean.TRUE.equals(tool.getReadOnlyHint())) + .destructiveHint(Boolean.TRUE.equals(tool.getDestructiveHint())) + .idempotentHint(Boolean.TRUE.equals(tool.getIdempotentHint())) + .openWorldHint(Boolean.TRUE.equals(tool.getOpenWorldHint())).build()) + .build(); + return McpServerFeatures.SyncToolSpecification.builder().tool(mcpTool) + .callHandler((context, request) -> invoke(tool.getName(), request.arguments())).build(); + } + + private McpSchema.CallToolResult invoke(String toolName, Map arguments) { + ResponseEntity response = businessToolController.executeDynamicTool(toolName, null, null, null, arguments); + boolean failed = !response.getStatusCode().is2xxSuccessful(); + Object body = response.getBody(); + try { + return McpSchema.CallToolResult.builder().addTextContent(objectMapper.writeValueAsString(body)) + .structuredContent(body).isError(failed).build(); + } catch (Exception error) { + return McpSchema.CallToolResult.builder().addTextContent(String.valueOf(body)).isError(failed).build(); + } + } + + private Map emptySchema() { + Map schema = new LinkedHashMap<>(); + schema.put("type", "object"); + schema.put("properties", Map.of()); + schema.put("additionalProperties", false); + return schema; + } +} diff --git a/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/presentation/ToolArgumentSchemaValidator.java b/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/presentation/ToolArgumentSchemaValidator.java new file mode 100644 index 00000000..dc8cfeab --- /dev/null +++ b/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/presentation/ToolArgumentSchemaValidator.java @@ -0,0 +1,30 @@ +package io.shinhanlife.dap.mcc.presentation; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.JsonSchema; +import com.networknt.schema.JsonSchemaFactory; +import com.networknt.schema.SpecVersion; +import com.networknt.schema.ValidationMessage; +import java.util.Set; +import java.util.Map; +import org.springframework.stereotype.Component; + +/** Validates tool arguments with the NetworkNT version selected by the MCP SDK. */ +@Component +public class ToolArgumentSchemaValidator { + + private final ObjectMapper objectMapper; + + public ToolArgumentSchemaValidator(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + public Set validate(Map schemaDefinition, Map arguments) throws Exception { + JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); + JsonNode schemaNode = objectMapper.valueToTree(schemaDefinition); + JsonSchema schema = factory.getSchema(schemaNode); + JsonNode argumentsNode = objectMapper.valueToTree(arguments); + return schema.validate(argumentsNode); + } +} diff --git a/dap-tool-core/src/test/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfigurationTest.java b/dap-tool-core/src/test/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfigurationTest.java new file mode 100644 index 00000000..c0fb8101 --- /dev/null +++ b/dap-tool-core/src/test/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfigurationTest.java @@ -0,0 +1,22 @@ +package io.shinhanlife.dap.mcc.mcp; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import io.modelcontextprotocol.server.transport.HttpServletStreamableServerTransportProvider; +import org.junit.jupiter.api.Test; +import org.springframework.boot.web.servlet.ServletRegistrationBean; + +class ToolMcpServerConfigurationTest { + + private final ToolMcpServerConfiguration configuration = new ToolMcpServerConfiguration(); + + @Test + void exposesOnlyExactMcpEndpointSoLegacyMcpApiPathsRemainAvailable() { + HttpServletStreamableServerTransportProvider transport = configuration.toolMcpTransportProvider(); + ServletRegistrationBean registration = configuration.toolMcpServlet(transport); + + assertEquals("/mcp", registration.getUrlMappings().iterator().next()); + assertFalse(registration.getUrlMappings().contains("/mcp/*")); + } +} diff --git a/dap-tool-core/src/test/java/io/shinhanlife/dap/mcc/presentation/ToolArgumentSchemaValidatorTest.java b/dap-tool-core/src/test/java/io/shinhanlife/dap/mcc/presentation/ToolArgumentSchemaValidatorTest.java new file mode 100644 index 00000000..e10dcc7d --- /dev/null +++ b/dap-tool-core/src/test/java/io/shinhanlife/dap/mcc/presentation/ToolArgumentSchemaValidatorTest.java @@ -0,0 +1,25 @@ +package io.shinhanlife.dap.mcc.presentation; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class ToolArgumentSchemaValidatorTest { + + private final ToolArgumentSchemaValidator validator = new ToolArgumentSchemaValidator(new ObjectMapper()); + + @Test + void validatesDraft7SchemaWithTheRuntimeNetworkntVersion() throws Exception { + Map schema = Map.of( + "type", "object", + "properties", Map.of("name", Map.of("type", "string")), + "required", List.of("name")); + + assertTrue(validator.validate(schema, Map.of("name", "Hong")).isEmpty()); + assertFalse(validator.validate(schema, Map.of()).isEmpty()); + } +} diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MetaCommonCodeConverter.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MetaCommonCodeConverter.java new file mode 100644 index 00000000..87f56ee9 --- /dev/null +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MetaCommonCodeConverter.java @@ -0,0 +1,31 @@ +package io.shinhanlife.dap.mcc.biz.cmm.converter; + +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeRequest; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeResponse; +import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_I; +import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_O; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.converter + * @className MetaCommonCodeConverter + * @description AX HUB 시스템 처리 클래스 + * @author 09863409 + * @create 2026.07.29 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.07.29  09863409    최초생성
+ * 
+ * 
+ */ +@Mapper(componentModel = "spring") +public interface MetaCommonCodeConverter { + @Mapping(target = "csNo", source = "groupCode", defaultValue = "GRP_COMM_CD") + CLCNNB00001_I toLegacyRequest(MetaCommonCodeRequest req); + + @Mapping(target = "codeList", ignore = true) + MetaCommonCodeResponse toResponse(CLCNNB00001_O mciRes); +} diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MetaTableConverter.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MetaTableConverter.java new file mode 100644 index 00000000..c59e3524 --- /dev/null +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MetaTableConverter.java @@ -0,0 +1,31 @@ +package io.shinhanlife.dap.mcc.biz.cmm.converter; + +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableRequest; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableResponse; +import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_I; +import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_O; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.converter + * @className MetaTableConverter + * @description AX HUB 시스템 처리 클래스 + * @author 09863409 + * @create 2026.07.29 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.07.29  09863409    최초생성
+ * 
+ * 
+ */ +@Mapper(componentModel = "spring") +public interface MetaTableConverter { + @Mapping(target = "csNo", source = "tableName", defaultValue = "TB_META_BAS") + CLCNNB00001_I toLegacyRequest(MetaTableRequest req); + + @Mapping(target = "tableList", ignore = true) + MetaTableResponse toResponse(CLCNNB00001_O mciRes); +} diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeRequest.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeRequest.java new file mode 100644 index 00000000..a28a3afe --- /dev/null +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeRequest.java @@ -0,0 +1,32 @@ +package io.shinhanlife.dap.mcc.biz.cmm.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.shinhanlife.dap.lib.annotation.McpParameter; +import lombok.Data; + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.dto + * @className MetaCommonCodeRequest + * @description AX HUB 시스템 처리 클래스 + * @author 09863409 + * @create 2026.07.29 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.07.29  09863409    최초생성
+ * 
+ * 
+ */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class MetaCommonCodeRequest { + @McpParameter(description = "통합코드 그룹 ID (예: GRP_SYS_01, GRP_COMM_CD)", required = false) + private String groupCode; + + @McpParameter(description = "코드명 검색 키워드 (예: 사용, 상태)", required = false) + private String codeName; + + @McpParameter(description = "사용여부 (예: Y, N)", required = false) + private String useYn; +} diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeResponse.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeResponse.java new file mode 100644 index 00000000..653a32a2 --- /dev/null +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeResponse.java @@ -0,0 +1,37 @@ +package io.shinhanlife.dap.mcc.biz.cmm.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +import java.util.List; + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.dto + * @className MetaCommonCodeResponse + * @description AX HUB 시스템 처리 클래스 + * @author 09863409 + * @create 2026.07.29 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.07.29  09863409    최초생성
+ * 
+ * 
+ */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class MetaCommonCodeResponse { + private List codeList; + + @Data + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class MetaCommonCodeItem { + private String groupCode; + private String code; + private String codeName; + private String codeDesc; + private Integer sortSeq; + private String useYn; + } +} diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaTableRequest.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaTableRequest.java new file mode 100644 index 00000000..1712fdec --- /dev/null +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaTableRequest.java @@ -0,0 +1,32 @@ +package io.shinhanlife.dap.mcc.biz.cmm.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.shinhanlife.dap.lib.annotation.McpParameter; +import lombok.Data; + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.dto + * @className MetaTableRequest + * @description AX HUB 시스템 처리 클래스 + * @author 09863409 + * @create 2026.07.29 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.07.29  09863409    최초생성
+ * 
+ * 
+ */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class MetaTableRequest { + @McpParameter(description = "테이블 물리명 키워드 (예: TB_CUST_BAS, TB_CONT)", required = false) + private String tableName; + + @McpParameter(description = "테이블 논리명(한글) 키워드 (예: 고객기본, 계약)", required = false) + private String tableLogicalName; + + @McpParameter(description = "스키마/소유자명 (예: DAPADM, SHLOWN)", required = false) + private String owner; +} diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaTableResponse.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaTableResponse.java new file mode 100644 index 00000000..1b9eaf6b --- /dev/null +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaTableResponse.java @@ -0,0 +1,37 @@ +package io.shinhanlife.dap.mcc.biz.cmm.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +import java.util.List; + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.dto + * @className MetaTableResponse + * @description AX HUB 시스템 처리 클래스 + * @author 09863409 + * @create 2026.07.29 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.07.29  09863409    최초생성
+ * 
+ * 
+ */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class MetaTableResponse { + private List tableList; + + @Data + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class MetaTableItem { + private String owner; + private String tableName; + private String tableLogicalName; + private String tableDesc; + private Integer columnCount; + private Long rowCount; + } +} diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaCommonCodeUseCase.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaCommonCodeUseCase.java new file mode 100644 index 00000000..f8c2a52a --- /dev/null +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaCommonCodeUseCase.java @@ -0,0 +1,37 @@ +package io.shinhanlife.dap.mcc.biz.cmm.usecase; + +import io.shinhanlife.dap.lib.annotation.McpFunction; +import io.shinhanlife.dap.lib.annotation.McpTool; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeRequest; + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.usecase + * @className MetaCommonCodeUseCase + * @description AX HUB 시스템 처리 클래스 + * @author 09863409 + * @create 2026.07.29 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.07.29  09863409    최초생성
+ * 
+ * 
+ */ +@McpTool( + routingType = "MCI", + categoryKey = "cmm" +) +public interface MetaCommonCodeUseCase { + @McpFunction( + displayName = "메타 통합코드 조회 툴", + name = "metaCommonCode", + description = "메타 통합코드 목록을 조회해줘", + prompt = "메타 통합코드 목록을 조회해줘", + mappingId = "CLCNNB00001", + register = false, + requiresApproval = false, + openWorldHint = true + ) + Object execute(MetaCommonCodeRequest req); +} diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaTableUseCase.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaTableUseCase.java new file mode 100644 index 00000000..e2ec7c64 --- /dev/null +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaTableUseCase.java @@ -0,0 +1,37 @@ +package io.shinhanlife.dap.mcc.biz.cmm.usecase; + +import io.shinhanlife.dap.lib.annotation.McpFunction; +import io.shinhanlife.dap.lib.annotation.McpTool; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableRequest; + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.usecase + * @className MetaTableUseCase + * @description AX HUB 시스템 처리 클래스 + * @author 09863409 + * @create 2026.07.29 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.07.29  09863409    최초생성
+ * 
+ * 
+ */ +@McpTool( + routingType = "MCI", + categoryKey = "cmm" +) +public interface MetaTableUseCase { + @McpFunction( + displayName = "메타 테이블 조회 툴", + name = "metaTable", + description = "메타 테이블 정보 목록을 조회해줘", + prompt = "메타 테이블 정보 목록을 조회해줘", + mappingId = "CLCNNB00001", + register = false, + requiresApproval = false, + openWorldHint = true + ) + Object execute(MetaTableRequest req); +} diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaCommonCodeUseCaseImpl.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaCommonCodeUseCaseImpl.java new file mode 100644 index 00000000..92ddf781 --- /dev/null +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaCommonCodeUseCaseImpl.java @@ -0,0 +1,89 @@ +package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.cmm.converter.MetaCommonCodeConverter; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeRequest; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeResponse; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeResponse.MetaCommonCodeItem; +import io.shinhanlife.dap.mcc.biz.cmm.usecase.MetaCommonCodeUseCase; +import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.MciCfpaClient; +import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_I; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl + * @className MetaCommonCodeUseCaseImpl + * @description AX HUB 시스템 처리 클래스 + * @author 09863409 + * @create 2026.07.29 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.07.29  09863409    최초생성
+ * 
+ * 
+ */ +@Slf4j +@Service +@RequiredArgsConstructor +public class MetaCommonCodeUseCaseImpl implements MetaCommonCodeUseCase { + + private final MciCfpaClient mci; + private final MetaCommonCodeConverter converter; + + @Override + public Object execute(MetaCommonCodeRequest req) { + log.info("[MCI Tool] {} 요청 수신. 파라미터: {}", "metaCommonCode", req); + try { + CLCNNB00001_I mciRequest = converter.toLegacyRequest(req); + + Object mciResponse = mci.callCfpa0001(mciRequest); + log.info("[MCI Tool] CLCNNB00001 MCI call completed. Returning response status: {}", + mciResponse != null); + + // 현업 테스트용으로 MCI 통신을 바이패스하고 하드코딩된 메타 통합코드 샘플 결과를 반환합니다. + MetaCommonCodeResponse res = new MetaCommonCodeResponse(); + List list = new ArrayList<>(); + + MetaCommonCodeItem item1 = new MetaCommonCodeItem(); + item1.setGroupCode(req.getGroupCode() != null ? req.getGroupCode() : "GRP_COMM_CD"); + item1.setCode("CD001"); + item1.setCodeName("진행중"); + item1.setCodeDesc("SR 요청 처리 진행 중 상태"); + item1.setSortSeq(1); + item1.setUseYn("Y"); + list.add(item1); + + MetaCommonCodeItem item2 = new MetaCommonCodeItem(); + item2.setGroupCode(req.getGroupCode() != null ? req.getGroupCode() : "GRP_COMM_CD"); + item2.setCode("CD002"); + item2.setCodeName("완료"); + item2.setCodeDesc("SR 요청 처리 완료 상태"); + item2.setSortSeq(2); + item2.setUseYn("Y"); + list.add(item2); + + MetaCommonCodeItem item3 = new MetaCommonCodeItem(); + item3.setGroupCode(req.getGroupCode() != null ? req.getGroupCode() : "GRP_COMM_CD"); + item3.setCode("CD003"); + item3.setCodeName("보류"); + item3.setCodeDesc("SR 요청 처리 일시 보류 상태"); + item3.setSortSeq(3); + item3.setUseYn("N"); + list.add(item3); + + res.setCodeList(list); + + return res; + } catch (Exception e) { + log.error("[MCI Tool] 연동 중 오류 발생: {}", e.getMessage(), e); + return Map.of("status", "ERROR", "message", e.getMessage() != null ? e.getMessage() : "Unknown error"); + } + } +} diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaTableUseCaseImpl.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaTableUseCaseImpl.java new file mode 100644 index 00000000..ef981898 --- /dev/null +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaTableUseCaseImpl.java @@ -0,0 +1,89 @@ +package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.cmm.converter.MetaTableConverter; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableRequest; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableResponse; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableResponse.MetaTableItem; +import io.shinhanlife.dap.mcc.biz.cmm.usecase.MetaTableUseCase; +import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.MciCfpaClient; +import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_I; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl + * @className MetaTableUseCaseImpl + * @description AX HUB 시스템 처리 클래스 + * @author 09863409 + * @create 2026.07.29 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.07.29  09863409    최초생성
+ * 
+ * 
+ */ +@Slf4j +@Service +@RequiredArgsConstructor +public class MetaTableUseCaseImpl implements MetaTableUseCase { + + private final MciCfpaClient mci; + private final MetaTableConverter converter; + + @Override + public Object execute(MetaTableRequest req) { + log.info("[MCI Tool] {} 요청 수신. 파라미터: {}", "metaTable", req); + try { + CLCNNB00001_I mciRequest = converter.toLegacyRequest(req); + + Object mciResponse = mci.callCfpa0001(mciRequest); + log.info("[MCI Tool] CLCNNB00001 MCI call completed. Returning response status: {}", + mciResponse != null); + + // 현업 테스트용으로 MCI 통신을 바이패스하고 하드코딩된 메타 테이블 샘플 결과를 반환합니다. + MetaTableResponse res = new MetaTableResponse(); + List list = new ArrayList<>(); + + MetaTableItem item1 = new MetaTableItem(); + item1.setOwner(req.getOwner() != null ? req.getOwner() : "DAPADM"); + item1.setTableName(req.getTableName() != null ? req.getTableName() : "TB_CUST_BAS"); + item1.setTableLogicalName("고객기본정보"); + item1.setTableDesc("고객 기본 프로필 및 인적사항 관리 테이블"); + item1.setColumnCount(35); + item1.setRowCount(1250000L); + list.add(item1); + + MetaTableItem item2 = new MetaTableItem(); + item2.setOwner(req.getOwner() != null ? req.getOwner() : "DAPADM"); + item2.setTableName("TB_CONT_MCD"); + item2.setTableLogicalName("계약주계약정보"); + item2.setTableDesc("보험 계약 주계약 상세 원장 테이블"); + item2.setColumnCount(58); + item2.setRowCount(3400000L); + list.add(item2); + + MetaTableItem item3 = new MetaTableItem(); + item3.setOwner(req.getOwner() != null ? req.getOwner() : "DAPADM"); + item3.setTableName("TB_CLAIM_DTL"); + item3.setTableLogicalName("청구접수상세"); + item3.setTableDesc("보험금 청구 접수 건별 내역 테이블"); + item3.setColumnCount(42); + item3.setRowCount(890000L); + list.add(item3); + + res.setTableList(list); + + return res; + } catch (Exception e) { + log.error("[MCI Tool] 연동 중 오류 발생: {}", e.getMessage(), e); + return Map.of("status", "ERROR", "message", e.getMessage() != null ? e.getMessage() : "Unknown error"); + } + } +} diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/converter/SolReqDetailConverter.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/converter/SolReqDetailConverter.java new file mode 100644 index 00000000..6b3a7019 --- /dev/null +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/converter/SolReqDetailConverter.java @@ -0,0 +1,31 @@ +package io.shinhanlife.dap.mcc.biz.sol.converter; + +import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqDetailRequest; +import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqDetailResponse; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io.SOLG00000002_I; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io.SOLG00000002_O; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +/** + * @package io.shinhanlife.dap.mcc.biz.sol.converter + * @className SolReqDetailConverter + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ *
+ * 
+ */ +@Mapper(componentModel = "spring") +public interface SolReqDetailConverter { + + @Mapping(source = "srId", target = "srId") + SOLG00000002_I toLegacyRequest(SolReqDetailRequest req); + + SolReqDetailResponse toResponse(SOLG00000002_O mciRes); +} diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqDetailRequest.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqDetailRequest.java new file mode 100644 index 00000000..13045419 --- /dev/null +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqDetailRequest.java @@ -0,0 +1,27 @@ +package io.shinhanlife.dap.mcc.biz.sol.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.shinhanlife.dap.lib.annotation.McpParameter; +import lombok.Data; + +/** + * @package io.shinhanlife.dap.mcc.biz.sol.dto + * @className SolReqDetailRequest + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ *
+ * 
+ */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class SolReqDetailRequest { + + @McpParameter(description = "상세 조회할 SOL 의뢰서 ID", required = true) + private String srId; +} diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqDetailResponse.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqDetailResponse.java new file mode 100644 index 00000000..b1c1c230 --- /dev/null +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqDetailResponse.java @@ -0,0 +1,33 @@ +package io.shinhanlife.dap.mcc.biz.sol.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +/** + * @package io.shinhanlife.dap.mcc.biz.sol.dto + * @className SolReqDetailResponse + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ *
+ * 
+ */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class SolReqDetailResponse { + + private String srId; + private String srName; + private String process; + private String devStage; + private String appName; + private String requester; + private String requestDate; + private String dueDate; + private String description; +} diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/SolReqDetailUseCase.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/SolReqDetailUseCase.java new file mode 100644 index 00000000..043bb0a2 --- /dev/null +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/SolReqDetailUseCase.java @@ -0,0 +1,39 @@ +package io.shinhanlife.dap.mcc.biz.sol.usecase; + +import io.shinhanlife.dap.lib.annotation.McpFunction; +import io.shinhanlife.dap.lib.annotation.McpTool; +import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqDetailRequest; + +/** + * @package io.shinhanlife.dap.mcc.biz.sol.usecase + * @className SolReqDetailUseCase + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ *
+ * 
+ */ +@McpTool( + routingType = "MCI", + categoryKey = "sol" +) +public interface SolReqDetailUseCase { + + @McpFunction( + displayName = "SolReqDetail 툴", + name = "solReqDetail", + description = "SOL 의뢰서 상세 조회", + prompt = "SOL 의뢰서 상세 조회해줘", + mappingId = "SOLG00000002", + register = false, + requiresApproval = false, + readOnlyHint = true, + openWorldHint = true + ) + Object execute(SolReqDetailRequest req); +} diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqDetailUseCaseImpl.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqDetailUseCaseImpl.java new file mode 100644 index 00000000..be197500 --- /dev/null +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqDetailUseCaseImpl.java @@ -0,0 +1,101 @@ +package io.shinhanlife.dap.mcc.biz.sol.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.sol.converter.SolReqDetailConverter; +import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqDetailRequest; +import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqDetailResponse; +import io.shinhanlife.dap.mcc.biz.sol.usecase.SolReqDetailUseCase; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.MciNclgClient; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io.SOLG00000002_I; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io.SOLG00000002_O; +import io.shinhanlife.glow.communication.dto.Transfer; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +/** + * @package io.shinhanlife.dap.mcc.biz.sol.usecase.impl + * @className SolReqDetailUseCaseImpl + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ *
+ * 
+ */ +@Slf4j +@Service +@RequiredArgsConstructor +public class SolReqDetailUseCaseImpl implements SolReqDetailUseCase { + + private final MciNclgClient mci; + private final SolReqDetailConverter converter; + + @Value("${sol.req-detail.mock-enabled:false}") + private boolean mockEnabled; + + @Override + public Object execute(SolReqDetailRequest req) { + log.info("[MCI Tool] {} 요청 수신. 파라미터: {}", "solReqDetail", req); + if (req == null || req.getSrId() == null || req.getSrId().isBlank()) { + return Map.of("status", "ERROR", "message", "srId는 필수입니다."); + } + + if (mockEnabled) { + return createLocalSampleResponse(req.getSrId()); + } + + try { + SOLG00000002_I mciRequest = converter.toLegacyRequest(req); + Transfer mciResponse = mci.callTo( + "SOLG00000002", "SOLG00000002", mciRequest, SOLG00000002_O.class); + + if (mciResponse == null || mciResponse.getBody() == null) { + return Map.of("status", "NOT_FOUND", "message", "의뢰서 상세 정보를 찾을 수 없습니다."); + } + return converter.toResponse(mciResponse.getBody()); + } catch (Exception e) { + log.error("[MCI Tool] 연동 중 오류 발생: {}", e.getMessage(), e); + return Map.of( + "status", "ERROR", + "message", e.getMessage() != null ? e.getMessage() : "Unknown error"); + } + } + + private Object createLocalSampleResponse(String srId) { + SolReqDetailResponse response = new SolReqDetailResponse(); + if ("SR-2026-001".equalsIgnoreCase(srId)) { + response.setSrId("SR-2026-001"); + response.setSrName("AX HUB 메인 화면 UI 개편"); + response.setProcess("진행중"); + response.setDevStage("개발(단위테스트)"); + response.setAppName("AX HUB"); + response.setRequester("신한준"); + response.setRequestDate("2026-07-01"); + response.setDueDate("2026-08-31"); + response.setDescription("AX HUB 메인 화면의 사용성과 접근성을 개선하는 UI 개편 의뢰입니다."); + return response; + } + if ("SR-2026-002".equalsIgnoreCase(srId)) { + response.setSrId("SR-2026-002"); + response.setSrName("SOL 연동 모듈 추가 개발"); + response.setProcess("진행중"); + response.setDevStage("분석/설계"); + response.setAppName("MCP Gateway"); + response.setRequester("고석민"); + response.setRequestDate("2026-07-15"); + response.setDueDate("2026-09-30"); + response.setDescription("SOL 의뢰서 조회 기능을 MCP 도구로 제공하기 위한 연동 모듈 개발 의뢰입니다."); + return response; + } + return Map.of( + "status", "NOT_FOUND", + "message", "의뢰서를 찾을 수 없습니다.", + "srId", srId); + } +} diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/io/SOLG00000002_I.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/io/SOLG00000002_I.java new file mode 100644 index 00000000..3b8b2cb1 --- /dev/null +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/io/SOLG00000002_I.java @@ -0,0 +1,23 @@ +package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io; + +import lombok.Data; + +/** + * @package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io + * @className SOLG00000002_I + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ *
+ * 
+ */ +@Data +public class SOLG00000002_I { + + private String srId; +} diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/io/SOLG00000002_O.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/io/SOLG00000002_O.java new file mode 100644 index 00000000..1aa61472 --- /dev/null +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/io/SOLG00000002_O.java @@ -0,0 +1,31 @@ +package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io; + +import lombok.Data; + +/** + * @package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io + * @className SOLG00000002_O + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ *
+ * 
+ */ +@Data +public class SOLG00000002_O { + + private String srId; + private String srName; + private String process; + private String devStage; + private String appName; + private String requester; + private String requestDate; + private String dueDate; + private String description; +} diff --git a/dap-tool-oth/src/test/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqDetailUseCaseImplTest.java b/dap-tool-oth/src/test/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqDetailUseCaseImplTest.java new file mode 100644 index 00000000..e2b245f0 --- /dev/null +++ b/dap-tool-oth/src/test/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqDetailUseCaseImplTest.java @@ -0,0 +1,45 @@ +package io.shinhanlife.dap.mcc.biz.sol.usecase.impl; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.shinhanlife.dap.mcc.biz.sol.converter.SolReqDetailConverter; +import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqDetailRequest; +import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqDetailResponse; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.MciNclgClient; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.test.util.ReflectionTestUtils; + +/** + * @package io.shinhanlife.dap.mcc.biz.sol.usecase.impl + * @className SolReqDetailUseCaseImplTest + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ *
+ * 
+ */ +class SolReqDetailUseCaseImplTest { + + @Test + void returnsLocalSampleDetailBySrId() { + MciNclgClient mci = Mockito.mock(MciNclgClient.class); + SolReqDetailConverter converter = Mockito.mock(SolReqDetailConverter.class); + SolReqDetailUseCaseImpl useCase = new SolReqDetailUseCaseImpl(mci, converter); + ReflectionTestUtils.setField(useCase, "mockEnabled", true); + + SolReqDetailRequest request = new SolReqDetailRequest(); + request.setSrId("SR-2026-001"); + + SolReqDetailResponse response = (SolReqDetailResponse) useCase.execute(request); + + assertThat(response.getSrId()).isEqualTo("SR-2026-001"); + assertThat(response.getSrName()).isEqualTo("AX HUB 메인 화면 UI 개편"); + Mockito.verifyNoInteractions(mci); + } +}