forked from kimhyungsik/ax_hub_mcp_tool
feat(core, other): add MCI_STRING support and GlowTrgmField parser with sample tool
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
package io.shinhanlife.glow.util;
|
||||
|
||||
import io.shinhanlife.glow.GlowTrgmField;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.glow.util
|
||||
* @className GlowTrgmParser
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Slf4j
|
||||
public class GlowTrgmParser {
|
||||
|
||||
/**
|
||||
* 고정 길이 문자열 전문을 @GlowTrgmField 어노테이션 정보에 따라 DTO 객체로 파싱합니다.
|
||||
* @param trgmString 원본 전문 문자열
|
||||
* @param clazz 매핑할 DTO 클래스
|
||||
* @return 파싱된 DTO 인스턴스
|
||||
*/
|
||||
public static <T> T parse(String trgmString, Class<T> clazz) {
|
||||
if (trgmString == null || trgmString.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
T instance = clazz.getDeclaredConstructor().newInstance();
|
||||
|
||||
List<Field> fields = new ArrayList<>();
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
if (field.isAnnotationPresent(GlowTrgmField.class)) {
|
||||
fields.add(field);
|
||||
}
|
||||
}
|
||||
|
||||
fields.sort(Comparator.comparingInt(f -> f.getAnnotation(GlowTrgmField.class).order()));
|
||||
|
||||
int currentIndex = 0;
|
||||
for (Field field : fields) {
|
||||
GlowTrgmField annotation = field.getAnnotation(GlowTrgmField.class);
|
||||
int length = annotation.length();
|
||||
|
||||
if (currentIndex >= trgmString.length()) {
|
||||
break;
|
||||
}
|
||||
|
||||
int endIndex = Math.min(currentIndex + length, trgmString.length());
|
||||
String value = trgmString.substring(currentIndex, endIndex).trim();
|
||||
|
||||
field.setAccessible(true);
|
||||
setFieldValue(instance, field, value);
|
||||
|
||||
currentIndex += length;
|
||||
}
|
||||
|
||||
return instance;
|
||||
} catch (Exception e) {
|
||||
log.error("GlowTrgmField 파싱 중 오류 발생: {}", e.getMessage(), e);
|
||||
throw new RuntimeException("전문 파싱 오류", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setFieldValue(Object instance, Field field, String value) throws IllegalAccessException {
|
||||
Class<?> fieldType = field.getType();
|
||||
|
||||
if (fieldType == String.class) {
|
||||
field.set(instance, value);
|
||||
} else if (fieldType == int.class || fieldType == Integer.class) {
|
||||
field.set(instance, value.isEmpty() ? 0 : Integer.parseInt(value));
|
||||
} else if (fieldType == long.class || fieldType == Long.class) {
|
||||
field.set(instance, value.isEmpty() ? 0L : Long.parseLong(value));
|
||||
} else if (fieldType == boolean.class || fieldType == Boolean.class) {
|
||||
field.set(instance, Boolean.parseBoolean(value));
|
||||
} else if (fieldType == double.class || fieldType == Double.class) {
|
||||
field.set(instance, value.isEmpty() ? 0.0 : Double.parseDouble(value));
|
||||
} else {
|
||||
field.set(instance, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,7 @@ public class LegacyEimsConnector {
|
||||
private final EimsSender jspJsonEimsSender; // 41~50 (JSP JSON)
|
||||
|
||||
private final EimsSender mciEimsSender; // 실시간 연계 (기존 HTTP/TCP 대체, 동기식 API)
|
||||
private final EimsSender mciStringEimsSender; // 실시간 연계 (String 전문 버전)
|
||||
private final EimsSender eaiEimsSender; // 비동기/대용량 연계 (배치 통신 등)
|
||||
|
||||
private final ObjectMapper jsonMapper;
|
||||
@@ -76,6 +77,10 @@ public class LegacyEimsConnector {
|
||||
log.info("[라우팅] JSP JSON 통신으로 전달");
|
||||
legacyResponse = jspJsonEimsSender.send(interfaceId, payload);
|
||||
}
|
||||
else if ("MCI_STRING".equalsIgnoreCase(routingType)) {
|
||||
log.info("[라우팅] 실시간 AI 요청 -> MCI 연계 어댑터(String)를 통해 EIMS 전달");
|
||||
legacyResponse = mciStringEimsSender.send(interfaceId, payload);
|
||||
}
|
||||
else {
|
||||
// 3. 아키텍처 규격에 맞춘 라우팅 (실시간 vs 비동기)
|
||||
if (isMciRouting(routingType, interfaceId)) {
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package io.shinhanlife.axhub.biz.mcp.adapter.sender;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StopWatch;
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.axhub.biz.mcp.adapter.sender
|
||||
* @className MciStringEimsSender
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Slf4j
|
||||
@Service("mciStringEimsSender")
|
||||
public class MciStringEimsSender implements EimsSender {
|
||||
|
||||
private final RestClient restClient;
|
||||
private final String mciUrl;
|
||||
|
||||
public MciStringEimsSender(@Value("${eims.mci.url}") String mciUrl) {
|
||||
this.mciUrl = mciUrl;
|
||||
this.restClient = RestClient.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String send(String interfaceId, String payload) throws Exception {
|
||||
StopWatch stopWatch = new StopWatch(); stopWatch.start();
|
||||
|
||||
try {
|
||||
log.info("🌐 [ESB 어댑터(String)] 전송 준비 완료 - RestClient 호출 시작 (Interface: {})", interfaceId);
|
||||
|
||||
String response = restClient.post()
|
||||
.uri(mciUrl)
|
||||
.contentType(MediaType.TEXT_PLAIN)
|
||||
.body(payload != null ? payload : "")
|
||||
.retrieve()
|
||||
.body(String.class);
|
||||
|
||||
log.info("🌐 [ESB 어댑터(String)] 응답 수신 완료: {}", response);
|
||||
return response != null ? response : "";
|
||||
|
||||
} finally {
|
||||
stopWatch.stop();
|
||||
log.info("📊 [SLA 모니터링 - MCI(String)] 소요시간: {} ms", stopWatch.getTotalTimeMillis());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package io.shinhanlife.axhub.biz.mcp.tool.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.axhub.biz.mcp.tool.dto
|
||||
* @className SampleStringReq
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class SampleStringReq {
|
||||
private String query;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package io.shinhanlife.axhub.biz.mcp.tool.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import io.shinhanlife.glow.GlowTrgmField;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.axhub.biz.mcp.tool.dto
|
||||
* @className SampleStringRes
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class SampleStringRes {
|
||||
@GlowTrgmField(order = 1, length = 10, description = "이름")
|
||||
private String name;
|
||||
|
||||
@GlowTrgmField(order = 2, length = 3, description = "나이")
|
||||
private int age;
|
||||
|
||||
@GlowTrgmField(order = 3, length = 8, description = "가입일자(YYYYMMDD)")
|
||||
private String joinDate;
|
||||
|
||||
@GlowTrgmField(order = 4, length = 2, description = "상태코드")
|
||||
private String statusCode;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package io.shinhanlife.axhub.biz.mcp.tool.service;
|
||||
|
||||
import io.shinhanlife.axhub.biz.mcp.tool.annotation.McpFunction;
|
||||
import io.shinhanlife.axhub.biz.mcp.tool.annotation.McpTool;
|
||||
import io.shinhanlife.axhub.biz.mcp.tool.dto.SampleStringReq;
|
||||
import io.shinhanlife.axhub.biz.mcp.tool.dto.SampleStringRes;
|
||||
import io.shinhanlife.glow.util.GlowTrgmParser;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.axhub.biz.mcp.tool.service
|
||||
* @className SampleStringToolService
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Service
|
||||
@McpTool(
|
||||
routingType = "MCI_STRING",
|
||||
group = "COMMON"
|
||||
)
|
||||
public class SampleStringToolService extends AbstractMcpToolService {
|
||||
|
||||
@McpFunction(
|
||||
name = "get_sample_string",
|
||||
description = "MCI String 버전과 GlowTrgmField 파싱을 테스트하는 샘플 툴입니다.",
|
||||
prompt = "MCI 전문(String) 연계 및 고정 길이 파싱 테스트 해줘.",
|
||||
mappingId = "TRGM_001"
|
||||
)
|
||||
public Object execute(SampleStringReq req) {
|
||||
// 1. EIMS(Legacy)를 통해 원본 고정 길이 문자열을 받아옵니다.
|
||||
java.util.Map<String, Object> result = executeLegacy("MCI_STRING", "TRGM_001", req);
|
||||
|
||||
if ("ERROR".equals(result.get("status"))) {
|
||||
return result;
|
||||
}
|
||||
|
||||
String rawStringResponse = (String) result.get("legacy_response");
|
||||
|
||||
// 2. 받아온 고정 길이 전문(String)을 GlowTrgmParser를 이용해 DTO로 파싱합니다.
|
||||
return GlowTrgmParser.parse(rawStringResponse, SampleStringRes.class);
|
||||
}
|
||||
}
|
||||
@@ -44,3 +44,7 @@ mcp:
|
||||
description: "고객상세 정보 조회"
|
||||
prompt: "이 고객의 상세 기본정보(주소, 연락처 등)를 알려줘."
|
||||
mappingId: "CRM_002"
|
||||
get_sample_string:
|
||||
description: "MCI String 버전과 GlowTrgmField 파싱을 테스트하는 샘플 툴입니다."
|
||||
prompt: "MCI 전문(String) 연계 및 고정 길이 파싱 테스트 해줘."
|
||||
mappingId: "TRGM_001"
|
||||
|
||||
Reference in New Issue
Block a user