refactor: Merge dap-common into dap-tool-core
This commit is contained in:
@@ -1,35 +0,0 @@
|
||||
plugins {
|
||||
id 'java-library'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api 'org.springframework.boot:spring-boot-starter-web'
|
||||
api 'org.springframework.boot:spring-boot-starter-validation'
|
||||
api 'org.springframework.boot:spring-boot-starter-data-redis'
|
||||
|
||||
api 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0'
|
||||
api 'io.github.resilience4j:resilience4j-core:2.2.0'
|
||||
api 'io.github.resilience4j:resilience4j-circuitbreaker:2.2.0'
|
||||
api 'io.github.resilience4j:resilience4j-ratelimiter'
|
||||
api 'io.github.resilience4j:resilience4j-retry:2.2.0'
|
||||
api 'org.springframework.boot:spring-boot-starter-aop:3.3.0'
|
||||
|
||||
api 'org.springframework.boot:spring-boot-starter-jdbc'
|
||||
api 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3'
|
||||
api 'com.h2database:h2'
|
||||
api 'p6spy:p6spy:3.9.1'
|
||||
api 'io.lettuce:lettuce-core:6.6.0.RELEASE'
|
||||
|
||||
api "org.mapstruct:mapstruct:1.5.5.Final"
|
||||
|
||||
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.kafka:spring-kafka:3.2.0'
|
||||
api 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly 'org.projectlombok:lombok:1.18.32'
|
||||
annotationProcessor 'org.projectlombok:lombok:1.18.32'
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package io.shinhanlife.dap.common.adapter.dto;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.common.adapter.dto
|
||||
* @className ErrorDetail
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
public class ErrorDetail {
|
||||
private int code;
|
||||
private String message;
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package io.shinhanlife.dap.common.adapter.dto;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.common.adapter.dto
|
||||
* @className JsonRpcRequest
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class JsonRpcRequest {
|
||||
private String jsonrpc;
|
||||
private String method;
|
||||
private Params params;
|
||||
private String id;
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package io.shinhanlife.dap.common.adapter.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
// 2. 응답 DTO
|
||||
/**
|
||||
* @package io.shinhanlife.dap.common.adapter.dto
|
||||
* @className JsonRpcResponse
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@JsonPropertyOrder({"jsonrpc", "result", "error", "id"})
|
||||
public class JsonRpcResponse {
|
||||
public String jsonrpc = "2.0";
|
||||
public Object result;
|
||||
public Object error;
|
||||
public String id;
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package io.shinhanlife.dap.common.adapter.dto;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.common.adapter.dto
|
||||
* @className Params
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Params {
|
||||
private String routingType;
|
||||
private String name;
|
||||
private String interfaceId;
|
||||
private Map<String, Object> data;
|
||||
private List<Map<String, Object>> spec;
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package io.shinhanlife.dap.common.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.common.config
|
||||
* @className CorsConfig
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Configuration
|
||||
public class CorsConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/**") // 모든 엔드포인트에 대해 CORS 허용
|
||||
.allowedOriginPatterns("*") // 외부 Agent Builder 등 모든 오리진 허용
|
||||
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH") // 허용할 HTTP 메서드
|
||||
.allowedHeaders("*") // 모든 헤더 허용
|
||||
.exposedHeaders("Mcp-Session-Id") // MCP-HTTP 세션 아이디 노출 허용
|
||||
.allowCredentials(true) // 쿠키/인증 정보 허용
|
||||
.maxAge(3600); // preflight 요청 캐시 시간 (초 단위)
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package io.shinhanlife.dap.common.config;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.apache.ibatis.session.SqlSessionFactory;
|
||||
import org.mybatis.spring.SqlSessionFactoryBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.common.config
|
||||
* @className MybatisConfig
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Configuration
|
||||
public class MybatisConfig {
|
||||
|
||||
@Bean
|
||||
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
|
||||
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
|
||||
sessionFactory.setDataSource(dataSource);
|
||||
return sessionFactory.getObject();
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package io.shinhanlife.dap.common.config;
|
||||
|
||||
import com.p6spy.engine.logging.Category;
|
||||
import com.p6spy.engine.spy.appender.MessageFormattingStrategy;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.common.config
|
||||
* @className P6SpySqlFormatter
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
public class P6SpySqlFormatter implements MessageFormattingStrategy {
|
||||
|
||||
@Override
|
||||
public String formatMessage(int connectionId, String now, long elapsed,
|
||||
String category, String prepared, String sql, String url) {
|
||||
|
||||
if (sql == null || sql.isBlank()) return "";
|
||||
if (Category.STATEMENT.getName().equals(category)) {
|
||||
|
||||
String prettySQL = sql
|
||||
.replaceAll("(?i)\\bSELECT\\b", "\nSELECT")
|
||||
.replaceAll("(?i)\\bFROM\\b", "\n FROM")
|
||||
.replaceAll("(?i)\\bWHERE\\b", "\n WHERE")
|
||||
.replaceAll("(?i)\\bAND\\b", "\n AND")
|
||||
.replaceAll("(?i)\\bOR\\b", "\n OR")
|
||||
.replaceAll("(?i)\\bINNER JOIN\\b", "\n INNER JOIN")
|
||||
.replaceAll("(?i)\\bLEFT JOIN\\b", "\n LEFT JOIN")
|
||||
.replaceAll("(?i)\\bORDER BY\\b", "\n ORDER BY")
|
||||
.replaceAll("(?i)\\bGROUP BY\\b", "\n GROUP BY")
|
||||
.replaceAll("(?i)\\bINSERT INTO\\b", "\nINSERT INTO")
|
||||
.replaceAll("(?i)\\bVALUES\\b", "\n VALUES")
|
||||
.replaceAll("(?i)\\bUPDATE\\b", "\nUPDATE")
|
||||
.replaceAll("(?i)\\bSET\\b", "\n SET")
|
||||
.replaceAll("(?i)\\bDELETE FROM\\b", "\nDELETE FROM");
|
||||
|
||||
return String.format("""
|
||||
\n┌─────────────────────────────────────────
|
||||
│ SQL [%dms]
|
||||
│%s
|
||||
└─────────────────────────────────────────
|
||||
""", elapsed, prettySQL.indent(2).stripTrailing());
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
package io.shinhanlife.dap.common.integration;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
// TODO: 실제 Glow Framework 의존성이 추가되면 아래 주석들을 풀고 사용하세요!
|
||||
// import io.shinhanlife.glow.communication.dto.CommonHeader;
|
||||
// import io.shinhanlife.glow.communication.dto.Transfer;
|
||||
// import io.shinhanlife.glow.communication.module.eai.component.GlowEaiComponent;
|
||||
// import io.shinhanlife.glow.communication.module.mci.component.GlowExtMciComponent;
|
||||
// import io.shinhanlife.glow.communication.module.mci.component.GlowMciComponent;
|
||||
// import io.shinhanlife.glow.communication.util.CommonHeaderFactory;
|
||||
|
||||
/**
|
||||
* [MCI / EAI 공통 연동 래퍼(Wrapper) 템플릿]
|
||||
* 신한라이프 Glow Framework 개발표준정의서를 바탕으로 대내/대외망/EAI 통신을
|
||||
* MCP 툴에서 손쉽게 호출할 수 있도록 일원화한 컴포넌트입니다.
|
||||
*/
|
||||
/**
|
||||
* @package io.shinhanlife.dap.common.integration
|
||||
* @className GlowIntegrationCall
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class GlowIntegrationCall {
|
||||
|
||||
// 1. 실제 의존성이 주입될 프레임워크 컴포넌트들 (임시 주석 처리)
|
||||
/*
|
||||
private final GlowMciComponent mci;
|
||||
private final GlowEaiComponent eai;
|
||||
private final GlowExtMciComponent extMci;
|
||||
*/
|
||||
|
||||
/**
|
||||
* 1. 대외 MCI 호출 (타행, 금융결제원 등 외부 기관)
|
||||
* 가이드 2.2.2에 명시된 필수 파라미터(기관코드, 종별코드, 업무코드, 거래코드)를 모두 포함합니다.
|
||||
*/
|
||||
/*
|
||||
public <S, R> Transfer<R> callExtMci(String itrfId, String frbuCd, String cmouDutjCd, String cmouCssfCd, String cmouTraCd, S body, Class<R> resBody) {
|
||||
|
||||
// 1. 공통 헤더 생성
|
||||
CommonHeader header = CommonHeaderFactory.createRequestHeader(itrfId);
|
||||
|
||||
// 2. 대외 전용 필수 코드 세팅 로직 (프레임워크 내부 스펙에 맞게 가공)
|
||||
// (예: 헤더에 해당 속성들을 주입하거나 Transfer 객체에 싣는 과정 추가)
|
||||
|
||||
// 3. Transfer 객체 빌드
|
||||
Transfer<S> req = Transfer.<S>builder()
|
||||
.header(header)
|
||||
.body(body)
|
||||
.build();
|
||||
|
||||
// 4. 대외 MCI 컴포넌트를 통해 최종 전송
|
||||
return extMci.call(req, resBody);
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* 2. EAI 호출 (대내망 중계기)
|
||||
* 가이드 2.3에 명시된 대로 수신서비스 ID 없이 인터페이스 ID(itrfId)만 필수로 받습니다.
|
||||
*/
|
||||
/*
|
||||
public <S, R> Transfer<R> callEai(String itrfId, S body, Class<R> resBody) {
|
||||
|
||||
// 1. EAI는 인터페이스 ID만으로 심플하게 헤더 생성
|
||||
CommonHeader header = CommonHeaderFactory.createRequestHeader(itrfId);
|
||||
|
||||
// 2. Transfer 객체 빌드
|
||||
Transfer<S> req = Transfer.<S>builder()
|
||||
.header(header)
|
||||
.body(body)
|
||||
.build();
|
||||
|
||||
// 3. EAI 컴포넌트를 통해 최종 전송
|
||||
return eai.call(req, resBody);
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* 3. 대내 MCI 호출 (사내 시스템 간 통신)
|
||||
*/
|
||||
/*
|
||||
public <S, R> Transfer<R> callMci(String itrfId, S body, Class<R> resBody) {
|
||||
|
||||
CommonHeader header = CommonHeaderFactory.createRequestHeader(itrfId);
|
||||
|
||||
Transfer<S> req = Transfer.<S>builder()
|
||||
.header(header)
|
||||
.body(body)
|
||||
.build();
|
||||
|
||||
return mci.call(req, resBody);
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package io.shinhanlife.dap.common.integration.dto;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import java.util.List;
|
||||
|
||||
// TODO: 실제 Glow Framework 의존성이 추가되면 아래 주석을 풀고 사용하세요!
|
||||
// import io.shinhanlife.glow.communication.annotation.GlowMciFieldInfo;
|
||||
|
||||
/**
|
||||
* [대외 MCI 연동용 DTO 표준 템플릿]
|
||||
* Glow Framework 개발표준정의서(2.2.1 IO 작성) 규칙을 100% 준수한 샘플입니다.
|
||||
* 새로운 대외 통신 전문을 만들 때 이 파일을 복사해서 필드명과 길이만 수정하여 사용하세요.
|
||||
*/
|
||||
/**
|
||||
* @package io.shinhanlife.dap.common.integration.dto
|
||||
* @className SampleGlowMessage
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor(access = AccessLevel.PUBLIC) // [규칙 1] Reflection을 위한 기본 생성자 필수 (public 유지)
|
||||
public class SampleGlowMessage {
|
||||
|
||||
// [규칙 2] @GlowMciFieldInfo 선언 필수 (order: 순서)
|
||||
// @GlowMciFieldInfo(order = 1)
|
||||
private MessageHeader header;
|
||||
|
||||
// [규칙 2] @GlowMciFieldInfo 선언 필수 (order: 순서)
|
||||
// @GlowMciFieldInfo(order = 2)
|
||||
private List<MessageBody> msgDtdvValu; // 다건(List) 본문 데이터
|
||||
|
||||
|
||||
@Getter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor(access = AccessLevel.PUBLIC)
|
||||
public static class MessageHeader {
|
||||
|
||||
// [규칙 2] 단건 필드의 경우 length 필수 입력 (EIMS 길이와 일치해야 함)
|
||||
// @GlowMciFieldInfo(order = 1, length = 1)
|
||||
private String msgTnsmTypeCd;
|
||||
|
||||
// @GlowMciFieldInfo(order = 2, length = 8)
|
||||
private int msdvLen;
|
||||
|
||||
// [규칙 3] 다건(List) 건수 필드의 경우, target 속성에 대상 변수명("msgDtdvValu") 필수 기입!
|
||||
// @GlowMciFieldInfo(order = 3, length = 2, target = "msgDtdvValu")
|
||||
private int msgRpttCc;
|
||||
}
|
||||
|
||||
|
||||
@Getter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor(access = AccessLevel.PUBLIC)
|
||||
public static class MessageBody {
|
||||
|
||||
// @GlowMciFieldInfo(order = 1, length = 8)
|
||||
private String msgCd;
|
||||
|
||||
// @GlowMciFieldInfo(order = 2, length = 1)
|
||||
private String msgPrnAttrCd;
|
||||
|
||||
// @GlowMciFieldInfo(order = 3, length = 200)
|
||||
private String msgCt;
|
||||
|
||||
// @GlowMciFieldInfo(order = 4, length = 200)
|
||||
private String anxMsgCt;
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package io.shinhanlife.dap.common.integration.mci.config;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.common.integration.mci.config
|
||||
* @className ShinhanIntegrationProperties
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "shinhan.integration")
|
||||
public class ShinhanIntegrationProperties {
|
||||
|
||||
/**
|
||||
* 환경유형코드: 운영(R), 테스트(T), 개발(D)
|
||||
*/
|
||||
private String envrTypeCd = "D";
|
||||
|
||||
private ServerInfo eai = new ServerInfo();
|
||||
private ServerInfo internalMci = new ServerInfo();
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class ServerInfo {
|
||||
private String url;
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package io.shinhanlife.dap.common.integration.mci.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonUnwrapped;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.common.integration.mci.dto
|
||||
* @className MciRequestWrapper
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class MciRequestWrapper<T> {
|
||||
private ShinhanCommonHeaderDto tgrmCmnnhddValu;
|
||||
|
||||
@JsonUnwrapped
|
||||
private T body;
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package io.shinhanlife.dap.common.integration.mci.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonUnwrapped;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.common.integration.mci.dto
|
||||
* @className MciResponseWrapper
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class MciResponseWrapper<T> {
|
||||
private ShinhanCommonHeaderDto tgrmCmnnhddValu;
|
||||
|
||||
@JsonUnwrapped
|
||||
private T body;
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package io.shinhanlife.dap.common.integration.mci.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.common.integration.mci.dto
|
||||
* @className ShinhanCommonHeaderDto
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ShinhanCommonHeaderDto {
|
||||
|
||||
private String envrTypeCd; // 환경유형코드 (D, T, R)
|
||||
private String glbId; // 전사공통키 (37 Byte)
|
||||
private String pgrsSriaNo; // 진행일련번호
|
||||
private String trgmVrsnInfoValu; // 전문버전정보값
|
||||
private String tgrmEncrYn; // 전문암호화여부
|
||||
private String gpcpCd; // 글로벌법인코드
|
||||
private String appliDutjCd; // 어플리케이션업무코드
|
||||
private String rcvSvcId; // 수신서비스ID
|
||||
private String reqRspnScCd; // 요청응답구분코드 (S:요청, R:응답)
|
||||
private String inqrTraTypeCd; // 조회거래유형코드
|
||||
private String reqTgrmTnsmDtptDt; // 요청전문전송일시
|
||||
private String itrIfId; // 인터페이스ID
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package io.shinhanlife.dap.common.mcp.config;
|
||||
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.common.mcp.config
|
||||
* @className CacheConfig
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
public class CacheConfig {
|
||||
|
||||
// 스프링이 캐시를 관리할 기본 저장소를 빈(Bean)으로 등록합니다.
|
||||
@Bean
|
||||
public CacheManager cacheManager() {
|
||||
return new ConcurrentMapCacheManager("eimsData"); // 아까 설정한 캐시 이름 등록
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package io.shinhanlife.dap.common.mcp.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.common.mcp.config
|
||||
* @className JacksonConfig
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Configuration
|
||||
public class JacksonConfig {
|
||||
|
||||
// 1. JSON 변환기(ObjectMapper)를 스프링 Bean으로 등록
|
||||
@Bean
|
||||
@Primary
|
||||
public ObjectMapper jsonMapper() {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
return mapper;
|
||||
}
|
||||
|
||||
// 2. XML 변환기(XmlMapper)를 스프링 Bean으로 등록
|
||||
@Bean
|
||||
public XmlMapper xmlMapper() {
|
||||
return new XmlMapper();
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package io.shinhanlife.dap.common.mcp.config;
|
||||
|
||||
import org.apache.kafka.clients.producer.ProducerConfig;
|
||||
import org.apache.kafka.common.serialization.StringSerializer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
|
||||
import org.springframework.kafka.core.KafkaTemplate;
|
||||
import org.springframework.kafka.core.ProducerFactory;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.common.mcp.config
|
||||
* @className KafkaLocalConfig
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Configuration
|
||||
public class KafkaLocalConfig {
|
||||
|
||||
@Value("${spring.kafka.bootstrap-servers:localhost:9092}")
|
||||
private String bootstrapServers;
|
||||
|
||||
// 1. 카프카 전송 공장(Factory) 세팅
|
||||
@Bean
|
||||
public ProducerFactory<String, String> producerFactory() {
|
||||
Map<String, Object> configProps = new HashMap<>();
|
||||
// 가짜 로컬 주소 혹은 환경변수 세팅
|
||||
configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
|
||||
// 데이터를 카프카로 보낼 때 문자열(String) 형태로 변환하겠다는 규칙
|
||||
configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
|
||||
configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
|
||||
|
||||
// 3초 만에 빠른 실패 처리 (로컬 무한 대기 방지)
|
||||
configProps.put(ProducerConfig.MAX_BLOCK_MS_CONFIG, 3000);
|
||||
// 재접속 주기를 10초로 설정 (콘솔 로그 도배 방지)
|
||||
configProps.put(ProducerConfig.RECONNECT_BACKOFF_MAX_MS_CONFIG, 10000);
|
||||
|
||||
return new DefaultKafkaProducerFactory<>(configProps);
|
||||
}
|
||||
|
||||
// 2. EaiEimsSender가 애타게 찾던 KafkaTemplate을 스프링 Bean으로 등록!
|
||||
@Bean
|
||||
public KafkaTemplate<String, String> kafkaTemplate() {
|
||||
return new KafkaTemplate<>(producerFactory());
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package io.shinhanlife.dap.common.mcp.config;
|
||||
|
||||
import io.swagger.v3.oas.models.Components;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.common.mcp.config
|
||||
* @className SwaggerConfig
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Configuration
|
||||
public class SwaggerConfig {
|
||||
|
||||
@Bean
|
||||
public OpenAPI customOpenAPI() {
|
||||
return new OpenAPI()
|
||||
.info(new Info()
|
||||
.title("Shinhan MCP Gateway API 명세서")
|
||||
.version("v1.0")
|
||||
.description("AI Agent와 신한라이프 내부망(EIMS/EAI)을 연결하는 Adapter Gateway API 문서입니다."))
|
||||
.addServersItem(new io.swagger.v3.oas.models.servers.Server().url("http://localhost:8080").description("Adapter Pod (8080)"))
|
||||
.addServersItem(new io.swagger.v3.oas.models.servers.Server().url("http://localhost:8081").description("Gateway Pod (8081)"))
|
||||
// 전역적으로 X-API-KEY 보안 설정을 Swagger UI에 추가합니다.
|
||||
.addSecurityItem(new SecurityRequirement().addList("X-API-KEY"))
|
||||
.components(new Components()
|
||||
.addSecuritySchemes("X-API-KEY",
|
||||
new SecurityScheme()
|
||||
.name("X-API-KEY")
|
||||
.type(SecurityScheme.Type.APIKEY)
|
||||
.in(SecurityScheme.In.HEADER)
|
||||
.description("헤더에 API Key를 입력해주세요. ")));
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package io.shinhanlife.dap.common.mcp.config;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
|
||||
import io.shinhanlife.dap.common.mcp.security.ApiKeyInterceptor;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.common.mcp.config
|
||||
* @className WebConfig
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
// 1. 우리가 만든 인터셉터를 주입받습니다.
|
||||
private final ApiKeyInterceptor apiKeyInterceptor;
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
// 2. 인터셉터 등록 및 검사할 URL 패턴 지정
|
||||
registry.addInterceptor(apiKeyInterceptor)
|
||||
.addPathPatterns("/rpc/**", "/mcp/api/v1/**") // /rpc/, /mcp/api/v1/ 로 시작하는 모든 API는 API Key 검사 수행!
|
||||
.excludePathPatterns(
|
||||
"/test/**", "/health", "/error", "/mcp/api/v1/admin/**",
|
||||
"/swagger-ui/**", "/v3/api-docs/**", "/swagger-resources/**", "/webjars/**", // Swagger UI 경로는 인증 제외
|
||||
"/mcp/api/v1/tools/docs/markdown", "/favicon.ico", "/mcp/api/v1/tools/list"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
// Swagger UI(8080)에서 Gateway(8081)로 API 호출 시 발생하는 CORS 에러 해결
|
||||
registry.addMapping("/**")
|
||||
.allowedOriginPatterns("*")
|
||||
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
||||
.allowedHeaders("*")
|
||||
.exposedHeaders("Mcp-Session-Id")
|
||||
.allowCredentials(true);
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
package io.shinhanlife.dap.common.mcp.exception;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.common.mcp.exception
|
||||
* @className GlobalExceptionHandler
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
import io.shinhanlife.dap.common.adapter.dto.ErrorDetail;
|
||||
import io.shinhanlife.dap.common.adapter.dto.JsonRpcResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.servlet.resource.NoResourceFoundException;
|
||||
|
||||
@Slf4j
|
||||
@RestControllerAdvice // 이 어노테이션이 전역 적용의 핵심입니다!
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(NoResourceFoundException.class)
|
||||
public ResponseEntity<Void> handleNoResourceFound(NoResourceFoundException e) {
|
||||
log.warn(" [Gateway Not Found] 요청하신 리소스를 찾을 수 없습니다: {}", e.getResourcePath());
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
@ExceptionHandler(IllegalArgumentException.class)
|
||||
public ResponseEntity<JsonRpcResponse> handleIllegalArgument(IllegalArgumentException e) {
|
||||
log.warn(" [Gateway Bad Request] 잘못된 요청: {}", e.getMessage());
|
||||
return buildErrorResponse(-32602, "Invalid params: " + e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
public ResponseEntity<JsonRpcResponse> handleRuntime(RuntimeException e) {
|
||||
log.error(" [Gateway Internal Error] 시스템 장애: {}", e.getMessage(), e);
|
||||
return buildErrorResponse(-32603, "Internal error: " + e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<JsonRpcResponse> handleAllException(Exception e) {
|
||||
log.error(" [Gateway Fatal Error] 치명적 오류 발생", e);
|
||||
return buildErrorResponse(-32000, "Server error: 시스템 관리자에게 문의하세요.");
|
||||
}
|
||||
|
||||
private ResponseEntity<JsonRpcResponse> buildErrorResponse(int code, String message) {
|
||||
JsonRpcResponse response = new JsonRpcResponse();
|
||||
response.setError(new ErrorDetail(code, message));
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
package io.shinhanlife.dap.common.mcp.filter;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.slf4j.MDC;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.common.mcp.filter
|
||||
* @className MdcLoggingFilter
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Component
|
||||
public class MdcLoggingFilter extends OncePerRequestFilter {
|
||||
|
||||
private static final String TRACE_ID_HEADER = "X-Trace-Id";
|
||||
private static final String MDC_KEY = "traceId";
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
|
||||
// 클라이언트가 보낸 Trace ID가 있으면 쓰고, 없으면 새로 생성
|
||||
String traceId = request.getHeader(TRACE_ID_HEADER);
|
||||
if (traceId == null || traceId.isEmpty()) {
|
||||
// 간결하게 8자리 UUID만 사용
|
||||
traceId = UUID.randomUUID().toString().substring(0, 8);
|
||||
}
|
||||
|
||||
// 로깅 컨텍스트에 고유 ID 저장
|
||||
MDC.put(MDC_KEY, traceId);
|
||||
|
||||
try {
|
||||
// 이 요청이 처리되는 동안 찍히는 모든 log.info, log.error에 traceId가 자동으로 붙습니다.
|
||||
filterChain.doFilter(request, response);
|
||||
} finally {
|
||||
// 메모리 누수 방지를 위해 요청이 끝나면 반드시 비워줍니다.
|
||||
MDC.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package io.shinhanlife.dap.common.mcp.security;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.slf4j.MDC;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.common.mcp.security
|
||||
* @className ApiKeyInterceptor
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ApiKeyInterceptor implements HandlerInterceptor {
|
||||
|
||||
// 1. 다중 테넌트 API Key 목록이 담긴 프로퍼티 객체를 주입받습니다.
|
||||
private final SecurityProperties securityProperties;
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
|
||||
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
String apiKey = request.getHeader("X-API-KEY");
|
||||
Map<String, String> validApiKeys = securityProperties.getApiKeys();
|
||||
|
||||
// 2. 만약 프로퍼티에 API Key가 하나도 설정되어 있지 않다면 (개발/로컬 환경 등) 인증 없이 통과시킵니다.
|
||||
if (validApiKeys == null || validApiKeys.isEmpty()) {
|
||||
MDC.put("tenantId", "anonymous");
|
||||
request.setAttribute("tenantId", "anonymous");
|
||||
log.debug(" [보안 패스] 등록된 API Key 없음 - 익명 사용자(anonymous)로 통과");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 3. 헤더로 들어온 API Key가 우리가 발급해준 목록(Map)에 존재하는지 확인합니다.
|
||||
if (apiKey == null || !validApiKeys.containsKey(apiKey)) {
|
||||
log.warn(" [보안 차단] 유효하지 않은 API Key 접근 시도 - IP: {}", request.getRemoteAddr());
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid API Key");
|
||||
return false; // 컨트롤러로 넘어가지 않음
|
||||
}
|
||||
|
||||
// 4. 유효하다면 해당 키에 맵핑된 Tenant ID(식별자)를 가져옵니다. (ex. mcp-client-1)
|
||||
String tenantId = validApiKeys.get(apiKey);
|
||||
|
||||
// 4. 추출한 Tenant ID를 현재 스레드의 로깅 컨텍스트(MDC)에 저장합니다.
|
||||
// 이렇게 하면 이 요청이 끝날 때까지 찍히는 모든 로그에 어떤 테넌트가 호출했는지 자동으로 기록됩니다.
|
||||
MDC.put("tenantId", tenantId);
|
||||
|
||||
// 5. 필요시 컨트롤러 로직에서 사용할 수 있도록 Request 속성에도 담아줍니다.
|
||||
request.setAttribute("tenantId", tenantId);
|
||||
|
||||
log.debug(" [보안 통과] API Key 인증 성공 - 접속 테넌트: {}", tenantId);
|
||||
|
||||
return true; // 인증 통과! 컨트롤러로 진행
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
|
||||
// 6. 메모리 누수를 방지하기 위해 요청 처리가 완전히 끝나면 MDC에서 테넌트 정보를 지워줍니다.
|
||||
MDC.remove("tenantId");
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package io.shinhanlife.dap.common.mcp.security;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* [다중 테넌트 설정 매핑 클래스]
|
||||
* application-local.properties 파일에 정의된 mcp.security.api-keys.* 설정들을
|
||||
* Map 자료구조로 자동 바인딩(주입) 받기 위한 설정 클래스입니다.
|
||||
*/
|
||||
/**
|
||||
* @package io.shinhanlife.dap.common.mcp.security
|
||||
* @className SecurityProperties
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "mcp.security")
|
||||
public class SecurityProperties {
|
||||
// API Key를 Key로, Tenant ID를 Value로 가지는 맵
|
||||
private Map<String, String> apiKeys = new HashMap<>();
|
||||
|
||||
// Tenant ID를 Key로, 허용된 도메인 그룹 목록을 Value로 가지는 맵 (ex. mcp-client-1 -> [CUSTOMER, COMMON])
|
||||
// 만약 "ALL" 이 포함되어 있다면 모든 도메인에 접근 허용
|
||||
private Map<String, List<String>> tenantDomains = new HashMap<>();
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package io.shinhanlife.dap.common.session.converter;
|
||||
|
||||
import io.shinhanlife.dap.common.session.dto.SessionDto;
|
||||
import io.shinhanlife.dap.common.session.dto.ZtUsacOutDto;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.common.session.converter
|
||||
* @className ZtUsacConverter
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Mapper(componentModel = "spring")
|
||||
public abstract class ZtUsacConverter {
|
||||
|
||||
@Mapping(target = "loginDtm", ignore = true)
|
||||
@Mapping(target = "isManager", ignore = true)
|
||||
public abstract SessionDto toSessionDto(ZtUsacOutDto dto);
|
||||
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package io.shinhanlife.dap.common.session.domain.model;
|
||||
|
||||
import io.shinhanlife.glow.db.dto.AuditInfo;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.common.session.domain.model
|
||||
* @className ZtUsacModel
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
@AllArgsConstructor
|
||||
public class ZtUsacModel extends AuditInfo {
|
||||
|
||||
/* 인사번호 */
|
||||
private String prafNo;
|
||||
/* 인사명 */
|
||||
private String prafNm;
|
||||
/* 조직번호 */
|
||||
private String ognzNo;
|
||||
/* 이메일주소 */
|
||||
private String addre;
|
||||
/* 인사직무코드 */
|
||||
private String prafOfduCd;
|
||||
/* 인사직무명 */
|
||||
private String prafOfduNm;
|
||||
/* 인사직급코드 */
|
||||
private String prafOfleCd;
|
||||
/* 인사직급명 */
|
||||
private String prafOfleNm;
|
||||
/* 인사직책코드 */
|
||||
private String prafDutyCd;
|
||||
/* 인사직책명 */
|
||||
private String prafDutyNm;
|
||||
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package io.shinhanlife.dap.common.session.domain.repository;
|
||||
|
||||
import io.shinhanlife.glow.GlowMybatisMapper;
|
||||
import io.shinhanlife.dap.common.session.dto.ZtUsacInDto;
|
||||
import io.shinhanlife.dap.common.session.dto.ZtUsacOutDto;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.common.session.domain.repository
|
||||
* @className ZtUsacRepository
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@GlowMybatisMapper
|
||||
public interface ZtUsacRepository {
|
||||
|
||||
/**
|
||||
* 사용자 조회 (단건)
|
||||
*
|
||||
* @param dto 사번
|
||||
* @return 인사정보
|
||||
*/
|
||||
ZtUsacOutDto selectZtUsac(ZtUsacInDto dto);
|
||||
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package io.shinhanlife.dap.common.session.domain.service;
|
||||
|
||||
import io.shinhanlife.dap.common.session.dto.ZtUsacInDto;
|
||||
import io.shinhanlife.dap.common.session.dto.ZtUsacOutDto;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.common.session.domain.service
|
||||
* @className ZtUsacService
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
public interface ZtUsacService {
|
||||
|
||||
/**
|
||||
* 사용자 조회 (단건)
|
||||
*
|
||||
* @param dto 사번
|
||||
* @return 인사정보
|
||||
*/
|
||||
ZtUsacOutDto selectZtUsac(ZtUsacInDto dto);
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package io.shinhanlife.dap.common.session.domain.service.impl;
|
||||
|
||||
import io.shinhanlife.dap.common.session.domain.repository.ZtUsacRepository;
|
||||
import io.shinhanlife.dap.common.session.domain.service.ZtUsacService;
|
||||
import io.shinhanlife.dap.common.session.dto.ZtUsacInDto;
|
||||
import io.shinhanlife.dap.common.session.dto.ZtUsacOutDto;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.common.session.domain.service.impl
|
||||
* @className ZtUsacServiceImpl
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ZtUsacServiceImpl implements ZtUsacService {
|
||||
|
||||
private final ZtUsacRepository ztUsacRepository;
|
||||
|
||||
/**
|
||||
* 사용자 조회 (단건)
|
||||
*
|
||||
* @param dto 사번
|
||||
* @return 인사정보
|
||||
*/
|
||||
@Override
|
||||
public ZtUsacOutDto selectZtUsac(ZtUsacInDto dto) {
|
||||
ZtUsacOutDto result = ztUsacRepository.selectZtUsac(dto);
|
||||
result.initLists();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
package io.shinhanlife.dap.common.session.dto;
|
||||
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.common.session.dto
|
||||
* @className SessionDto
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class SessionDto {
|
||||
|
||||
/* 인사번호 */
|
||||
private String prafNo;
|
||||
/* 인사명 */
|
||||
private String prafNm;
|
||||
/* 조직번호 */
|
||||
private String ognzNo;
|
||||
/* 조직번호 */
|
||||
private String ognzNm;
|
||||
/* 이메일주소 */
|
||||
private String addre;
|
||||
/* 인사직무코드 */
|
||||
private String prafOfduCd;
|
||||
/* 인사직무명 */
|
||||
private String prafOfduNm;
|
||||
/* 인사직급코드 */
|
||||
private String prafOfleCd;
|
||||
/* 인사직급명 */
|
||||
private String prafOfleNm;
|
||||
/* 인사직책코드 */
|
||||
private String prafDutyCd;
|
||||
/* 인사직책명 */
|
||||
private String prafDutyNm;
|
||||
|
||||
private List<String> roleNoList;
|
||||
private List<String> roleNmList;
|
||||
private List<String> tgtrPrafNoList;
|
||||
private List<String> tgtrOgnzNoList;
|
||||
|
||||
|
||||
// 유틸성
|
||||
private String loginDtm; // 로그인일시
|
||||
private String isManager; // 관리자여부
|
||||
|
||||
public void setLoginDtm() {
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
|
||||
this.loginDtm = LocalDateTime.now().format(formatter);
|
||||
}
|
||||
|
||||
public void setIsManager(String isManager) {
|
||||
// TODO 역할 필터링 후 관리자 여부 체크
|
||||
this.isManager = "Y";
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package io.shinhanlife.dap.common.session.dto;
|
||||
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.common.session.dto
|
||||
* @className ZtUsacInDto
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ZtUsacInDto {
|
||||
/* 인사번호 */
|
||||
private String prafNo;
|
||||
|
||||
/* 사용여부 */
|
||||
@Builder.Default
|
||||
private String puseYn = "Y";
|
||||
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package io.shinhanlife.dap.common.session.dto;
|
||||
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.common.session.dto
|
||||
* @className ZtUsacOutDto
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
@AllArgsConstructor
|
||||
@Setter
|
||||
public class ZtUsacOutDto {
|
||||
|
||||
/* 인사번호 */
|
||||
private String prafNo;
|
||||
/* 인사명 */
|
||||
private String prafNm;
|
||||
/* 조직번호 */
|
||||
private String ognzNo;
|
||||
/* 조직명 */
|
||||
private String ognzNm;
|
||||
/* 이메일주소 */
|
||||
// @GlowSecureField(type = DataSecureType.DECRYPT_DB, direction = SafeDBType.COMM) TODO 방화벽 뚫리면 확인
|
||||
private String addre;
|
||||
/* 인사직무코드 */
|
||||
private String prafOfduCd;
|
||||
/* 인사직무명 */
|
||||
private String prafOfduNm;
|
||||
/* 인사직급코드 */
|
||||
private String prafOfleCd;
|
||||
/* 인사직급명 */
|
||||
private String prafOfleNm;
|
||||
/* 인사직책코드 */
|
||||
private String prafDutyCd;
|
||||
/* 인사직책명 */
|
||||
private String prafDutyNm;
|
||||
/* 사용여부 */
|
||||
private String puseYn;
|
||||
|
||||
private String roleNoStrList;
|
||||
private String roleNmStrList;
|
||||
private String tgtrPrafNoStrList;
|
||||
private String tgtrOgnzNoStrList;
|
||||
|
||||
private List<String> roleNoList;
|
||||
private List<String> roleNmList;
|
||||
private List<String> tgtrPrafNoList;
|
||||
private List<String> tgtrOgnzNoList;
|
||||
|
||||
public void initLists() {
|
||||
this.roleNoList = convertStrToList(roleNoStrList);
|
||||
this.roleNmList = convertStrToList(roleNmStrList);
|
||||
this.tgtrPrafNoList = convertStrToList(tgtrPrafNoStrList);
|
||||
this.tgtrOgnzNoList = convertStrToList(tgtrOgnzNoStrList);
|
||||
}
|
||||
|
||||
private List<String> convertStrToList(String str) {
|
||||
if (str == null || str.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return Arrays.asList(str.split(","));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
package io.shinhanlife.dap.common.session.presentation;
|
||||
|
||||
import io.micrometer.common.util.StringUtils;
|
||||
import io.shinhanlife.glow.BaseResponse;
|
||||
import io.shinhanlife.glow.BizException;
|
||||
import io.shinhanlife.glow.GlowControllerId;
|
||||
import io.shinhanlife.glow.ResponseUtil;
|
||||
import io.shinhanlife.dap.common.session.converter.ZtUsacConverter;
|
||||
import io.shinhanlife.dap.common.session.domain.service.ZtUsacService;
|
||||
import io.shinhanlife.dap.common.session.dto.SessionDto;
|
||||
import io.shinhanlife.dap.common.session.dto.ZtUsacInDto;
|
||||
import io.shinhanlife.dap.common.session.dto.ZtUsacOutDto;
|
||||
import io.shinhanlife.dap.common.session.presentation.io.SsoResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.ibatis.javassist.NotFoundException;
|
||||
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.Objects;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.common.session.presentation
|
||||
* @className SsoRestController
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
@RequestMapping("/sso")
|
||||
public class SsoRestController {
|
||||
|
||||
private static final String NLS_LOGIN_URL = "";
|
||||
private final ZtUsacService ztUsacService;
|
||||
private final ZtUsacConverter ztUsacConverter;
|
||||
|
||||
/**
|
||||
* sso 연동 전 임시 로그인
|
||||
*
|
||||
* @param request
|
||||
* @param response
|
||||
* @param session
|
||||
* @param <T>
|
||||
* @return
|
||||
*/
|
||||
@GlowControllerId("tempLogin")
|
||||
@PostMapping("/tempLogin")
|
||||
public <T> ResponseEntity<BaseResponse<SsoResponse>> tempLogin(HttpServletRequest request, HttpServletResponse response,
|
||||
HttpSession session, @RequestBody SessionDto requestDto) {
|
||||
try {
|
||||
if (StringUtils.isEmpty(requestDto.getPrafNo())) {
|
||||
throw new NotFoundException("SSO >> not found sso id");
|
||||
}
|
||||
|
||||
// DB 유저 가져오기
|
||||
ZtUsacOutDto ztUsacOutDto = ztUsacService.selectZtUsac(ZtUsacInDto.builder().prafNo(requestDto.getPrafNo()).puseYn("Y")
|
||||
.build());
|
||||
if (Objects.isNull(ztUsacOutDto) || StringUtils.isEmpty(ztUsacOutDto.getPrafNo())) {
|
||||
throw new BizException("SSO >> not found UserInfo >> retCode:");
|
||||
}
|
||||
|
||||
SessionDto sessionDto = ztUsacConverter.toSessionDto(ztUsacOutDto);
|
||||
sessionDto.setLoginDtm(); // 로그인 시점 세팅
|
||||
session.setAttribute("userInfo", sessionDto);
|
||||
return ResponseUtil.ok(SsoResponse.builder().retCode("0").userInfo(sessionDto).build());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("로그인 실패", e);
|
||||
session.invalidate();
|
||||
}
|
||||
|
||||
return ResponseUtil.ok(SsoResponse.builder().redirectUrl(NLS_LOGIN_URL).build());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package io.shinhanlife.dap.common.session.presentation.io;
|
||||
|
||||
import io.shinhanlife.dap.common.session.dto.SessionDto;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.common.session.presentation.io
|
||||
* @className SsoResponse
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SsoResponse {
|
||||
|
||||
private String retCode;
|
||||
private SessionDto userInfo;
|
||||
private String redirectUrl;
|
||||
|
||||
}
|
||||
@@ -1,276 +0,0 @@
|
||||
package io.shinhanlife.dap.common.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Scanner;
|
||||
|
||||
public class PodScaffolder {
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
Scanner scanner = new Scanner(System.in);
|
||||
|
||||
System.out.println("=========================================");
|
||||
System.out.println(" MCP Tool Pod Scaffolder (Java CLI) ");
|
||||
System.out.println("=========================================\n");
|
||||
|
||||
String rawModuleName = getOrAsk(args, 0, scanner, "1. 생성할 모듈(Pod) 이름 (예: payment 또는 dap-tool-payment): ");
|
||||
String moduleName = rawModuleName.startsWith("dap-tool-") ? rawModuleName : "dap-tool-" + rawModuleName;
|
||||
String portStr = getOrAsk(args, 1, scanner, "2. 사용할 포트 번호 (예: 8085): ");
|
||||
String shortName = moduleName.replace("dap-tool-", "").replace("-", "");
|
||||
|
||||
String defaultAuthor = System.getProperty("user.name");
|
||||
String defaultDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy.MM.dd"));
|
||||
|
||||
String author = getOrAsk(args, 2, scanner, "3. 작성자 (엔터 입력 시 '" + defaultAuthor + "'): ");
|
||||
if (author.trim().isEmpty()) author = defaultAuthor;
|
||||
String createDate = getOrAsk(args, 3, scanner, "4. 작성일 (엔터 입력 시 '" + defaultDate + "'): ");
|
||||
if (createDate.trim().isEmpty()) createDate = defaultDate;
|
||||
|
||||
String result = scaffoldPod(moduleName, portStr, shortName, author, createDate);
|
||||
System.out.println(result);
|
||||
}
|
||||
|
||||
private static String getOrAsk(String[] args, int index, Scanner scanner, String prompt) {
|
||||
if (args.length > index) {
|
||||
return args[index];
|
||||
}
|
||||
System.out.print(prompt);
|
||||
return scanner.nextLine().trim();
|
||||
}
|
||||
|
||||
public static String scaffoldPod(String moduleName, String portStr, String shortName, String author, String createDate) throws IOException {
|
||||
String envSourceDir = System.getenv("AXHUB_SOURCE_DIR");
|
||||
Path rootDir = envSourceDir != null ? Paths.get(envSourceDir) : Paths.get(".");
|
||||
|
||||
Path modulePath = rootDir.resolve(Paths.get(moduleName));
|
||||
if (Files.exists(modulePath)) {
|
||||
return "[오류] 이미 존재하는 모듈입니다: " + moduleName;
|
||||
}
|
||||
|
||||
StringBuilder log = new StringBuilder();
|
||||
log.append("[1/6] 모듈 디렉터리 생성 중...\n");
|
||||
Files.createDirectories(modulePath);
|
||||
|
||||
log.append("[2/6] build.gradle 생성 중...\n");
|
||||
String buildGradle = """
|
||||
plugins {
|
||||
id 'org.springframework.boot'
|
||||
}
|
||||
dependencies {
|
||||
implementation project(':dap-tool-core')
|
||||
}
|
||||
dependencies {
|
||||
compileOnly 'org.projectlombok:lombok:1.18.32'
|
||||
annotationProcessor 'org.projectlombok:lombok:1.18.32'
|
||||
}
|
||||
""";
|
||||
Files.writeString(modulePath.resolve("build.gradle"), buildGradle);
|
||||
|
||||
log.append("[3/6] Dockerfile 생성 중...\n");
|
||||
String dockerfile = """
|
||||
FROM eclipse-temurin:21-jdk-alpine
|
||||
WORKDIR /app
|
||||
COPY build/libs/%s-0.0.1-SNAPSHOT.jar app.jar
|
||||
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||
""".formatted(moduleName);
|
||||
Files.writeString(modulePath.resolve("Dockerfile"), dockerfile);
|
||||
|
||||
log.append("[4/6] Application 클래스 및 설정 파일 생성 중...\n");
|
||||
Path srcPath = modulePath.resolve("src/main/java/io/shinhanlife/dap/mcc/" + shortName);
|
||||
Files.createDirectories(srcPath);
|
||||
|
||||
String appClass = """
|
||||
package io.shinhanlife.dap.mcc.%s;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.mcc.%s
|
||||
* @className DapTool%sApplication
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author %s
|
||||
* @create %s
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* %s %s 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@SpringBootApplication(scanBasePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.common.adapter", "io.shinhanlife.dap.common.mcp", "io.shinhanlife.dap.common.config"})
|
||||
@ConfigurationPropertiesScan(basePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.common.adapter", "io.shinhanlife.dap.common.mcp", "io.shinhanlife.dap.common.config"})
|
||||
@EnableCaching
|
||||
public class DapTool%sApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(DapTool%sApplication.class, args);
|
||||
}
|
||||
}
|
||||
""".formatted(shortName, shortName, capitalize(shortName), author, createDate, createDate, author, capitalize(shortName), capitalize(shortName));
|
||||
Files.writeString(srcPath.resolve("DapTool" + capitalize(shortName) + "Application.java"), appClass);
|
||||
|
||||
Path resPath = modulePath.resolve("src/main/resources");
|
||||
Files.createDirectories(resPath);
|
||||
String applicationYml = """
|
||||
spring:
|
||||
config:
|
||||
import: "classpath:config/application-glow-local.yml"
|
||||
""";
|
||||
Files.writeString(resPath.resolve("application-local.yml"), applicationYml);
|
||||
|
||||
String applicationProperties = """
|
||||
server.port=%s
|
||||
spring.application.name=%s
|
||||
|
||||
spring.profiles.active=local
|
||||
|
||||
# Suppress Kafka Connection Logs
|
||||
logging.level.org.apache.kafka=ERROR
|
||||
|
||||
# Auto Prefix Namespace
|
||||
mcp.namespace=%s
|
||||
|
||||
# API 보안 키 설정
|
||||
mcp.security.tenant-domains.TESTER-DEV=ALL
|
||||
""".formatted(portStr, moduleName, shortName);
|
||||
Files.writeString(resPath.resolve("application.properties"), applicationProperties);
|
||||
|
||||
String applicationLocalProperties = """
|
||||
# Local 환경 전용 설정 (H2 메모리 DB 등)
|
||||
spring.datasource.url=jdbc:p6spy:h2:mem:testdb;DB_CLOSE_DELAY=-1;
|
||||
spring.datasource.driverClassName=com.p6spy.engine.spy.P6SpyDriver
|
||||
spring.datasource.username=sa
|
||||
spring.datasource.password=password
|
||||
|
||||
spring.h2.console.enabled=true
|
||||
# EIMS 동적 라우팅 접속 정보
|
||||
eims.http.url=http://localhost:${server.port}/api/gateway
|
||||
eims.tcp.host=127.0.0.1
|
||||
eims.tcp.port=8090
|
||||
eims.tcp.timeout=5000
|
||||
eims.jsp.form.url=http://localhost:${server.port}/mock/jsp-form
|
||||
eims.jsp.json.url=http://localhost:${server.port}/mock/jsp-json
|
||||
eims.mci.url=http://localhost:${server.port}/api/mock/esb/api
|
||||
eims.mcistring.url=http://localhost:${server.port}/api/mock/esb/string
|
||||
|
||||
# Gateway/Tool URLs
|
||||
axhub.gateway.url=http://localhost:8081
|
||||
axhub.tool.url=http://localhost:${server.port}
|
||||
|
||||
# Disable Kafka
|
||||
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration
|
||||
|
||||
# Suppress Kafka Connection Logs
|
||||
logging.level.org.apache.kafka=ERROR
|
||||
""";
|
||||
Files.writeString(resPath.resolve("application-local.properties"), applicationLocalProperties);
|
||||
|
||||
String applicationDevProperties = """
|
||||
# Render 클라우드 환경 전용 설정
|
||||
axhub.gateway.url=https://axhub-gateway.onrender.com
|
||||
axhub.tool.url=https://%s.onrender.com
|
||||
|
||||
# EIMS 동적 라우팅 접속 정보 (Mock)
|
||||
eims.http.url=http://localhost:${server.port}/api/gateway
|
||||
eims.tcp.host=127.0.0.1
|
||||
eims.tcp.port=8090
|
||||
eims.tcp.timeout=5000
|
||||
eims.jsp.form.url=http://localhost:${server.port}/mock/jsp-form
|
||||
eims.jsp.json.url=http://localhost:${server.port}/mock/jsp-json
|
||||
eims.mci.url=http://localhost:${server.port}/api/mock/esb/api
|
||||
eims.mcistring.url=http://localhost:${server.port}/api/mock/esb/string
|
||||
""".formatted(moduleName);
|
||||
Files.writeString(resPath.resolve("application-dev.properties"), applicationDevProperties);
|
||||
|
||||
String logbackXml = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<property name="LOG_PATTERN" value="%%d{yyyy-MM-dd HH:mm:ss.SSS} [%%thread] [%%X{traceId}] %%-5level %%logger{36} - %%msg%%n" />
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>${LOG_PATTERN}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>logs/%s.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>logs/%s-%%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<maxHistory>30</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${LOG_PATTERN}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE" />
|
||||
<appender-ref ref="FILE" />
|
||||
</root>
|
||||
<logger name="io.shinhanlife" level="DEBUG" />
|
||||
</configuration>
|
||||
""".formatted(moduleName, moduleName);
|
||||
Files.writeString(resPath.resolve("logback-spring.xml"), logbackXml);
|
||||
|
||||
log.append("[5/6] settings.gradle 에 모듈 등록 중...\n");
|
||||
Path settingsPath = rootDir.resolve(Paths.get("settings.gradle"));
|
||||
if (Files.exists(settingsPath)) {
|
||||
String settings = Files.readString(settingsPath);
|
||||
if (!settings.contains("include '" + moduleName + "'")) {
|
||||
Files.writeString(settingsPath, System.lineSeparator() + "include '" + moduleName + "'" + System.lineSeparator(), StandardOpenOption.APPEND);
|
||||
}
|
||||
}
|
||||
|
||||
log.append("[6/6] docker-compose.yml 에 서비스 추가 중...\n");
|
||||
Path dockerComposePath = rootDir.resolve(Paths.get("docker-compose.yml"));
|
||||
if (Files.exists(dockerComposePath)) {
|
||||
String compose = Files.readString(dockerComposePath);
|
||||
String serviceName = moduleName.replace("dap-", ""); // e.g. tool-payment
|
||||
if (!compose.contains(" " + serviceName + ":")) {
|
||||
String newService = """
|
||||
%s:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: %s/Dockerfile
|
||||
ports:
|
||||
- "%s:%s"
|
||||
depends_on:
|
||||
- redis
|
||||
environment:
|
||||
- TZ=Asia/Seoul
|
||||
- SPRING_REDIS_HOST=redis
|
||||
- SPRING_REDIS_PORT=6379
|
||||
- SPRING_DATA_REDIS_PORT=6379
|
||||
- AXHUB_GATEWAY_URL=http://gateway:8081
|
||||
- AXHUB_TOOL_URL=http://%s:%s
|
||||
- GLOW_COMMUNICATION_MCI_HOST=http://mci-mock
|
||||
- GLOW_COMMUNICATION_MCI_PORT=8080
|
||||
- GLOW_COMMUNICATION_EXTMCI_HOST=http://mci-mock
|
||||
- GLOW_COMMUNICATION_EXTMCI_PORT=8080
|
||||
- GLOW_COMMUNICATION_EAI_HOST=http://mci-mock
|
||||
- GLOW_COMMUNICATION_EAI_PORT=8080
|
||||
""".formatted(serviceName, moduleName, portStr, portStr, serviceName, portStr);
|
||||
Files.writeString(dockerComposePath, System.lineSeparator() + newService, StandardOpenOption.APPEND);
|
||||
}
|
||||
}
|
||||
|
||||
log.append("\n=========================================\n");
|
||||
log.append(" Pod Scaffolding Complete! \n");
|
||||
log.append("=========================================\n");
|
||||
log.append("1. [새로운 모듈] ").append(moduleName).append(" 폴더가 생성되었습니다.\n");
|
||||
log.append("2. [ToolScaffolder]를 사용해 이 모듈 안에 툴을 추가하세요.\n");
|
||||
log.append("3. 실행 전 Gradle 동기화(Sync)를 한 번 진행해 주세요.\n");
|
||||
return log.toString();
|
||||
}
|
||||
|
||||
private static String capitalize(String str) {
|
||||
if (str == null || str.isEmpty()) return str;
|
||||
return str.substring(0, 1).toUpperCase() + str.substring(1);
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package io.shinhanlife.dap.common.util;
|
||||
|
||||
import io.shinhanlife.dap.common.session.dto.SessionDto;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.common.util
|
||||
* @className SessionUtil
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
public class SessionUtil {
|
||||
|
||||
private static final String SESSION_KEY = "userInfo";
|
||||
|
||||
private SessionUtil() {}
|
||||
|
||||
public static SessionDto getSession() {
|
||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
if (attributes == null) return null;
|
||||
HttpSession session = attributes.getRequest().getSession(false);
|
||||
if (session == null) return null;
|
||||
return (SessionDto) session.getAttribute(SESSION_KEY);
|
||||
}
|
||||
|
||||
public static String getPrafNo() {
|
||||
SessionDto session = getSession();
|
||||
return session != null ? session.getPrafNo() : null;
|
||||
}
|
||||
|
||||
public static String getOgnzNo() {
|
||||
SessionDto session = getSession();
|
||||
return session != null ? session.getOgnzNo() : null;
|
||||
}
|
||||
}
|
||||
@@ -1,372 +0,0 @@
|
||||
package io.shinhanlife.dap.common.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Scanner;
|
||||
|
||||
/**
|
||||
* MCP Tool 코드를 자동 생성(Scaffolding)하는 유틸리티 클래스
|
||||
*
|
||||
* [실행 방법]
|
||||
* 방법 1. IDE(IntelliJ 등)에서 직접 실행 (대화형 모드 추천 ⭐)
|
||||
* - 이 클래스(ToolScaffolder.java)를 열고 main 메서드를 직접 실행(Run)합니다.
|
||||
* - 콘솔 창에 뜨는 질문에 차례대로 값을 입력하기만 하면 파일이 생성됩니다.
|
||||
*
|
||||
* 방법 2. 커맨드라인(터미널)에서 실행 (명령어 기반)
|
||||
* - 컴파일: javac -encoding UTF-8 dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/util/ToolScaffolder.java
|
||||
* - 실행: java -cp dap-tool-core/src/main/java io.shinhanlife.dap.mcc.util.ToolScaffolder [이름] [ID] "[설명]" "[그룹]" "[통신방식]" "[모듈명]"
|
||||
*/
|
||||
/**
|
||||
* @package io.shinhanlife.dap.mcc.util
|
||||
* @className ToolScaffolder
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
public class ToolScaffolder {
|
||||
|
||||
private static final String BASE_PACKAGE = "io.shinhanlife.dap.mcc";
|
||||
private static final String BASE_PACKAGE_PATH = "src/main/java/io/shinhanlife/dap/mcc";
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
Scanner scanner = new Scanner(System.in);
|
||||
|
||||
System.out.println("=========================================");
|
||||
System.out.println(" MCP Tool Scaffolder (Java CLI) ");
|
||||
System.out.println("=========================================\n");
|
||||
|
||||
String baseName = getOrAsk(args, 0, scanner, "1. 생성할 Tool의 기본 이름 (예: ExchangeRate) [영문 PascalCase]: ");
|
||||
String interfaceId = getOrAsk(args, 1, scanner, "2. 레거시 API 인터페이스 ID (예: EXCH_001): ");
|
||||
String description = getOrAsk(args, 2, scanner, "3. Tool 기능 설명 (예: 환율 조회): ");
|
||||
String group = getOrAsk(args, 3, scanner, "4. Tool 소속 그룹 (예: NOTIFICATION, CLAIM, POLICY, HR, CONTRACT, CUSTOMER 등): ");
|
||||
if (group.isEmpty()) group = "COMMON";
|
||||
String routingType = getOrAsk(args, 4, scanner, "5. 통신 프로토콜 (예: HTTP, TCP, MCI, EAI): ");
|
||||
if (routingType.trim().isEmpty()) {
|
||||
routingType = "HTTP";
|
||||
}
|
||||
String moduleName = getOrAsk(args, 5, scanner, "6. 코드를 생성할 모듈 (기본: dap-tool-other): ");
|
||||
if (moduleName.trim().isEmpty()) {
|
||||
moduleName = "dap-tool-other";
|
||||
}
|
||||
|
||||
String defaultAuthor = System.getProperty("user.name");
|
||||
String defaultDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy.MM.dd"));
|
||||
|
||||
String author = getOrAsk(args, 6, scanner, "7. 작성자 (엔터 입력 시 '" + defaultAuthor + "'): ");
|
||||
if (author.trim().isEmpty()) author = defaultAuthor;
|
||||
String createDate = getOrAsk(args, 7, scanner, "8. 작성일 (엔터 입력 시 '" + defaultDate + "'): ");
|
||||
if (createDate.trim().isEmpty()) createDate = defaultDate;
|
||||
|
||||
String result = scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, createDate, true);
|
||||
System.out.println(result);
|
||||
}
|
||||
|
||||
private static String getOrAsk(String[] args, int index, Scanner scanner, String prompt) {
|
||||
if (args.length > index) {
|
||||
return args[index];
|
||||
}
|
||||
System.out.print(prompt);
|
||||
return scanner.nextLine().trim();
|
||||
}
|
||||
|
||||
public static String scaffold(String baseName, String interfaceId, String description, String group, String routingType, String moduleName, String author, String createDate, boolean register) throws IOException {
|
||||
baseName = toPascalCase(baseName);
|
||||
String envSourceDir = System.getenv("AXHUB_SOURCE_DIR");
|
||||
Path rootDir = envSourceDir != null ? Paths.get(envSourceDir) : Paths.get(".");
|
||||
|
||||
Path serviceDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "service"));
|
||||
Path dtoDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "dto"));
|
||||
|
||||
String shortName = moduleName.replace("dap-tool-", "").replace("-", "");
|
||||
Path legacyDtoDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, shortName, "dto"));
|
||||
Path converterDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, shortName, "converter"));
|
||||
|
||||
Files.createDirectories(serviceDir);
|
||||
Files.createDirectories(dtoDir);
|
||||
Files.createDirectories(legacyDtoDir);
|
||||
Files.createDirectories(converterDir);
|
||||
|
||||
StringBuilder log = new StringBuilder();
|
||||
|
||||
// Generate Req DTO
|
||||
String reqContent = """
|
||||
package %s.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @package %s.dto
|
||||
* @className %sReq
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author %s
|
||||
* @create %s
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* %s %s 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class %sReq {
|
||||
// TODO: Add request fields here
|
||||
}
|
||||
""".formatted(BASE_PACKAGE, BASE_PACKAGE, baseName, author, createDate, createDate, author, baseName);
|
||||
Files.writeString(dtoDir.resolve(baseName + "Req.java"), reqContent);
|
||||
|
||||
// Generate Res DTO
|
||||
String resContent = """
|
||||
package %s.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @package %s.dto
|
||||
* @className %sRes
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author %s
|
||||
* @create %s
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* %s %s 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class %sRes {
|
||||
private String status;
|
||||
private String message;
|
||||
// TODO: Add response fields here
|
||||
}
|
||||
""".formatted(BASE_PACKAGE, BASE_PACKAGE, baseName, author, createDate, createDate, author, baseName);
|
||||
Files.writeString(dtoDir.resolve(baseName + "Res.java"), resContent);
|
||||
|
||||
String toolName = baseName.isEmpty() ? baseName : Character.toLowerCase(baseName.charAt(0)) + baseName.substring(1);
|
||||
|
||||
// Generate Service
|
||||
String serviceContent = """
|
||||
package %s.service;
|
||||
|
||||
import %s.annotation.McpFunction;
|
||||
import %s.annotation.McpTool;
|
||||
import %s.dto.%sReq;
|
||||
import %s.dto.%sRes;
|
||||
import %s.%s.converter.%sLegacyConverter;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/**
|
||||
* @package %s.service
|
||||
* @className %sService
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author %s
|
||||
* @create %s
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* %s %s 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Service
|
||||
@McpTool(
|
||||
routingType = "%s",
|
||||
categoryKey = "%s"
|
||||
)
|
||||
public class %sService extends AbstractMcpToolService {
|
||||
|
||||
private final %sLegacyConverter converter = Mappers.getMapper(%sLegacyConverter.class);
|
||||
|
||||
@McpFunction(
|
||||
displayName = "%s 툴",
|
||||
name = "%s",
|
||||
description = "%s",
|
||||
prompt = "%s",
|
||||
mappingId = "%s",
|
||||
register = %s,
|
||||
requiresApproval = false
|
||||
)
|
||||
public Object execute(%sReq req) {
|
||||
// %sLegacyReq legacyReq = converter.toLegacyReq(req);
|
||||
return executeLegacy("%s", "%s", req); // Or pass legacyReq
|
||||
}
|
||||
}
|
||||
""".formatted(
|
||||
BASE_PACKAGE,
|
||||
BASE_PACKAGE,
|
||||
BASE_PACKAGE,
|
||||
BASE_PACKAGE, baseName,
|
||||
BASE_PACKAGE, baseName,
|
||||
BASE_PACKAGE, shortName, baseName,
|
||||
BASE_PACKAGE,
|
||||
baseName,
|
||||
author,
|
||||
createDate,
|
||||
createDate, author,
|
||||
routingType, group.toLowerCase(),
|
||||
baseName,
|
||||
baseName, baseName,
|
||||
baseName, toolName, description, description + " 해줘.", interfaceId, register,
|
||||
baseName,
|
||||
baseName,
|
||||
routingType, interfaceId
|
||||
);
|
||||
|
||||
Files.writeString(serviceDir.resolve(baseName + "Service.java"), serviceContent);
|
||||
|
||||
|
||||
|
||||
// Generate Legacy Req DTO
|
||||
String legacyReqContent = """
|
||||
package %s.%s.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @package %s.%s.dto
|
||||
* @className %sLegacyReq
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author %s
|
||||
* @create %s
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* %s %s 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Data
|
||||
public class %sLegacyReq {
|
||||
// TODO: Add legacy request fields here
|
||||
}
|
||||
""".formatted(BASE_PACKAGE, shortName, BASE_PACKAGE, shortName, baseName, author, createDate, createDate, author, baseName);
|
||||
Files.writeString(legacyDtoDir.resolve(baseName + "LegacyReq.java"), legacyReqContent);
|
||||
|
||||
// Generate Legacy Res DTO
|
||||
String legacyResContent = """
|
||||
package %s.%s.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @package %s.%s.dto
|
||||
* @className %sLegacyRes
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author %s
|
||||
* @create %s
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* %s %s 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Data
|
||||
public class %sLegacyRes {
|
||||
// TODO: Add legacy response fields here
|
||||
}
|
||||
""".formatted(BASE_PACKAGE, shortName, BASE_PACKAGE, shortName, baseName, author, createDate, createDate, author, baseName);
|
||||
Files.writeString(legacyDtoDir.resolve(baseName + "LegacyRes.java"), legacyResContent);
|
||||
|
||||
// Generate Legacy Converter
|
||||
String converterContent = """
|
||||
package %s.%s.converter;
|
||||
|
||||
import %s.dto.%sReq;
|
||||
import %s.dto.%sRes;
|
||||
import %s.%s.dto.%sLegacyReq;
|
||||
import %s.%s.dto.%sLegacyRes;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/**
|
||||
* @package %s.%s.converter
|
||||
* @className %sLegacyConverter
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author %s
|
||||
* @create %s
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* %s %s 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Mapper(componentModel = "spring")
|
||||
public interface %sLegacyConverter {
|
||||
|
||||
// @Mapping(source = "sourceField", target = "targetField")
|
||||
%sLegacyReq toLegacyReq(%sReq req);
|
||||
|
||||
%sRes toRes(%sLegacyRes legacyRes);
|
||||
}
|
||||
""".formatted(
|
||||
BASE_PACKAGE, shortName,
|
||||
BASE_PACKAGE, baseName,
|
||||
BASE_PACKAGE, baseName,
|
||||
BASE_PACKAGE, shortName, baseName,
|
||||
BASE_PACKAGE, shortName, baseName,
|
||||
BASE_PACKAGE, shortName, baseName, author, createDate, createDate, author,
|
||||
baseName, baseName, baseName, baseName, baseName, baseName
|
||||
);
|
||||
Files.writeString(converterDir.resolve(baseName + "LegacyConverter.java"), converterContent);
|
||||
|
||||
log.append("\n=========================================\n");
|
||||
log.append(" Scaffolding Complete!\n");
|
||||
log.append("=========================================\n");
|
||||
log.append("[Service] ").append(serviceDir.resolve(baseName + "Service.java")).append("\n");
|
||||
log.append("[Req DTO] ").append(dtoDir.resolve(baseName + "Req.java")).append("\n");
|
||||
log.append("[Res DTO] ").append(dtoDir.resolve(baseName + "Res.java")).append("\n");
|
||||
log.append("[Legacy Req DTO] ").append(legacyDtoDir.resolve(baseName + "LegacyReq.java")).append("\n");
|
||||
log.append("[Legacy Res DTO] ").append(legacyDtoDir.resolve(baseName + "LegacyRes.java")).append("\n");
|
||||
log.append("[Legacy Converter] ").append(converterDir.resolve(baseName + "LegacyConverter.java")).append("\n");
|
||||
log.append("\n Tip: ").append(interfaceId).append(" 목업 데이터를 mock-responses.json에 추가하세요.\n");
|
||||
|
||||
return log.toString();
|
||||
}
|
||||
|
||||
private static String toPascalCase(String str) {
|
||||
if (str == null || str.isEmpty()) {
|
||||
return str;
|
||||
}
|
||||
StringBuilder result = new StringBuilder();
|
||||
boolean capitalizeNext = true;
|
||||
for (char c : str.toCharArray()) {
|
||||
if (c == '_' || c == '-' || c == ' ') {
|
||||
capitalizeNext = true;
|
||||
} else if (capitalizeNext) {
|
||||
result.append(Character.toUpperCase(c));
|
||||
capitalizeNext = false;
|
||||
} else {
|
||||
result.append(c);
|
||||
}
|
||||
}
|
||||
if (result.length() > 0) {
|
||||
result.setCharAt(0, Character.toUpperCase(result.charAt(0)));
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
package io.shinhanlife.dap.common.util;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.common.util
|
||||
* @className ToolSourceUpdater
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class ToolSourceUpdater {
|
||||
|
||||
public static void updateToolSource(String toolName, String domainGroup, String description, boolean register, Boolean requiresApproval) throws Exception {
|
||||
// 1. Find all *Service.java files in axhub-tool-* directories
|
||||
String envSourceDir = System.getenv("AXHUB_SOURCE_DIR");
|
||||
Path rootDir = envSourceDir != null ? Paths.get(envSourceDir) : Paths.get(".");
|
||||
|
||||
List<Path> javaFiles;
|
||||
try (Stream<Path> paths = Files.walk(rootDir)) {
|
||||
javaFiles = paths
|
||||
.filter(Files::isRegularFile)
|
||||
.filter(p -> p.toString().endsWith("Service.java"))
|
||||
.filter(p -> p.toString().contains("axhub-tool-"))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
Path targetFile = null;
|
||||
String content = null;
|
||||
|
||||
// 2. Find the specific file for the tool
|
||||
String functionName = toolName;
|
||||
if (toolName.contains("_")) {
|
||||
functionName = toolName.substring(toolName.indexOf("_") + 1);
|
||||
}
|
||||
|
||||
Pattern namePattern = Pattern.compile("@McpFunction\\s*\\([^)]*name\\s*=\\s*\"" + Pattern.quote(toolName) + "\"", Pattern.DOTALL);
|
||||
Pattern namePattern2 = Pattern.compile("@McpFunction\\s*\\([^)]*name\\s*=\\s*\"" + Pattern.quote(functionName) + "\"", Pattern.DOTALL);
|
||||
|
||||
for (Path path : javaFiles) {
|
||||
String text = Files.readString(path);
|
||||
if (namePattern.matcher(text).find()) {
|
||||
targetFile = path;
|
||||
content = text;
|
||||
break;
|
||||
} else if (namePattern2.matcher(text).find()) {
|
||||
targetFile = path;
|
||||
content = text;
|
||||
toolName = functionName; // Use baseName for subsequent replacements
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (targetFile == null) {
|
||||
throw new Exception("소스 코드를 찾을 수 없습니다: " + toolName);
|
||||
}
|
||||
|
||||
// 3. Update @McpTool group
|
||||
if (domainGroup != null && !domainGroup.trim().isEmpty()) {
|
||||
Pattern groupPattern = Pattern.compile("(@McpTool\\s*\\([^)]*group\\s*=\\s*\")([^\"]+)(\")", Pattern.DOTALL);
|
||||
Matcher groupMatcher = groupPattern.matcher(content);
|
||||
if (groupMatcher.find()) {
|
||||
content = groupMatcher.replaceFirst("$1" + domainGroup + "$3");
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Update @McpFunction description
|
||||
if (description != null) {
|
||||
Pattern funcPattern = Pattern.compile("(@McpFunction\\s*\\([^)]*name\\s*=\\s*\"" + Pattern.quote(toolName) + "\"[^)]*description\\s*=\\s*\")([^\"]+)(\")", Pattern.DOTALL);
|
||||
Matcher funcMatcher = funcPattern.matcher(content);
|
||||
if (funcMatcher.find()) {
|
||||
content = funcMatcher.replaceFirst("$1" + description.replace("\\", "\\\\").replace("$", "\\\\$") + "$3");
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Update register flag
|
||||
Pattern regPattern = Pattern.compile("(@McpFunction\\s*\\([^)]*name\\s*=\\s*\"" + Pattern.quote(toolName) + "\"[^)]*register\\s*=\\s*)(true|false)([^a-zA-Z0-9])", Pattern.DOTALL);
|
||||
Matcher regMatcher = regPattern.matcher(content);
|
||||
if (regMatcher.find()) {
|
||||
content = regMatcher.replaceFirst("$1" + register + "$3");
|
||||
} else {
|
||||
Pattern addRegPattern = Pattern.compile("(@McpFunction\\s*\\([^)]*name\\s*=\\s*\"" + Pattern.quote(toolName) + "\")", Pattern.DOTALL);
|
||||
Matcher addRegMatcher = addRegPattern.matcher(content);
|
||||
if (addRegMatcher.find()) {
|
||||
content = addRegMatcher.replaceFirst("$1, register = " + register);
|
||||
}
|
||||
}
|
||||
|
||||
// 5.5 Update requiresApproval flag
|
||||
if (requiresApproval != null) {
|
||||
Pattern appPattern = Pattern.compile("(@McpFunction\\s*\\([^)]*name\\s*=\\s*\"" + Pattern.quote(toolName) + "\"[^)]*requiresApproval\\s*=\\s*)(true|false)([^a-zA-Z0-9])", Pattern.DOTALL);
|
||||
Matcher appMatcher = appPattern.matcher(content);
|
||||
if (appMatcher.find()) {
|
||||
content = appMatcher.replaceFirst("$1" + requiresApproval + "$3");
|
||||
} else {
|
||||
Pattern addAppPattern = Pattern.compile("(@McpFunction\\s*\\([^)]*name\\s*=\\s*\"" + Pattern.quote(toolName) + "\")", Pattern.DOTALL);
|
||||
Matcher addAppMatcher = addAppPattern.matcher(content);
|
||||
if (addAppMatcher.find()) {
|
||||
content = addAppMatcher.replaceFirst("$1, requiresApproval = " + requiresApproval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Write back to file
|
||||
Files.writeString(targetFile, content);
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
package io.shinhanlife.dap.mcg.dto;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Tool(Agent)의 명세 및 라우팅 정보를 담고 있는 메타데이터 클래스
|
||||
* Redis 레지스트리에 저장되며, Planner와 Router 간의 통신 객체(Plan)로 사용됩니다.
|
||||
*/
|
||||
/**
|
||||
* @package io.shinhanlife.dap.mcg.dto
|
||||
* @className ToolMetadata
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
public class ToolMetadata {
|
||||
|
||||
// 1. Tool 기본 정보
|
||||
private String uid; // UUID 형식의 고유 식별자
|
||||
private String semver; // 버전 (예: 1.0.0)
|
||||
private String displayName; // 사람이 읽는 라벨 (1-128자)
|
||||
private String name; // MCP 서브툴 명칭 (64자 이하, 예: CustomerSearchTool)
|
||||
private String description; // 툴의 목적 및 설명 (LLM 프롬프트에 활용 가능)
|
||||
|
||||
// 2. 파라미터 스키마 (JSON Schema 형태의 Map)
|
||||
private Map<String, Object> parametersSchema;
|
||||
|
||||
// 2-0. 프론트엔드 UI용 함수별 프롬프트 매핑 (추가됨)
|
||||
private Map<String, String> actionPrompts;
|
||||
|
||||
// 2-1. 도메인 부서 그룹명 (category_key, 슬러그 형식)
|
||||
private String categoryKey;
|
||||
|
||||
// 2-2. 툴 처리 엔드포인트 URI 경로 (예: /api/tool/customer-info)
|
||||
private String endpoint;
|
||||
|
||||
// 2-3. Pod 실행 URL (독립적인 Microservice 라우팅용, 예: http://localhost:8082)
|
||||
private String podUrl;
|
||||
|
||||
// 2-4. 가시성 여부
|
||||
@Builder.Default
|
||||
private Boolean visible = true;
|
||||
|
||||
// 2-5. Redis 등록 여부 (UI 표출용)
|
||||
@Builder.Default
|
||||
private Boolean isRegistered = true;
|
||||
|
||||
// 2-6. HITL 승인 필요 여부
|
||||
@Builder.Default
|
||||
private Boolean requiresApproval = false;
|
||||
|
||||
|
||||
|
||||
// 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초당 허용 최대 요청 수
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package io.shinhanlife.glow;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
//@Schema(description = "응답 에러 객체. 성공 케이스일 경우 null. 실제 에러가 발생할 경우에만 예외명, 예외 메시지 필드 세팅 예정.")
|
||||
/**
|
||||
* @package io.shinhanlife.glow
|
||||
* @className BaseException
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
public class BaseException {
|
||||
|
||||
// @Schema(description = "Error 코드 Meta 참조 운영. (예) 20001, 50001 등", shinhanlife = "20001")
|
||||
String code;
|
||||
|
||||
// @Schema(description = "메시지")
|
||||
String message;
|
||||
|
||||
// @Schema(description = "예외명.", shinhanlife = "NullPointerException")
|
||||
@Builder.Default
|
||||
String exceptionName = "";
|
||||
|
||||
// @Schema(description = "예외상세", shinhanlife = "StackTrace")
|
||||
@Builder.Default
|
||||
String exceptionDetail = "";
|
||||
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package io.shinhanlife.glow;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.glow
|
||||
* @className BaseResponse
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@ToString
|
||||
@Getter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
public class BaseResponse<T> {
|
||||
|
||||
// @Schema(description = "응답 코드 HTTP STATUS 오류 - 실제 Header 는 200 으로 내려감", shinhanlife = "200")
|
||||
private int code;
|
||||
|
||||
// @Schema(description = "응답 메시지. 응답 코드와 매핑된 메시지.", shinhanlife = "데이터 생성 성공")
|
||||
private String message;
|
||||
|
||||
// @Schema(description = "응답 본문 데이터. 실제 비즈니스 데이터.")
|
||||
private T data;
|
||||
|
||||
private BaseException error;
|
||||
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package io.shinhanlife.glow;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.glow
|
||||
* @className BizException
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
public class BizException extends RuntimeException {
|
||||
public BizException(String s) {
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package io.shinhanlife.glow;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.glow
|
||||
* @className GlowAppServiceId
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface GlowAppServiceId {
|
||||
|
||||
String value();
|
||||
|
||||
String description() default "";
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package io.shinhanlife.glow;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.glow
|
||||
* @className GlowControllerId
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
public @interface GlowControllerId {
|
||||
String value();
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package io.shinhanlife.glow;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.glow
|
||||
* @className GlowIndexPaging
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface GlowIndexPaging {
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package io.shinhanlife.glow;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface GlowLogTarget {
|
||||
|
||||
Target[] value() default {};
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.glow
|
||||
* @className Target
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
enum Target {
|
||||
FILE, CONSOLE
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package io.shinhanlife.glow;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.glow
|
||||
* @className GlowLogger
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Component
|
||||
@Scope("prototype")
|
||||
public class GlowLogger {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(GlowLogger.class);
|
||||
|
||||
public void debug(String message, Object... args) { log.debug(message, args); }
|
||||
public void info(String message, Object... args) { log.info(message, args); }
|
||||
public void warn(String message, Object... args) { log.warn(message, args); }
|
||||
public void error(String message, Object... args) { log.error(message, args); }
|
||||
public void error(String message, Throwable t) { log.error(message, t); }
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package io.shinhanlife.glow;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.glow
|
||||
* @className GlowMciFieldInfo
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface GlowMciFieldInfo {
|
||||
|
||||
/**
|
||||
* 필드 순서
|
||||
*/
|
||||
int order();
|
||||
|
||||
/**
|
||||
* 필드 길이 (List인 경우 전체 길이로 활용될 수 있음)
|
||||
*/
|
||||
int length();
|
||||
|
||||
/**
|
||||
* 필드 설명
|
||||
*/
|
||||
String description() default "";
|
||||
|
||||
/**
|
||||
* List 매핑 시 대상 DTO 클래스
|
||||
*/
|
||||
Class<?> target() default void.class;
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package io.shinhanlife.glow;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.glow
|
||||
* @className GlowMybatisMapper
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* MyBatis Mapper 인터페이스를 나타내는 어노테이션
|
||||
* MyBatis Mapper와 동일한 기능을 제공합니다.
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Mapper
|
||||
@Component
|
||||
public @interface GlowMybatisMapper {
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package io.shinhanlife.glow;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.glow
|
||||
* @className GlowServiceGroupId
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
public @interface GlowServiceGroupId {
|
||||
String value();
|
||||
|
||||
String description();
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package io.shinhanlife.glow;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.glow
|
||||
* @className GlowTrgmField
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface GlowTrgmField {
|
||||
|
||||
/**
|
||||
* 필드 순서
|
||||
*/
|
||||
int order();
|
||||
|
||||
/**
|
||||
* 필드 길이
|
||||
*/
|
||||
int length();
|
||||
|
||||
/**
|
||||
* 필드 설명
|
||||
*/
|
||||
String description() default "";
|
||||
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
package io.shinhanlife.glow;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.apache.ibatis.session.RowBounds;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.glow
|
||||
* @className PageInfo
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class PageInfo extends RowBounds implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 페이지번호 ( 입력값 )
|
||||
*/
|
||||
@GlowTrgmField(order = 1, length = 5, description = "페이지번호")
|
||||
private int pageNo;
|
||||
|
||||
/**
|
||||
* 페이지 데이터 건수 ( 열 건수, 입력값 )
|
||||
*/
|
||||
@GlowTrgmField(order = 2, length = 5, description = "페이지데이터건수")
|
||||
private int pageDataCc;
|
||||
|
||||
/**
|
||||
* 총페이지 수 ( 리턴값 )
|
||||
*/
|
||||
@GlowTrgmField(order = 3, length = 10, description = "총페이지수")
|
||||
private int totaPageCn;
|
||||
|
||||
/**
|
||||
* 총 페이지 데이터 건수 ( 리턴값 )
|
||||
*/
|
||||
@GlowTrgmField(order = 4, length = 10, description = "총페이지데이터건수")
|
||||
private int totaPageDataCc;
|
||||
|
||||
public PageInfo(int pageNo, int pageDataCc) {
|
||||
super(((pageNo <= 0 ? 1 : pageNo) - 1) * pageDataCc, pageDataCc);
|
||||
this.pageNo = pageNo;
|
||||
this.pageDataCc = pageDataCc;
|
||||
}
|
||||
|
||||
public PageInfo() {
|
||||
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public int getOffset() {
|
||||
return super.getOffset();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public int getLimit() {
|
||||
return super.getLimit();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package io.shinhanlife.glow;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.glow
|
||||
* @className ResponseCode
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public enum ResponseCode {
|
||||
|
||||
/* ===================== 성공 ===================== */
|
||||
SUCCESS(HttpStatus.OK, "정상 처리되었습니다."),
|
||||
CREATED(HttpStatus.CREATED, "데이터 생성 성공"),
|
||||
|
||||
/* ===================== 클라이언트 오류 (4xx) ===================== */
|
||||
BAD_REQUEST(HttpStatus.BAD_REQUEST, "잘못된 요청입니다."),
|
||||
INVALID_PARAMETER(HttpStatus.BAD_REQUEST, "유효하지 않은 파라미터입니다."),
|
||||
UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "인증이 필요합니다."),
|
||||
FORBIDDEN(HttpStatus.FORBIDDEN, "접근 권한이 없습니다."),
|
||||
NOT_FOUND(HttpStatus.NOT_FOUND, "요청한 리소스를 찾을 수 없습니다."),
|
||||
METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED, "허용되지 않은 HTTP 메서드입니다."),
|
||||
CONFLICT(HttpStatus.CONFLICT, "이미 존재하는 데이터입니다."),
|
||||
|
||||
/* ===================== 서버 오류 (5xx) ===================== */
|
||||
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류가 발생했습니다."),
|
||||
SERVICE_UNAVAILABLE(HttpStatus.SERVICE_UNAVAILABLE, "서비스를 사용할 수 없습니다.");
|
||||
|
||||
private final HttpStatus status;
|
||||
private final String message;
|
||||
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
package io.shinhanlife.glow;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.glow
|
||||
* @className ResponseUtil
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
public final class ResponseUtil {
|
||||
|
||||
private ResponseUtil() {
|
||||
|
||||
}
|
||||
|
||||
public static <T> ResponseEntity<BaseResponse<T>> ok() {
|
||||
return ResponseEntity.ok(
|
||||
BaseResponse.<T>builder()
|
||||
.code(ResponseCode.SUCCESS.getStatus().value())
|
||||
.message(ResponseCode.SUCCESS.getMessage())
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
public static <T> ResponseEntity<BaseResponse<T>> ok(T data) {
|
||||
return ResponseEntity.ok(
|
||||
BaseResponse.<T>builder()
|
||||
.code(ResponseCode.SUCCESS.getStatus().value())
|
||||
.message(ResponseCode.SUCCESS.getMessage())
|
||||
.data(data)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
public static <T> ResponseEntity<BaseResponse<T>> ok(T data, ResponseCode responseCode) {
|
||||
return ResponseEntity.ok(
|
||||
BaseResponse.<T>builder()
|
||||
.code(responseCode.getStatus().value())
|
||||
.message(responseCode.getMessage())
|
||||
.data(data)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
public static <T> ResponseEntity<BaseResponse<T>> error(
|
||||
HttpStatus status, String code, String message
|
||||
) {
|
||||
return ResponseEntity.status(status)
|
||||
.body(
|
||||
BaseResponse.<T>builder()
|
||||
.code(status.value())
|
||||
.error(
|
||||
BaseException.builder()
|
||||
.code(code)
|
||||
.message(message)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
public static <T> ResponseEntity<BaseResponse<T>> error(
|
||||
HttpStatus status, String code, String message, String exceptionName, String stackTrace
|
||||
) {
|
||||
return ResponseEntity.status(status)
|
||||
.body(
|
||||
BaseResponse.<T>builder()
|
||||
.code(status.value())
|
||||
.error(
|
||||
BaseException.builder()
|
||||
.code(code)
|
||||
.message(message)
|
||||
.exceptionName(exceptionName)
|
||||
.exceptionDetail(stackTrace)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
public static <T> ResponseEntity<BaseResponse<T>> okError(
|
||||
HttpStatus status, String code, String message
|
||||
) {
|
||||
return ResponseEntity.ok(
|
||||
BaseResponse.<T>builder()
|
||||
.code(status.value())
|
||||
.error(
|
||||
BaseException.builder()
|
||||
.code(code)
|
||||
.message(message)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
public static <T> ResponseEntity<BaseResponse<T>> okError(
|
||||
HttpStatus status, String code, String message, String exceptionName, String stackTrace
|
||||
) {
|
||||
return ResponseEntity.ok(
|
||||
BaseResponse.<T>builder()
|
||||
.code(status.value())
|
||||
.error(
|
||||
BaseException.builder()
|
||||
.code(code)
|
||||
.message(message)
|
||||
.exceptionName(exceptionName)
|
||||
.exceptionDetail(stackTrace)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package io.shinhanlife.glow.db.dto;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.Builder;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.glow.db.dto
|
||||
* @className AuditInfo
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AuditInfo {
|
||||
private Date systRgiDt; // 시스템등록일시
|
||||
private String systRgiPrafNo; // 시스템등록인사번호
|
||||
private String systRgiOgnzNo; // 시스템등록조직번호
|
||||
private String systRgiSystCd; // 시스템등록시스템코드
|
||||
private String systRgiPrgrId; // 시스템등록프로그램ID
|
||||
private Date systChgDt; // 시스템변경일시
|
||||
private String systChgPrafNo; // 시스템변경인사번호
|
||||
private String systChgOgnzNo; // 시스템변경조직번호
|
||||
private String systChgSystCd; // 시스템변경시스템코드
|
||||
private String systChgPrgrId; // 시스템변경프로그램ID
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
package io.shinhanlife.glow.util;
|
||||
|
||||
import io.shinhanlife.glow.GlowMciFieldInfo;
|
||||
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 GlowMciParser
|
||||
* @description MCI 고정 길이 전문 파싱 유틸리티 (GlowMciFieldInfo 기반)
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
*/
|
||||
@Slf4j
|
||||
public class GlowMciParser {
|
||||
|
||||
public static <T> T parse(String mciString, Class<T> clazz) {
|
||||
if (mciString == null || mciString.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
T instance = clazz.getDeclaredConstructor().newInstance();
|
||||
|
||||
List<Field> fields = new ArrayList<>();
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
if (field.isAnnotationPresent(GlowMciFieldInfo.class)) {
|
||||
fields.add(field);
|
||||
}
|
||||
}
|
||||
|
||||
fields.sort(Comparator.comparingInt(f -> f.getAnnotation(GlowMciFieldInfo.class).order()));
|
||||
|
||||
int currentIndex = 0;
|
||||
for (Field field : fields) {
|
||||
GlowMciFieldInfo annotation = field.getAnnotation(GlowMciFieldInfo.class);
|
||||
int length = annotation.length();
|
||||
|
||||
if (currentIndex >= mciString.length()) {
|
||||
break;
|
||||
}
|
||||
|
||||
int endIndex = Math.min(currentIndex + length, mciString.length());
|
||||
String value = mciString.substring(currentIndex, endIndex);
|
||||
|
||||
field.setAccessible(true);
|
||||
setFieldValue(instance, field, value, annotation);
|
||||
|
||||
currentIndex += length;
|
||||
}
|
||||
|
||||
return instance;
|
||||
} catch (Exception e) {
|
||||
log.error("GlowMciFieldInfo 파싱 중 오류 발생: {}", e.getMessage(), e);
|
||||
throw new RuntimeException("MCI 전문 파싱 오류", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setFieldValue(Object instance, Field field, String value, GlowMciFieldInfo annotation) throws IllegalAccessException {
|
||||
Class<?> fieldType = field.getType();
|
||||
String trimmedValue = value.trim();
|
||||
|
||||
if (fieldType == String.class) {
|
||||
field.set(instance, trimmedValue);
|
||||
} else if (fieldType == int.class || fieldType == Integer.class) {
|
||||
field.set(instance, trimmedValue.isEmpty() ? 0 : Integer.parseInt(trimmedValue));
|
||||
} else if (fieldType == long.class || fieldType == Long.class) {
|
||||
field.set(instance, trimmedValue.isEmpty() ? 0L : Long.parseLong(trimmedValue));
|
||||
} else if (fieldType == boolean.class || fieldType == Boolean.class) {
|
||||
field.set(instance, Boolean.parseBoolean(trimmedValue));
|
||||
} else if (fieldType == double.class || fieldType == Double.class) {
|
||||
field.set(instance, trimmedValue.isEmpty() ? 0.0 : Double.parseDouble(trimmedValue));
|
||||
} else if (List.class.isAssignableFrom(fieldType)) {
|
||||
Class<?> targetClass = annotation.target();
|
||||
if (targetClass != void.class) {
|
||||
List<Object> list = new ArrayList<>();
|
||||
int itemLength = calculateTotalLength(targetClass);
|
||||
if (itemLength > 0) {
|
||||
for (int i = 0; i < value.length(); i += itemLength) {
|
||||
int end = Math.min(i + itemLength, value.length());
|
||||
String itemStr = value.substring(i, end);
|
||||
if (itemStr.trim().isEmpty()) continue;
|
||||
list.add(parse(itemStr, targetClass));
|
||||
}
|
||||
}
|
||||
field.set(instance, list);
|
||||
}
|
||||
} else {
|
||||
field.set(instance, trimmedValue);
|
||||
}
|
||||
}
|
||||
|
||||
private static int calculateTotalLength(Class<?> clazz) {
|
||||
int total = 0;
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
if (field.isAnnotationPresent(GlowMciFieldInfo.class)) {
|
||||
total += field.getAnnotation(GlowMciFieldInfo.class).length();
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package io.shinhanlife.glow.util;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.glow.util
|
||||
* @className GlowMciParserTest
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
import io.shinhanlife.glow.GlowMciFieldInfo;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
public class GlowMciParserTest {
|
||||
|
||||
public static class DummyMciDto {
|
||||
@GlowMciFieldInfo(order = 1, length = 10)
|
||||
private String customerId;
|
||||
|
||||
@GlowMciFieldInfo(order = 2, length = 15)
|
||||
private String name;
|
||||
|
||||
@GlowMciFieldInfo(order = 3, length = 3)
|
||||
private int age;
|
||||
|
||||
public String getCustomerId() { return customerId; }
|
||||
public String getName() { return name; }
|
||||
public int getAge() { return age; }
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("고정 길이 MCI 문자열을 DTO로 파싱하는 테스트")
|
||||
public void testParseFixedLengthString() {
|
||||
// given: 고정 길이 텍스트 (총 28자리)
|
||||
// ID(10) + Name(15) + Age(3)
|
||||
String rawMciString = "CUST000001KIM SHINHAN 035";
|
||||
|
||||
// when
|
||||
DummyMciDto result = GlowMciParser.parse(rawMciString, DummyMciDto.class);
|
||||
|
||||
// then
|
||||
System.out.println("==================================================");
|
||||
System.out.println(" [원본 MCI 전문] : [" + rawMciString + "]");
|
||||
System.out.println(" [파싱된 ID (10자리)] : [" + result.getCustomerId() + "]");
|
||||
System.out.println(" [파싱된 Name (15자리)] : [" + result.getName() + "]");
|
||||
System.out.println(" [파싱된 Age (3자리)] : [" + result.getAge() + "]");
|
||||
System.out.println("==================================================");
|
||||
|
||||
assertNotNull(result);
|
||||
assertEquals("CUST000001", result.getCustomerId());
|
||||
assertEquals("KIM SHINHAN", result.getName());
|
||||
assertEquals(35, result.getAge());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user